Communitygithub.com

architecture-hexagonale

Concevez, implémentez et refactorisez des systèmes Ports & Adaptateurs avec des limites de domaine claires, inversion de dépendances et orchestration de cas d'utilisation testable en TypeScript, Java, Kotlin et Go.

Qu'est-ce que architecture-hexagonale ?

Cette compétence aide les développeurs à appliquer l'architecture hexagonale (Ports et Adaptateurs) pour isoler la logique métier des détails d'infrastructure et de transport. Elle est utile lors de la création de nouvelles fonctionnalités nécessitant maintenabilité et testabilité, ou lors du refactoring de code legacy où la logique métier est mélangée à des préoccupations d'E/S. La compétence est supportée pour plusieurs langages (TypeScript, JavaScript, Java, Kotlin, Go) et plateformes d'agents IA comme Claude Code, Cursor ou Codex. Elle guide l'utilisateur à travers six étapes : modélisation des limites de cas d'utilisation, définition des ports sortants, implémentation de l'orchestration pure, construction d'adaptateurs, câblage dans un point de composition racine et test par limite. Elle inclut des exemples concrets, des schémas Mermaid, des structures de modules recommandées, et des anti-patterns à éviter. Un playbook de migration pas à pas est fourni, basé sur l'approche strangler pour éviter les réécritures complètes.

Compatible avec~Claude Code~Codex CLI~Cursor
npx skills add https://github.com/affaan-m/everything-claude-code/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 Adaptateurs) 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 en périphérie.

Quand l'utiliser

  • Construire de nouvelles fonctionnalités où la maintenabilité et la testabilité à long terme sont importantes.
  • Refactoriser un code fortement couplé aux frameworks 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 l'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 limites, une conception centrée domaine, le refactoring de services fortement couplés ou le découplage de la logique applicative de bibliothèques spécifiques.

Concepts Clés

  • Modèle de domaine : Règles métier et entités/objets valeur. Aucun import de framework.
  • Cas d'utilisation (couche applicative) : Orchestrent le comportement du domaine et les étapes des workflows.
  • 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 files d'attente, wrappers SDK).
  • Point de composition racine (composition root) : Emplacement de câblage unique où les adaptateurs concrets sont liés aux cas d'utilisation.

Les interfaces des ports sortants vivent généralement dans la couche applicative (ou dans le domaine uniquement lorsque l'abstraction est véritablement au niveau 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 ports (contrats entrants/sortants)
  • Domaine -> abstractions uniquement domaine (aucune dépendance framework ou infrastructure)
  • Domaine -> rien d'externe

Comment ça fonctionne

Étape 1 : Modéliser une limite de cas d'utilisation

Définissez un cas d'utilisation unique avec un DTO d'entrée et de sortie clair. Gardez les détails de transport (Express req, GraphQL context, wrappers de charge utile de tâche) en dehors de cette limite.

Étape 2 : Définir d'abord les ports sortants

Identifiez chaque effet de bord comme un port :

  • Persistance (UserRepositoryPort)
  • Appels externes (BillingGatewayPort)
  • Transversal (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/arguments. Elle valide les invariants au niveau applicatif, coordonne les règles du domaine et retourne des structures de données simples.

Étape 4 : Construire les adaptateurs en périphérie

  • Adaptateur entrant : Convertit l'entrée du protocole en entrée du cas d'utilisation.
  • Adaptateur sortant : Mappe les contrats de l'application aux API/ORM/constructeurs de requêtes concrets.
  • Le mapping reste dans les adaptateurs, pas à l'intérieur des cas d'utilisation.

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

Instanciez les adaptateurs, puis injectez-les dans les cas d'utilisation. Gardez ce câblage centralisé pour éviter tout comportement de service locator caché.

Étape 6 : Tester par limite

  • Test unitaire des cas d'utilisation avec des ports factices.
  • Test d'intégration des adaptateurs avec de vraies dépendances d'infrastructure.
  • Test de bout en bout des 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 Applicative)"]
  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 module suggérée

Utilisez une organisation par fonctionnalité avec des limites 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 de 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 retourne une nouvelle instance de 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 racine

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

Utilisez les mêmes règles de limite 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 par constructeur/arguments.
    • Adaptateurs : adapters/inbound/*, adapters/outbound/*.
    • Composition : module factory/container explicite (pas de globaux cachés).
  • 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 ; gardez le câblage hors des classes domaine/cas d'utilisation.
  • Kotlin
    • Modules/packages reflètent la séparation Java (domain, application.port, application.usecase, adapter).
    • Ports : interfaces Kotlin.
    • Cas d'utilisation : classes avec injection par constructeur (Koin/Dagger/Spring/manuelle).
    • Composition : définitions de modules ou fonctions de composition dédiées ; évitez les patterns de service locator.
  • 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 explicites New....
    • Composition : câblage dans cmd/<app>/main.go (ou package de câblage dédié), gardez les constructeurs explicites.

Anti-patterns à é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 dans req, res ou les métadonnées de file d'attente.
  • Retourner des lignes de base de données directement depuis les cas d'utilisation sans mapping domaine/applicatif.
  • 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 sur de nombreux fichiers avec des singletons globaux cachés.

Playbook de migration

  1. Choisissez une tranche verticale (un point de terminaison/tâche) avec une douleur de changement fréquent.
  2. Extrayez une limite de cas d'utilisation avec des types d'entrée/sortie explicites.
  3. Introduisez des ports sortants autour des appels d'infrastructure existants.
  4. Déplacez la logique d'orchestration des contrôleurs/services vers le cas d'utilisation.
  5. Gardez les anciens adaptateurs, mais faites-leur déléguer au nouveau cas d'utilisation.
  6. Ajoutez des tests autour de la nouvelle limite (unitaire + intégration adaptateur).
  7. Répétez tranche par tranche ; évitez les réécritures complètes.

Refactoring de systèmes existants

  • Approche étrangleur (strangler) : conservez les points de terminaison actuels, acheminez un cas d'utilisation à la fois via les nouveaux ports/adaptateurs.
  • Pas de réécritures big-bang : migrez par tranche fonctionnelle et préservez le comportement avec des tests de caractérisation.
  • Façade d'abord : enveloppez les services legacy derrière des ports sortants avant de remplacer les internes.
  • Geler la composition : centralisez 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 : priorisez les flux à forte vélocité de changement et à faible rayon d'impact en premier.
  • Chemin de retour : conservez un basculement réversible ou un commutateur de route par tranche migrée jusqu'à ce que le comportement en production soit vérifié.

Conseils de test (Mêmes limites hexagonales)

  • Tests de domaine : testez les entités/objets valeur comme des règles métier pures (pas de mocks, pas de configuration framework).
  • Tests unitaires de cas d'utilisation : testez l'orchestration avec des fakes/stubs pour les ports sortants ; vérifiez les résultats métier et les interactions avec les ports.
  • Tests de contrat des adaptateurs sortants : définissez des suites de contrats partagées au niveau du port et exécutez-les contre chaque implémentation d'adaptateur.
  • Tests d'adaptateurs entrants : vérifiez le mapping de protocole (charge utile HTTP/CLI/file d'attente vers entrée du cas d'utilisation et mapping de sortie/erreur vers le protocole).
  • Tests d'intégration des adaptateurs : exécutez contre une vraie infrastructure (BDD/API/file d'attente) pour la sérialisation, le comportement des schémas/requêtes, les tentatives et les timeouts.
  • Tests de bout en bout : couvrez les parcours utilisateur critiques via adaptateur entrant -> cas d'utilisation -> adaptateur sortant.
  • Sécurité du refactoring : ajoutez des tests de caractérisation avant l'extraction ; conservez-les jusqu'à ce que le comportement à la nouvelle limite 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 se produit aux limites (adaptateur entrant + invariants du cas d'utilisation).
  • Utilisez des transformations immuables (retournez de nouvelles valeurs/entités au lieu de muter un état partagé).
  • Les erreurs sont traduites à travers les limites (erreurs d'infrastructure -> erreurs applicatives/domaine).
  • Le point de composition racine est explicite et facile à auditer.
  • Les cas d'utilisation sont testables avec de simples fakes 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/claude-api

Anthropic Claude API patterns for Python and TypeScript. Covers Messages API, streaming, tool use, vision, extended thinking, batches, prompt caching, and Claude Agent SDK. Use when building applications with the Claude API or Anthropic SDKs.

affaan-m/everything-claude-code

End-to-end marketing campaign planning and execution. Covers audience research, positioning, campaign angle definition, landing page copy, email sequences, social posts, ad copy, short-form video scripts, and content calendars. Use as the orchestration layer for multi-channel product launches. Use when planning or executing a multi-channel product launch, or producing landing page, email, social, or ad copy.

affaan-m/everything-claude-code

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/everything-claude-code-conventions

Development conventions and patterns for everything-claude-code. JavaScript project with conventional commits.

affaan-m/frontend-design

Create distinctive, production-grade frontend interfaces with high design quality. Use when the user asks to build web components, pages, or applications and the visual direction matters as much as the code quality.

affaan-m/gget

gget CLI and Python workflow for quick genomic database queries, sequence lookup, BLAST-style searches, enrichment checks, and reproducible bioinformatics evidence logs.

affaan-m/literature-review

Systematic literature-review workflow for academic, biomedical, technical, and scientific topics, including search planning, source screening, synthesis, citation checks, and evidence logging.

affaan-m/motion-ui

Production-ready UI motion system for React/Next.js. Use when implementing animations, transitions, or motion patterns.

affaan-m/project-guidelines-example

Example project-specific skill template based on a real production application.

affaan-m/pubmed-database

Direct PubMed and NCBI E-utilities search workflows for biomedical literature, MeSH queries, PMID lookup, citation retrieval, and API-backed literature monitoring.

affaan-m/scholar-evaluation

Structured scholarly-work evaluation for papers, proposals, literature reviews, methods sections, evidence quality, citation support, and research-writing feedback.

affaan-m/uspto-database

USPTO patent and trademark data workflow for official record lookup, PatentSearch queries, TSDR checks, assignment data, and reproducible IP research logs.

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.

Skills associés