💻 Développement

dev-clean-architecture-guide

Guide d'implémentation de la Clean Architecture et Architecture Hexagonale.

⚡ Installation & lancement en 1 commande

Copiez-collez dans votre terminal : le skill s'installe dans ~/.claude/skills et Claude Code se lance directement dessus.

macOS / Linux
curl -fsSL https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.sh | sh -s -- dev-clean-architecture-guide --launch
Windows (PowerShell)
iex "& { $(iwr -useb https://raw.githubusercontent.com/khalilbenaz/claude-skills-collection/main/install.ps1) } dev-clean-architecture-guide -Launch"

🚀 Déjà installé ?

claude "/dev-clean-architecture-guide"

Ou tapez /dev-clean-architecture-guide dans une session Claude Code, ou décrivez simplement votre besoin — le skill se déclenche automatiquement via le skill-router.

🔑 Déclencheurs automatiques

Le skill s'active automatiquement quand votre demande contient :

clean architecturearchitecture hexagonaleports and adaptersonion architectureséparation des couchesdependency inversion

📦 Installation manuelle

git clone https://github.com/khalilbenaz/claude-skills-collection.git cp -r claude-skills-collection/skills/dev-clean-architecture-guide ~/.claude/skills/

Payload du plugin : skills/dev-clean-architecture-guide · source éditable : dev-skills/clean-architecture-guide

📖 Manuel

Clean Architecture Guide

Critères de décision : quand l'appliquer

SignalRecommandation
CRUD simple, faible logique métierNon — over-engineering garanti
Logique métier complexe, longue durée de vieOui — justifié
Plusieurs canaux d'entrée (API REST + CLI + message queue)Oui — ports & adapters
Équipe > 3 devs, domaines bornés distinctsOui — séparation forte
Prototype / MVP court termeNon — préférer une architecture plate modulaire

Workflow en étapes

1. Modéliser le domaine (cercle intérieur)

Pas de framework, pas de DTO externe, pas de ORM mapping ici. Uniquement des classes métier pures.

// Domain/Entities/Order.cs (.NET)
public class Order
{
    public Guid Id { get; private set; }
    public Money Total { get; private set; }
    public OrderStatus Status { get; private set; }

    public void Confirm()
    {
        if (Status != OrderStatus.Pending)
            throw new DomainException("Order already processed.");
        Status = OrderStatus.Confirmed;
        AddEvent(new OrderConfirmedEvent(Id));
    }
}

Checklist domaine :


2. Définir les ports (interfaces)

Les ports sont des contrats dans la couche Application ou Domain. Ils décrivent ce dont on a besoin, pas comment c'est implémenté.

// Application/Ports/IOrderRepository.ts (TypeScript)
export interface IOrderRepository {
  findById(id: string): Promise<Order | null>;
  save(order: Order): Promise<void>;
}

export interface IPaymentGateway {
  charge(amount: Money, token: string): Promise<PaymentResult>;
}

3. Implémenter les Use Cases (Application Services)

Un use case = une classe, une méthode publique, un résultat.

// Application/UseCases/ConfirmOrder/ConfirmOrderUseCase.ts
export class ConfirmOrderUseCase {
  constructor(
    private readonly orders: IOrderRepository,
    private readonly payment: IPaymentGateway,
    private readonly events: IEventBus,
  ) {}

  async execute(cmd: ConfirmOrderCommand): Promise<void> {
    const order = await this.orders.findById(cmd.orderId);
    if (!order) throw new NotFoundException('Order not found');

    order.confirm();
    await this.payment.charge(order.total, cmd.paymentToken);
    await this.orders.save(order);
    await this.events.publish(order.pullEvents());
  }
}

Règles use case :


4. Créer les Adapters (Infrastructure / Frameworks)

// Infrastructure/Adapters/Persistence/EfOrderRepository.cs
public class EfOrderRepository : IOrderRepository
{
    private readonly AppDbContext _db;
    public EfOrderRepository(AppDbContext db) => _db = db;

    public async Task<Order?> FindByIdAsync(Guid id) =>
        await _db.Orders.FirstOrDefaultAsync(o => o.Id == id);

    public async Task SaveAsync(Order order)
    {
        _db.Orders.Update(order);
        await _db.SaveChangesAsync();
    }
}

Adapters typiques à créer : Repository (DB), Gateway (API externe), EmailSender, FileStorage, Cache, MessagePublisher.


5. Arborescence de projet recommandée

src/
├── Domain/              # Entités, Value Objects, Domain Events, interfaces de domaine
│   ├── Entities/
│   ├── ValueObjects/
│   └── Events/
├── Application/         # Use Cases, DTOs, interfaces de ports
│   ├── UseCases/
│   └── Ports/
├── Infrastructure/      # Adapters : DB, HTTP, Message Queue, Cache
│   ├── Persistence/
│   ├── Gateways/
│   └── Messaging/
└── Presentation/        # Controllers, CLI, Workers (dépend d'Application uniquement)
    ├── Http/
    └── Workers/

Pour les petits projets : organiser par feature à l'intérieur de chaque couche.


6. Composition Root (DI)

Tout l'assemblage se fait à la frontière externe, jamais dans le domaine.

// Program.cs / Startup.cs (.NET)
builder.Services.AddScoped<IOrderRepository, EfOrderRepository>();
builder.Services.AddScoped<IPaymentGateway, StripePaymentAdapter>();
builder.Services.AddScoped<ConfirmOrderUseCase>();
// NestJS Module
@Module({
  providers: [
    ConfirmOrderUseCase,
    { provide: IOrderRepository, useClass: TypeOrmOrderRepository },
    { provide: IPaymentGateway, useClass: StripeAdapter },
  ],
})
export class OrderModule {}

7. Stratégie de tests par couche

CoucheType de testIsolation
DomainUnitAucune dépendance — vitesse maximale
Application (Use Cases)UnitMocks des ports (IOrderRepository, etc.)
Infrastructure (Adapters)IntegrationVraie DB (Testcontainers, SQLite in-memory)
PresentationE2EStack complète, HTTP réel
// Test unitaire use case — zéro infra
it('should confirm a pending order', async () => {
  const order = Order.create({ ... });
  const repo = { findById: vi.fn().mockResolvedValue(order), save: vi.fn() };
  const payment = { charge: vi.fn().mockResolvedValue({ success: true }) };

  await new ConfirmOrderUseCase(repo, payment, eventBus).execute({ orderId: order.id });

  expect(order.status).toBe(OrderStatus.Confirmed);
  expect(repo.save).toHaveBeenCalledWith(order);
});

Anti-patterns / Pièges

Anti-patternSymptômeCorrection
Anemic Domain ModelEntités = getters/setters, logique dans les servicesDéplacer la logique dans l'entité
Fuite d'infrastructureDbContext ou HttpClient injecté dans le domaineCréer un port + adapter
Use Case trop grosUn use case orchestre 10 opérationsDécouper en use cases ou domain services
DTO dans le domaineL'entité expose des champs de mapping ORMSéparer Entity et mapping model
Composition Root éclatéDI configuré dans plusieurs couchesCentraliser dans le projet hôte
Circular dependencyDomain importe Application ou InfrastructureVérifier la flèche de dépendance : toujours vers l'intérieur
Overuse d'abstractionsInterface 1:1 avec implémentation unique, jamais swappéeNe créer un port que s'il y a un vrai besoin d'inversion

Règle de dépendance — rappel absolu

Presentation → Application → Domain
Infrastructure → Application → Domain

Jamais l'inverse. Si le domaine importe un namespace infrastructure, c'est un bug d'architecture à corriger immédiatement via un port.


Bonnes pratiques 2026

# Enforcer les règles en CI (Node)
npx depcruise src --config .dependency-cruiser.js --output-type err