Rapport d'Architecture Logicielle - R-Type¶
Executive Summary¶
R-Type suit une architecture hexagonale (Ports & Adapters) rigoureuse en trois couches distinctes. La separation des responsabilites est claire et les patterns de conception sont appliques de maniere coherente.
Competences couvertes: 4 (architecture), 5 (composants logiciels), 6 (segmentation)
1. Architecture Hexagonale¶
Structure Generale¶
src/server/
├── include/
│ ├── domain/ # Coeur metier (31 fichiers)
│ ├── application/ # Cas d'usage (2 fichiers)
│ └── infrastructure/ # Adaptateurs et infrastructure
└── src/ # Implementations
1.1 Couche Domain (Coeur Metier)¶
Principe : Le domaine n'a AUCUNE dependance externe
Fichier : src/server/include/domain/entities/Player.hpp
namespace domain::entities {
class Player {
private:
value_objects::Health _health;
value_objects::player::PlayerId _id;
value_objects::Position _position;
// No infrastructure dependencies!
};
}
Entites metier (domain/entities/):
- Player.hpp - Joueur avec sante, position, ID
- User.hpp - Utilisateur avec authentification
- Room.hpp - Salle de jeu avec gestion des slots, etat
Value Objects (domain/value_objects/):
- Health.hpp - Immuable, encapsule validation
- PlayerId.hpp, UserId.hpp - IDs fortement types
- Email.hpp, Username.hpp - Entites de valeur avec validation
Exemple - Value Object immuable:
// domain/value_objects/Health.hpp
class Health {
private:
float _healthPoint;
void validate(float value);
public:
Health heal(float value) const; // Retourne NOUVELLE instance
Health damage(float value) const; // Retourne NOUVELLE instance
};
Services de Domaine (domain/services/):
- GameRule.hpp - 200+ lignes de regles metier pures:
- Calculs de degats d'armes
- Systeme de combo et score
- Statistiques des ennemis
- Mecanique du Wave Cannon
Extrait : src/server/include/domain/services/GameRule.hpp
class GameRule {
public:
uint8_t getMissileDamage(uint8_t weaponType, uint8_t weaponLevel) const;
float getMissileSpeed(uint8_t weaponType, uint8_t weaponLevel) const;
uint16_t getEnemyPointValue(uint8_t enemyType) const;
bool shouldBossChangePhase(uint16_t currentHP, uint16_t maxHP, uint8_t phase) const;
// All const, stateless methods - no I/O!
};
Exceptions de Domaine (domain/exceptions/):
- Hierarchie structuree
- DomainException -> entites specifiques (PlayerNameException, HealthException)
- Propriete : Le domaine lance des exceptions, jamais de codes d'erreur
1.2 Couche Application (Cas d'Usage)¶
Principe : Orchestre le domaine avec les ports
Fichier : src/server/include/application/use_cases/auth/Register.hpp
class Register {
private:
std::shared_ptr<IUserRepository> _userRepository; // OUT port
std::shared_ptr<IIdGenerator> _idGenerator; // OUT port
std::shared_ptr<ILogger> _logger; // OUT port
public:
std::optional<User> execute(
const std::string& username,
const std::string& email,
const std::string& unHashedPassword);
};
Dependency Injection Pattern : - Les ports sont injectes par le constructeur - Permet les tests unitaires sans adapter reel
Use Cases (application/use_cases/):
- auth/Register.hpp - Creer un utilisateur
- auth/Login.hpp - Authentifier un utilisateur
Ports de Sortie (application/ports/out/):
| Port | Role |
|---|---|
ILogger |
Abstraction logging |
IIdGenerator |
Abstraction generateur d'ID |
persistence/IUserRepository |
CRUD utilisateurs |
persistence/ILeaderboardRepository |
Classements |
persistence/IChatMessageRepository |
Messages chat |
persistence/IFriendshipRepository |
Systeme amis |
Exemple : src/server/include/application/ports/out/ILogger.hpp
class ILogger {
virtual void debug(const std::string& message) = 0;
virtual void info(const std::string& message) = 0;
virtual void warn(const std::string& message) = 0;
virtual void error(const std::string& message) = 0;
template<typename... Args>
void info(std::format_string<Args...> fmt, Args&&... args) {
info(std::format(fmt, std::forward<Args>(args)...));
}
};
Services d'Application (application/services/):
- AchievementChecker - Verifie et deverrouille les succes apres chaque jeu
- 10 types d'achievements (FirstBlood, Exterminator, ComboMaster, etc.)
- Logique metier appliquee en fin de partie
1.3 Couche Infrastructure (Adaptateurs)¶
Principe : Implementation des ports, communication reseau, persistence
A. Adaptateurs Entrants (IN)¶
Fichier : src/server/include/infrastructure/adapters/in/network/UDPServer.hpp
Serveur UDP pour messages de jeu en temps reel (20 Hz broadcast):
class UDPServer {
private:
game::GameInstanceManager _instanceManager;
std::shared_ptr<SessionManager> _sessionManager;
std::shared_ptr<ILeaderboardRepository> _leaderboardRepository;
void handle_receive(const boost::system::error_code& error, size_t bytes_transferred);
void broadcastSnapshotForRoom(const std::string& roomCode,
const std::shared_ptr<game::GameWorld>& gameWorld);
};
Adaptateurs Reseau Entrants :
- UDPServer.hpp - Jeu (port 4124, 20 Hz)
- TCPAuthServer.hpp - Authentification TLS (port 4125)
- TCPAdminServer.hpp - Admin en local (port 4127)
- VoiceUDPServer.hpp - Voice chat Opus (port 4126)
Architecture Reseau :
Client Server
│ │
│──── TCP (Login/Auth) ────────→ TCPAuthServer (4125)
│ │
│──── TCP (Room Mgmt) ────────→ TCPAuthServer (4125)
│ │
│──── UDP (Game) ────────────→ UDPServer (4124)
│←─── UDP (Snapshot 20Hz) ─────│
│ │
│──── UDP (Voice) ──────────→ VoiceUDPServer (4126)
│ │
└──── TCP (Admin) ──────────→ TCPAdminServer (4127) [localhost]
B. Adaptateurs Sortants (OUT)¶
Fichier : src/server/include/infrastructure/adapters/out/SpdLogAdapter.hpp
Implementation du port ILogger :
class SpdLogAdapter : public application::ports::out::ILogger {
void debug(const std::string& message) override {
server::logging::Logger::getMainLogger()->debug(message);
}
void info(const std::string& message) override {
server::logging::Logger::getMainLogger()->info(message);
}
// Adapte spdlog au port ILogger
};
Adaptateurs MongoDB (infrastructure/adapters/out/persistence/):
- MongoDBUserRepository.hpp - Implemente IUserRepository
- MongoDBLeaderboardRepository.hpp - Implemente ILeaderboardRepository
- MongoDBFriendshipRepository.hpp - Implemente IFriendshipRepository
- MongoDBChatMessageRepository.hpp - Implemente IChatMessageRepository
- MongoDBPrivateMessageRepository.hpp - Implemente IPrivateMessageRepository
Exemple : src/server/include/infrastructure/adapters/out/persistence/MongoDBUserRepository.hpp
class MongoDBUserRepository : public IUserRepository {
private:
std::shared_ptr<MongoDBConfiguration> _mongoDB;
mongocxx::collection getCollection();
public:
void save(const User& user) const override;
std::optional<User> findById(const std::string& id) override;
std::optional<User> findByEmail(const std::string& email) override;
};
2. Design Patterns Identifies¶
2.1 Hexagonal Architecture (Ports & Adapters)¶
| Composant | Exemple | Benefice |
|---|---|---|
| Port OUT | IUserRepository |
Decoupler de MongoDB |
| Adapter | MongoDBUserRepository |
Implementer le contrat |
| Domain | User, Email |
Logique metier pure |
| Use Case | Register |
Orchestrer le flux |
Preuve : Les ports definissent des contrats; les adaptateurs les implementent sans que le domaine les connaisse.
2.2 Dependency Injection Pattern¶
Fichier : src/server/include/application/use_cases/auth/Register.hpp (lignes 33-36)
explicit Register(
std::shared_ptr<IUserRepository> userRepository,
std::shared_ptr<IIdGenerator> idGenerator,
std::shared_ptr<ILogger> logger);
Avantages : - Testabilite (mock les ports) - Inversion de controle (IoC) - Flexibilite : changer la persistence sans modifier Register
2.3 Repository Pattern¶
Ports definissent le contrat :
// application/ports/out/persistence/IUserRepository.hpp
class IUserRepository {
virtual void save(const User& user) const = 0;
virtual std::optional<User> findById(const std::string& id) = 0;
virtual std::optional<User> findByEmail(const std::string& email) = 0;
};
Adaptateurs implementent pour MongoDB :
// infrastructure/adapters/out/persistence/MongoDBUserRepository.hpp
class MongoDBUserRepository : public IUserRepository {
void save(const User& user) const override { /* MongoDB insert */ }
std::optional<User> findById(const std::string& id) override { /* Query */ }
};
2.4 Singleton Pattern (Infrastructure)¶
Fichier : src/server/include/infrastructure/logging/Logger.hpp (lignes 26-40)
class Logger {
public:
static void init();
static std::shared_ptr<spdlog::logger> getMainLogger();
static std::shared_ptr<spdlog::logger> getNetworkLogger();
static std::shared_ptr<spdlog::logger> getGameLogger();
private:
static std::shared_ptr<spdlog::logger> s_mainLogger;
static std::shared_ptr<spdlog::logger> s_networkLogger;
};
Justification : - Logger doit etre unique pour toute l'application - Infrastructure pattern (pas domaine) - Acces global acceptable pour logging
2.5 Strategy Pattern (Armes)¶
Fichier : src/server/include/infrastructure/game/GameWorld.hpp (lignes 104-234)
struct Missile {
WeaponType weaponType; // Strategy selector
uint8_t weaponLevel;
static float getSpeed(WeaponType type, uint8_t level) {
float base = getBaseSpeed(type); // Strategy: base speed per type
return base * getSpeedMultiplier(level) / 100.0f;
}
static uint8_t getBaseDamage(WeaponType type) {
switch (type) {
case WeaponType::Spread: return DAMAGE_SPREAD;
case WeaponType::Laser: return DAMAGE_LASER;
case WeaponType::Missile: return DAMAGE_MISSILE;
default: return DAMAGE_STANDARD;
}
}
};
Strategies d'armes : | Arme | Vitesse | Degats | Temps de rechargement | |------|---------|--------|----------------------| | Standard | 600 | 20 | 0.3s | | Spread | 550 | 8x3 | 0.4s | | Laser | 900 | 12 | 0.18s | | Missile | 350 | 50 | 0.7s |
2.6 Observer Pattern (Signal)¶
Fichier : src/client/include/events/Signal.hpp
template<typename ...Args>
class Signal {
public:
using Slot = std::function<void(Args...)>;
void connect(const Slot& slot) {
slots_.push_back(slot);
}
void emit(Args... args) {
for (const auto& slot : slots_) {
slot(args...);
}
}
};
Utilisation : - Notifications d'evenements reseau - Callbacks pour UI updates - Systemes d'achievements
2.7 State Pattern (Salles)¶
Fichier : src/server/include/domain/entities/Room.hpp (lignes 44-49)
enum class State {
Waiting, // Attente de joueurs
Starting, // Compte a rebours
InGame, // Jeu en cours
Closed // Fermee
};
Transitions d'etat :
2.8 Template Method Pattern (Scenes Client)¶
Fichier : src/client/include/scenes/IScene.hpp (lignes 27-42)
class IScene {
public:
virtual void handleEvent(const events::Event& event) = 0;
virtual void update(float deltatime) = 0;
virtual void render() = 0;
};
Toutes les scenes heritent du template :
- GameScene.hpp
- LoginScene.hpp
- LobbyScene.hpp
- LeaderboardScene.hpp
- FriendsScene.hpp
- PrivateChatScene.hpp
2.9 Factory Pattern (Graphics Plugin)¶
Fichier : src/client/include/graphics/IGraphicPlugin.hpp
class IGraphicPlugin {
virtual const char* getName() const = 0;
virtual std::shared_ptr<IWindow> createWindow(
Vec2u winSize,
const std::string& name
) = 0;
virtual std::shared_ptr<core::IRenderer> createRenderer(
std::shared_ptr<graphics::IWindow> window
) = 0;
};
typedef graphics::IGraphicPlugin* (*create_t)();
typedef void (*destroy_t)(graphics::IGraphicPlugin*);
Implementations : - SFML (lib/sfml/include/plugins/) - SDL2 (lib/sdl2/include/plugins/)
Chargement dynamique (DynamicLib pattern) :
- Plugins charges a runtime via dlopen
- Pas de dependance statique a SFML ou SDL2
2.10 Chain of Responsibility (Scene Manager)¶
Fichier : src/client/include/scenes/SceneManager.hpp (lignes 17-72)
class SceneManager {
std::stack<std::unique_ptr<IScene>> _sceneStack;
void changeScene(std::unique_ptr<IScene> newScene);
void pushScene(std::unique_ptr<IScene> scene);
void popScene();
void handleEvent(const events::Event& event) {
// Passe evenement a la scene courante (au sommet de la pile)
if (auto scene = currentScene())
scene->handleEvent(event);
}
};
Chaine de traitement :
3. Separation des Responsabilites (SOLID)¶
3.1 Single Responsibility Principle¶
Exemple 1 - Domain :
- Health.hpp : Validation + mutation immuable
- PlayerId.hpp : Type-safe ID
Exemple 2 - Infrastructure :
- MongoDBUserRepository.hpp : CRUD utilisateurs
- SpdLogAdapter.hpp : Adaptateur de logging
- UDPServer.hpp : Communication reseau UDP
3.2 Open/Closed Principle¶
Extensible sans modification :
// Ajouter une nouvelle arme : creer strategy, pas modifier GameRule
enum class WeaponType {
Standard,
Spread,
Laser,
Missile,
WaveCannon // <- Ajout phase 3
// NewWeapon <- Ajout futur
};
3.3 Liskov Substitution Principle¶
Tous les adaptateurs implementent le contrat du port :
IUserRepository* repo = new MongoDBUserRepository(config);
// ou
IUserRepository* repo = new InMemoryUserRepository(); // Future test implementation
3.4 Interface Segregation Principle¶
Ports petits et cibles :
// ILogger.hpp
class ILogger {
virtual void info(...) = 0;
virtual void error(...) = 0;
};
// Separe de persistence
class IUserRepository {
virtual void save(const User&) = 0;
virtual std::optional<User> findById(...) = 0;
};
3.5 Dependency Inversion Principle¶
Les cas d'usage dependent des ports abstraits, pas des implementations :
// Register depend de IUserRepository (abstraction)
// Pas de dependance a MongoDBUserRepository (implementation)
explicit Register(std::shared_ptr<IUserRepository> userRepository);
4. Segmentation Architecturale¶
4.1 Par Domaine¶
Server Architecture Layers:
┌─────────────────────────────────────────┐
│ Infrastructure Layer (Adapters) │
│ - UDPServer, TCPAuthServer │
│ - MongoDBRepositories │
│ - Logger, NetworkStats │
├─────────────────────────────────────────┤
│ Application Layer (Orchestration) │
│ - Use Cases (Login, Register) │
│ - Services (AchievementChecker) │
├─────────────────────────────────────────┤
│ Domain Layer (Business Logic) │
│ - Entities (User, Player, Room) │
│ - Value Objects (Health, Position) │
│ - Services (GameRule, CollisionRule) │
│ - Exceptions (DomainException) │
└─────────────────────────────────────────┘
4.2 Par Feature¶
Game Features :
- GameWorld.hpp - State central
- Missile, Enemy, Boss - Entites de jeu
- PlayerScore, PowerUp, ForcePod - Mechaniques
Session Management :
- SessionManager.hpp - Gestion JWT, timeouts
- RoomManager.hpp - Lobbies, ready system
- GameInstanceManager.hpp - Multi-instance routing
Social Features : - Friendship system (0x060x protocol) - Private messaging (0x069x protocol) - Blocked users - Friend requests
5. Diagrammes Architecture¶
5.1 Architecture Hexagonale¶
graph TB
UI["Client UI<br/>(Scenes, Graphics)"]
TCP["TCP Adapters<br/>(Auth, Rooms)"]
UDP["UDP Adapter<br/>(Game State)"]
Domain["Domain<br/>(User, Player, Room<br/>GameRule, Health)"]
App["Application<br/>(Login, Register<br/>Achievement)"]
Mongo["MongoDB<br/>Repositories"]
Logger["Logger<br/>Adapter"]
UI -->|requests| TCP
TCP -->|calls| App
App -->|uses| Domain
App -->|queries| Mongo
UDP -->|game state| Domain
Domain -->|validation| App
App -->|logs| Logger
Logger -->|logs to| Mongo
style Domain fill:#fff4e6
style App fill:#f0f4ff
style Mongo fill:#e8f5e9
style TCP fill:#fce4ec
style UDP fill:#fce4ec
style Logger fill:#f3e5f5
5.2 Flux de Donnees Applicatif¶
sequenceDiagram
participant Client
participant TCPAuth as TCPAuthServer
participant Register as Register UseCase
participant Repo as IUserRepository
participant Domain as User Entity
participant Mongo as MongoDB
Client->>TCPAuth: Login credentials
TCPAuth->>Register: execute()
Register->>Repo: findByEmail()
Repo->>Mongo: Query
Mongo-->>Repo: User document
Repo-->>Domain: Deserialize to User
Domain->>Domain: verifyPassword()
alt Password valid
Register-->>TCPAuth: LoginAck + Token
TCPAuth-->>Client: Auth successful
else Password invalid
Register-->>TCPAuth: LoginNack
TCPAuth-->>Client: Auth failed
end
6. Metriques d'Architecture¶
| Metrique | Valeur | Status |
|---|---|---|
| Couches strictes | 3 (Domain, App, Infra) | Respectees |
| Dependances Domain | 0 externes | Excellent |
| Ports abstraits | 11+ interfaces | Complet |
| Adaptateurs MongoDB | 9 repositorys | Couvrant |
| Value Objects | 10+ classes | Immuables |
| Exceptions typees | 15+ classes | Specifiques |
| Services stateless | GameRule, CollisionRule | Pur |
| Injection de dependance | 100% UseCases | Systematique |
7. Points Forts de l'Architecture¶
- Separation parfaite : Domain ne connait PAS Infrastructure
- Testabilite : Ports permettent mocks faciles
- Extensibilite : Ajouter adapters sans modifier Domain
- Clarte de responsabilites : Chaque classe a UN role
- Protocol-driven design : Binary protocol bien structure
- ECS integration (Phase 4) : Infrastructure optionnelle (feature flag)
- Multi-instance support : GameInstanceManager route par room
- Thread-safety : Strands Boost.ASIO pour concurrence
CONCLUSION¶
R-Type implemente une Hexagonal Architecture exemplaire : - Domain isole et testable - Application orchestrant les cas d'usage - Infrastructure abstraite via ports - Patterns de conception appropries (Factory, Strategy, Observer, etc.) - Clean Code (nommage, organisation, responsabilites) - Prepare pour l'evolution (ECS phase 4, multi-instance)
Score global : 9/10 - Architecture solide et maintenable