Files
Mangarr/.cursor/rules/architecture.mdc

210 lines
6.1 KiB
Plaintext

---
description:
globs: *.php
alwaysApply: false
---
# Architecture Hexagonale de Mangarr
## Structure Générale
L'application suit une architecture hexagonale (ports & adapters) avec une séparation claire des responsabilités. Le code métier est organisé en domaines distincts dans le dossier `src/Domain/`.
```
src/
└── Domain/
├── Shared/ # Code partagé entre les domaines
├── Manga/ # Domaine de gestion des mangas
├── Reader/ # Domaine de lecture
└── Scraping/ # Domaine de scraping
```
## Organisation des Domaines
Chaque domaine suit la même structure hexagonale :
```
Domain/Manga/
├── Domain/ # Cœur métier
│ ├── Entity/ # Entités du domaine
│ ├── ValueObject/ # Objets de valeur
│ ├── Event/ # Événements du domaine
│ └── Exception/ # Exceptions métier
├── Application/ # Cas d'utilisation
│ ├── Command/ # Commandes (DTO)
│ ├── CommandHandler/# Gestionnaires de commandes
│ ├── Query/ # Requêtes (DTO)
│ ├── QueryHandler/ # Gestionnaires de requêtes
│ └── Response/ # Objets de réponse (DTO)
└── Infrastructure/ # Adaptateurs
├── Repository/ # Implémentation des repositories
├── Service/ # Services techniques
└── Persistence/ # Persistence des données
```
## Règles d'Architecture
### 1. Règles Générales
- Tout le code métier doit résider dans le namespace `App\Domain`
- Les dépendances externes doivent être limitées et explicitement autorisées
- Les exceptions standards et utilitaires autorisés :
- `DateTimeImmutable`
- `RuntimeException`
- `Exception`
- `DomainException`
- `Symfony\Component\HttpKernel\Exception`
- `InvalidArgumentException`
### 2. Domaine Shared
- Le domaine `Shared` ne doit dépendre d'aucun autre domaine
- Il contient les contrats et les types partagés entre les domaines
- Exemple : `App\Domain\Shared\Contract\UuidInterface`
### 3. Couche Domain
- Ne doit dépendre que d'elle-même et du domaine Shared
- Contient la logique métier pure
- Ne doit pas avoir de dépendances externes
- Structure des composants :
- Les `Entity` sont les objets métier principaux
- Les `ValueObject` sont immuables et s'auto-valident
- Les `Event` représentent les changements d'état du domaine
- Les `Exception` définissent les erreurs métier spécifiques
### 4. Couche Application
- Peut dépendre de son propre domaine et du domaine Shared
- Peut utiliser les dépendances externes autorisées :
- `Symfony\Component\Messenger`
- `Ramsey\Uuid`
- Ne doit JAMAIS dépendre de la couche Infrastructure
- Structure des composants :
- Les `Query` sont des DTO (Data Transfer Objects) en lecture seule
- Les `Command` sont des DTO pour les modifications
- Les `QueryHandler` doivent :
- Implémenter `QueryHandlerInterface`
- Prendre une seule `Query` en paramètre
- Retourner une `Response`
- Les `CommandHandler` doivent :
- Implémenter `CommandHandlerInterface`
- Prendre une seule `Command` en paramètre
- Ne pas retourner de valeur (void)
- Les `Response` sont des DTO immuables pour les résultats de requêtes
### 5. Couche Infrastructure
- Implémente les interfaces définies dans le domaine
- Peut dépendre de toutes les couches de son domaine
- Contient les adaptateurs pour les services externes
- Structure des composants :
- Les `Repository` implémentent les interfaces du domaine
- Les `Service` fournissent des fonctionnalités techniques
- La `Persistence` gère le stockage des données
## Flux de Dépendances
```
Infrastructure → Application → Domain
↓ ↓ ↓
External Shared Shared
```
## Validation
Les règles d'architecture sont validées par phparkitect. Les violations de ces règles entraîneront une erreur lors de la validation.
## Exemples de Code
### Domain Layer
```php
namespace App\Domain\Manga\Domain\Entity;
class Manga
{
private MangaId $id;
private Title $title;
private Description $description;
public function __construct(MangaId $id, Title $title)
{
$this->id = $id;
$this->title = $title;
}
}
```
### Application Layer
```php
namespace App\Domain\Manga\Application\Query;
readonly class GetMangaByIdQuery
{
public function __construct(
public string $id
) {}
}
namespace App\Domain\Manga\Application\QueryHandler;
class GetMangaByIdQueryHandler implements QueryHandlerInterface
{
public function __construct(
private MangaRepositoryInterface $mangaRepository
) {}
public function __invoke(GetMangaByIdQuery $query): MangaResponse
{
$manga = $this->mangaRepository->get($query->id);
return new MangaResponse($manga);
}
}
namespace App\Domain\Manga\Application\Command;
readonly class CreateMangaCommand
{
public function __construct(
public string $title,
public ?string $description = null,
) {}
}
namespace App\Domain\Manga\Application\CommandHandler;
class CreateMangaCommandHandler implements CommandHandlerInterface
{
public function __construct(
private MangaRepositoryInterface $mangaRepository
) {}
public function __invoke(CreateMangaCommand $command): void
{
$manga = Manga::create($command->title, $command->description);
$this->mangaRepository->save($manga);
}
}
namespace App\Domain\Manga\Application\Response;
readonly class MangaResponse
{
public function __construct(
public string $id,
public string $title,
public ?string $description
) {}
public static function fromEntity(Manga $manga): self
{
return new self(
$manga->getId()->toString(),
$manga->getTitle()->value(),
$manga->getDescription()?->value()
);
}
}
```
### Infrastructure Layer
```php
namespace App\Domain\Manga\Infrastructure\Repository;
use App\Domain\Manga\Domain\Repository\MangaRepositoryInterface;
class DoctrineMangaRepository implements MangaRepositoryInterface
{
// Implementation
}
```