Communitygithub.com

Architecture Hexagonale (Ports & Adapters)

Concevoir, implémenter et refactoriser des systèmes Ports & Adapters avec des frontières de domaine claires, une inversion de dépendances et une orchestration de cas d'utilisation testable pour les services TypeScript, Java, Kotlin et Go. À utiliser lors de l'introduction ou du refactoring vers Ports et Adapters, ou lorsque la logique métier est devenue entremêlée avec les E/S.

Qu'est-ce que Architecture Hexagonale (Ports & Adapters) ?

Cette compétence guide les développeurs dans l'adoption de l'architecture hexagonale (Ports and Adapters). Elle permet de maintenir une logique métier indépendante des frameworks, du transport et de la persistance. L'architecture hexagonale garantit que le noyau applicatif dépend uniquement de ports abstraits, les adaptateurs implémentant ces ports à la périphérie. La compétence est applicable à TypeScript, JavaScript, Java, Kotlin et Go, et fournit des conseils spécifiques pour la structure des packages et l'injection de dépendances. Elle détaille un processus étape par étape : modéliser une frontière de cas d'utilisation, définir les ports sortants, implémenter le cas d'utilisation avec une orchestration pure, construire les adaptateurs et tout câbler dans un point de composition centralisé. Les tests sont répartis par frontière (unitaires, intégration, end-to-end). Un playbook de migration est également fourni pour refactorer des systèmes existants en adoptant une approche par tranche verticale (strangler) plutôt qu'une réécriture complète. La compétence met en garde contre les anti-patrons courants (entités de domaine qui importent des ORM, cas d'utilisation qui lisent directement `req`/`res`, adaptateurs qui s'appellent directement, etc.) et propose une checklist de bonnes pratiques. Les exemples de code sont donnés en TypeScript, avec des mappings pour Java, Kotlin et Go.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/ECC/tree/main/skills/hexagonal-architecture

Demander à votre IA préférée

Ouvre une nouvelle conversation avec cette compétence d'agent déjà préchargée.

Documentation

Architecture Hexagonale

L'architecture hexagonale (Ports et Adapters) maintient la logique métier indépendante des frameworks, du transport et des détails de persistance. L'application centrale dépend de ports abstraits, et les adaptateurs implémentent ces ports à la périphérie.

Quand l'utiliser

  • Construire de nouvelles fonctionnalités où la maintenabilité à long terme et la testabilité comptent.
  • Refactoriser du code en couches ou fortement lié à un framework où la logique métier est mélangée aux préoccupations d'E/S.
  • Prendre en charge plusieurs interfaces pour le même cas d'utilisation (HTTP, CLI, workers de file d'attente, tâches cron).
  • Remplacer une infrastructure (base de données, API externes, bus de messages) sans réécrire les règles métier.

Utilisez cette compétence lorsque la demande implique des frontières, une conception centrée sur le domaine, un refactoring de services fortement couplés, ou un découplage de la logique applicative de bibliothèques spécifiques.

Concepts fondamentaux

  • Modèle de domaine : Règles métier et entités/objets valeur. Aucun import de framework.
  • Cas d'utilisation (couche application) : Orchestrent le comportement du domaine et les étapes du workflow.
  • Ports entrants : Contrats décrivant ce que l'application peut faire (commandes/requêtes/interfaces de cas d'utilisation).
  • Ports sortants : Contrats pour les dépendances dont l'application a besoin (référentiels, passerelles, éditeurs d'événements, horloge, UUID, etc.).
  • Adaptateurs : Implémentations d'infrastructure et de livraison des ports (contrôleurs HTTP, référentiels de base de données, consommateurs de file d'attente, wrappers SDK).
  • Point de composition : Emplacement unique de câblage où les adaptateurs concrets sont liés aux cas d'utilisation.

Les interfaces des ports sortants résident généralement dans la couche application (ou dans le domaine uniquement lorsque l'abstraction est véritablement au niveau du domaine), tandis que les adaptateurs d'infrastructure les implémentent.

La direction des dépendances est toujours vers l'intérieur :

  • Adaptateurs -> application/domaine
  • Application -> interfaces de port (contrats entrants/sortants)
  • Domaine -> abstractions propres au domaine (aucune dépendance framework ou infrastructure)
  • Domaine -> rien d'externe

Comment ça fonctionne

Étape 1 : Modéliser une frontière de cas d'utilisation

Définir un cas d'utilisation unique avec un DTO d'entrée et de sortie clair. Conserver les détails de transport (Express req, GraphQL context, wrappers de charge utile de travail) en dehors de cette frontière.

Étape 2 : Définir les ports sortants en premier

Identifier chaque effet de bord comme un port :

  • persistance (UserRepositoryPort)
  • appels externes (BillingGatewayPort)
  • transversaux (LoggerPort, ClockPort)

Les ports doivent modéliser des capacités, pas des technologies.

Étape 3 : Implémenter le cas d'utilisation avec une orchestration pure

La classe/fonction du cas d'utilisation reçoit les ports via le constructeur/les arguments. Elle valide les invariants au niveau de l'application, coordonne les règles du domaine et renvoie des structures de données simples.

Étape 4 : Construire les adaptateurs à la périphérie

  • L'adaptateur entrant convertit l'entrée du protocole en entrée du cas d'utilisation.
  • L'adaptateur sortant mappe les contrats de l'application aux API/ORM/constructeurs de requêtes concrets.
  • Le mappage reste dans les adaptateurs, pas à l'intérieur des cas d'utilisation.

Étape 5 : Câbler le tout dans un point de composition

Instancier les adaptateurs, puis les injecter dans les cas d'utilisation. Centraliser ce câblage pour éviter tout comportement de localisateur de services caché.

Étape 6 : Tester par frontière

  • Tester unitairement les cas d'utilisation avec des ports factices.
  • Tester l'intégration des adaptateurs avec les vraies dépendances d'infrastructure.
  • Tester de bout en bout les flux utilisateur via les adaptateurs entrants.

Diagramme d'architecture

flowchart LR
  Client["Client (HTTP/CLI/Worker)"] --> InboundAdapter["Adaptateur entrant"]
  InboundAdapter -->|"appelle"| UseCase["Cas d'utilisation (Couche application)"]
  UseCase -->|"utilise"| OutboundPort["Port sortant (Interface)"]
  OutboundAdapter["Adaptateur sortant"] -->|"implémente"| OutboundPort
  OutboundAdapter --> ExternalSystem["BDD/API/File d'attente"]
  UseCase --> DomainModel["Modèle de domaine"]

Structure de modules suggérée

Utiliser une organisation par fonctionnalité avec des frontières explicites :

src/
  features/
    orders/
      domain/
        Order.ts
        OrderPolicy.ts
      application/
        ports/
          inbound/
            CreateOrder.ts
          outbound/
            OrderRepositoryPort.ts
            PaymentGatewayPort.ts
        use-cases/
          CreateOrderUseCase.ts
      adapters/
        inbound/
          http/
            createOrderRoute.ts
        outbound/
          postgres/
            PostgresOrderRepository.ts
          stripe/
            StripePaymentGateway.ts
      composition/
        ordersContainer.ts

Exemple TypeScript

Définitions des ports

export interface OrderRepositoryPort {
  save(order: Order): Promise<void>;
  findById(orderId: string): Promise<Order | null>;
}

export interface PaymentGatewayPort {
  authorize(input: { orderId: string; amountCents: number }): Promise<{ authorizationId: string }>;
}

Cas d'utilisation

type CreateOrderInput = {
  orderId: string;
  amountCents: number;
};

type CreateOrderOutput = {
  orderId: string;
  authorizationId: string;
};

export class CreateOrderUseCase {
  constructor(
    private readonly orderRepository: OrderRepositoryPort,
    private readonly paymentGateway: PaymentGatewayPort
  ) {}

  async execute(input: CreateOrderInput): Promise<CreateOrderOutput> {
    const order = Order.create({ id: input.orderId, amountCents: input.amountCents });

    const auth = await this.paymentGateway.authorize({
      orderId: order.id,
      amountCents: order.amountCents,
    });

    // markAuthorized renvoie une nouvelle instance d'Order ; elle ne mute pas sur place.
    const authorizedOrder = order.markAuthorized(auth.authorizationId);
    await this.orderRepository.save(authorizedOrder);

    return {
      orderId: order.id,
      authorizationId: auth.authorizationId,
    };
  }
}

Adaptateur sortant

export class PostgresOrderRepository implements OrderRepositoryPort {
  constructor(private readonly db: SqlClient) {}

  async save(order: Order): Promise<void> {
    await this.db.query(
      "insert into orders (id, amount_cents, status, authorization_id) values ($1, $2, $3, $4)",
      [order.id, order.amountCents, order.status, order.authorizationId]
    );
  }

  async findById(orderId: string): Promise<Order | null> {
    const row = await this.db.oneOrNone("select * from orders where id = $1", [orderId]);
    return row ? Order.rehydrate(row) : null;
  }
}

Point de composition

export const buildCreateOrderUseCase = (deps: { db: SqlClient; stripe: StripeClient }) => {
  const orderRepository = new PostgresOrderRepository(deps.db);
  const paymentGateway = new StripePaymentGateway(deps.stripe);

  return new CreateOrderUseCase(orderRepository, paymentGateway);
};

Mapping multi-langage

Utiliser les mêmes règles de frontière dans tous les écosystèmes ; seuls la syntaxe et le style de câblage changent.

  • TypeScript/JavaScript
    • Ports : application/ports/* sous forme d'interfaces/types.
    • Cas d'utilisation : classes/fonctions avec injection via constructeur/argument.
    • Adaptateurs : adapters/inbound/*, adapters/outbound/*.
    • Composition : module factory/container explicite (pas de globales cachées).
  • Java
    • Packages : domain, application.port.in, application.port.out, application.usecase, adapter.in, adapter.out.
    • Ports : interfaces dans application.port.*.
    • Cas d'utilisation : classes simples (Spring @Service est optionnel, pas obligatoire).
    • Composition : configuration Spring ou classe de câblage manuelle ; garder le câblage hors des classes de domaine/cas d'utilisation.
  • Kotlin
    • Modules/packages suivent la division Java (domain, application.port, application.usecase, adapter).
    • Ports : interfaces Kotlin.
    • Cas d'utilisation : classes avec injection par constructeur (Koin/Dagger/Spring/manuel).
    • Composition : définitions de modules ou fonctions de composition dédiées ; éviter les patrons de localisateur de services.
  • Go
    • Packages : internal/<feature>/domain, application, ports, adapters/inbound, adapters/outbound.
    • Ports : petites interfaces possédées par le package applicatif consommateur.
    • Cas d'utilisation : structs avec champs d'interface et constructeurs New... explicites.
    • Composition : câblage dans cmd/<app>/main.go (ou package de câblage dédié), garder les constructeurs explicites.

Anti-patrons à éviter

  • Les entités du domaine qui importent des modèles ORM, des types de framework web ou des clients SDK.
  • Les cas d'utilisation qui lisent directement req, res ou des métadonnées de file d'attente.
  • Retourner des lignes de base de données directement depuis les cas d'utilisation sans mappage domaine/application.
  • Laisser les adaptateurs s'appeler directement au lieu de passer par les ports des cas d'utilisation.
  • Répartir le câblage des dépendances dans de nombreux fichiers avec des singletons globaux cachés.

Playbook de migration

  1. Choisir une tranche verticale (un seul endpoint/travail) avec une fréquence de changement élevée.
  2. Extraire une frontière de cas d'utilisation avec des types d'entrée/sortie explicites.
  3. Introduire des ports sortants autour des appels d'infrastructure existants.
  4. Déplacer la logique d'orchestration des contrôleurs/services vers le cas d'utilisation.
  5. Conserver les anciens adaptateurs, mais leur faire déléguer le travail au nouveau cas d'utilisation.
  6. Ajouter des tests autour de la nouvelle frontière (unitaire + intégration adaptateur).
  7. Répéter tranche par tranche ; éviter les réécritures complètes.

Refactoring de systèmes existants

  • Approche Strangler : conserver les endpoints actuels, router un cas d'utilisation à la fois via les nouveaux ports/adaptateurs.
  • Pas de réécriture big-bang : migrer par tranche de fonctionnalité et préserver le comportement avec des tests de caractérisation.
  • Façade d'abord : envelopper les services existants derrière des ports sortants avant de remplacer les internes.
  • Geler la composition : centraliser le câblage tôt pour que les nouvelles dépendances ne fuient pas dans les couches domaine/cas d'utilisation.
  • Règle de sélection des tranches : prioriser les flux à forte vélocité de changement et à faible rayon d'impact.
  • Chemin de retour : conserver un interrupteur réversible ou un aiguillage de route par tranche migrée jusqu'à ce que le comportement en production soit vérifié.

Conseils de test (Mêmes frontières hexagonales)

  • Tests du domaine : tester les entités/objets valeur comme des règles métier pures (pas de mocks, pas de configuration de framework).
  • Tests unitaires des cas d'utilisation : tester l'orchestration avec des fakes/stubs pour les ports sortants ; vérifier les résultats métier et les interactions avec les ports.
  • Tests de contrat des adaptateurs sortants : définir des suites de contrat partagées au niveau du port et les exécuter contre chaque implémentation d'adaptateur.
  • Tests des adaptateurs entrants : vérifier le mappage de protocole (charge utile HTTP/CLI/file d'attente vers entrée du cas d'utilisation et sortie/erreur mappée vers le protocole).
  • Tests d'intégration des adaptateurs : exécuter contre une vraie infrastructure (BDD/API/file d'attente) pour la sérialisation, le comportement schéma/requête, les tentatives et les délais d'attente.
  • Tests de bout en bout : couvrir les parcours utilisateur critiques via l'adaptateur entrant -> cas d'utilisation -> adaptateur sortant.
  • Sécurité du refactoring : ajouter des tests de caractérisation avant l'extraction ; les conserver jusqu'à ce que le comportement de la nouvelle frontière soit stable et équivalent.

Liste de vérification des bonnes pratiques

  • Les couches domaine et cas d'utilisation n'importent que des types internes et des ports.
  • Chaque dépendance externe est représentée par un port sortant.
  • La validation a lieu aux frontières (adaptateur entrant + invariants du cas d'utilisation).
  • Utiliser des transformations immuables (renvoyer de nouvelles valeurs/entités au lieu de muter un état partagé).
  • Les erreurs sont traduites à travers les frontières (erreurs d'infrastructure -> erreurs application/domaine).
  • Le point de composition est explicite et facile à auditer.
  • Les cas d'utilisation sont testables avec des fakes simples en mémoire pour les ports.
  • Le refactoring commence par une tranche verticale avec des tests préservant le comportement.
  • Les spécificités du langage/framework restent dans les adaptateurs, jamais dans les règles du domaine.

Individual skills in this repo

This repo contains 20 individual skills — each has its own dedicated page.

accessibility

Design, implement, and audit inclusive digital products using WCAG 2.2 Level AA. Use when building or auditing UI that must meet WCAG 2.2 Level AA, or when reviewing a change for keyboard, contrast, or screen-reader support.

affaan-m/content-engine

Create platform-native content systems for X, LinkedIn, TikTok, YouTube, newsletters, and repurposed multi-platform campaigns. Use when the user wants social posts, threads, scripts, content calendars, or one source asset adapted cleanly across platforms.

affaan-m/ECC

Multi-platform content distribution across X, LinkedIn, Threads, and Bluesky. Adapts content per platform using content-engine patterns. Never posts identical content cross-platform. Use when the user wants to distribute content across social platforms.

affaan-m/fal-ai-media

Unified media generation via fal.ai MCP — image, video, and audio. Covers text-to-image (Nano Banana), text/image-to-video (Seedance, Kling, Veo 3), text-to-speech (CSM-1B), and video-to-audio (ThinkSound). Use when the user wants to generate images, videos, or audio with AI.

affaan-m/manim-video

日本語翻訳:このファイルは manim-video 用の日本語翻訳が必要です

affaan-m/remotion-video-creation

Remotion のベストプラクティス - React で動画を作成する。3D、アニメーション、音声、字幕、チャート、トランジションなどをカバーするドメイン固有の29のルール。

affaan-m/video-editing

AI-assisted video editing workflows for cutting, structuring, and augmenting real footage. Covers the full pipeline from raw capture through FFmpeg, Remotion, ElevenLabs, fal.ai, and final polish in Descript or CapCut. Use when the user wants to edit video, cut footage, create vlogs, or build video content.

agent-architecture-audit

Full-stack diagnostic for agent and LLM applications. Audits the 12-layer agent stack for wrapper regression, memory pollution, tool discipline failures, hidden repair loops, and rendering corruption. Produces severity-ranked findings with code-first fixes. Essential for developers building agent applications, autonomous loops, or any LLM-powered feature. Use when an agent or LLM feature misbehaves and the failing layer is unknown, or before shipping an agent stack.

agent-eval

Head-to-head comparison of coding agents (Claude Code, Aider, Codex, etc.) on custom tasks with pass rate, cost, time, and consistency metrics. Use when choosing between coding agents, or when a change to an agent setup needs measured pass rate, cost, and time rather than an impression.

agent-harness-construction

Design and optimize AI agent action spaces, tool definitions, and observation formatting for higher completion rates. Use when defining or revising an agent

agentic-engineering

Operate as an agentic engineer using eval-first execution, decomposition, and cost-aware model routing. Use when planning or executing engineering work that agents will carry out end to end.

agentic-os

Build persistent multi-agent operating systems on Claude Code. Covers kernel architecture, specialist agents, slash commands, file-based memory, scheduled automation, and state management without external databases. Use when building a persistent multi-agent system on Claude Code with its own memory, commands, and scheduling.

agent-introspection-debugging

Structured self-debugging workflow for AI agent failures using capture, diagnosis, contained recovery, and introspection reports. Use when an agent run fails and you need a reproducible diagnosis instead of a retry.

agent-payment-x402

Add x402 payment execution to AI agents with per-task budgets, spending controls, and non-custodial wallets. Supports Base through agentwallet-sdk and X Layer through OKX Payments / OKX Agent Payments Protocol. Use when an agent must pay for something itself and needs per-task budgets, spending controls, and a non-custodial wallet.

agent-self-evaluation

Use after completing any non-trivial task. The agent self-rates its output on 5 axes — accuracy, completeness, clarity, actionability, conciseness — with concrete evidence per criterion. Produces a structured 1-5 scorecard with specific improvement suggestions.

agent-sort

Build an evidence-backed ECC install plan for a specific repo by sorting skills, commands, rules, hooks, and extras into DAILY vs LIBRARY buckets using parallel repo-aware review passes. Use when ECC should be trimmed to what a project actually needs instead of loading the full bundle.

ai-first-engineering

Engineering operating model for teams where AI agents generate a large share of implementation output. Use when setting team process, review gates, or ownership rules for a codebase largely written by agents.

ai-regression-testing

Regression testing strategies for AI-assisted development. Sandbox-mode API testing without database dependencies, automated bug-check workflows, and patterns to catch AI blind spots where the same model writes and reviews code. Use when adding regression coverage to AI-assisted code, or when the same model both wrote and reviewed a change.

android-clean-architecture

Clean Architecture patterns for Android and Kotlin Multiplatform projects — module structure, dependency rules, UseCases, Repositories, and data layer patterns. Use when structuring modules, layers, or data flow in an Android or KMP project.

angular-developer

Generates Angular code and provides architectural guidance. Trigger when creating projects, components, or services, or for best practices on reactivity (signals, linkedSignal, resource), forms, dependency injection, routing, SSR, accessibility (ARIA), animations, styling (component styles, Tailwind CSS), testing, or CLI tooling.

Skills associés