Anti-Corruption Layer

Protéger un modèle interne face à un système externe en traduisant les concepts au lieu de laisser fuiter un modèle étranger dans le domaine.

Le problème

Quand un système doit intégrer un autre système, le danger n’est pas seulement technique.

Le vrai danger est souvent métier :

  • on reprend les noms du système externe dans notre code
  • on laisse ses statuts piloter notre logique
  • on expose ses objets jusque dans le domaine
  • on adapte notre modèle interne à ses contraintes
  • on finit par coder “comme l’autre système pense”

Résultat :

  • le domaine interne devient flou
  • les concepts métier perdent leur sens local
  • un changement externe force des changements profonds chez nous
  • les intégrations deviennent du couplage caché
  • le système externe contamine progressivement le modèle

Exemple classique :

  • un provider de paiement a ses propres statuts
  • un ERP a son propre modèle client
  • un CRM a sa propre définition d’un contrat
  • un partenaire expose une API avec des noms et structures arbitraires

Si on laisse tout ça entrer directement dans le cœur du système, on perd rapidement la cohérence du modèle interne.

L’anti-corruption layer sert à éviter ça.

L’idée simple

Un anti-corruption layer est une couche de traduction entre un modèle externe et un modèle interne.

L’idée est simple :

  • le système externe parle son langage
  • notre système parle le sien
  • on traduit à la frontière au lieu de fusionner les deux

Autrement dit :

  • on n’importe pas directement le modèle externe dans notre domaine
  • on absorbe l’écart dans une couche dédiée
  • on protège le sens de notre modèle interne

Le mot important ici est protéger.

Le sujet n’est pas juste de faire un mapping technique. Le sujet est d’éviter qu’un modèle étranger déforme le nôtre.

Comment ça fonctionne

1. Le système externe garde son propre modèle

Un système externe a souvent :

  • ses noms
  • ses structures
  • ses statuts
  • ses règles implicites
  • ses identifiants
  • ses erreurs

Ce modèle peut être parfaitement valide chez lui. Mais il n’a pas de raison de devenir le langage de notre domaine.

Exemple :

un provider de paiement peut parler de :

  • payment_intent
  • requires_capture
  • processing
  • succeeded

Ces termes peuvent être utiles au bord du système. Ils ne doivent pas forcément devenir les concepts internes du domaine.

2. Le modèle interne doit rester cohérent

Le modèle interne existe pour servir notre métier, pas pour refléter fidèlement une API externe.

Il peut donc avoir :

  • d’autres noms
  • d’autres regroupements
  • d’autres frontières
  • d’autres statuts
  • d’autres règles

Exemple :

notre système peut vouloir parler de :

  • PaymentRequested
  • PaymentAuthorized
  • PaymentFailed

Même si le provider externe a un vocabulaire plus technique ou plus détaillé.

3. La traduction se fait à la frontière

L’anti-corruption layer vit au bord entre les deux mondes.

Son rôle :

  • recevoir des données externes
  • les traduire vers le modèle interne
  • traduire aussi dans l’autre sens si nécessaire
  • isoler les détails externes
  • empêcher la fuite de concepts étrangers vers le domaine

Ce n’est pas forcément une grosse couche. Parfois, c’est un adapter avec une logique de mapping claire. Mais la responsabilité doit être explicite.

Schéma

Anti-Corruption Layer

Lecture utile :

  • le système externe ne parle pas directement au domaine
  • la traduction se fait à la frontière
  • le modèle interne garde son propre langage

Exemple concret

Prenons un système de facturation qui dépend d’un provider de paiement externe.

Le provider expose ce type de payload :

export type ExternalPaymentStatus = {
  id: string;
  state: "requires_payment_method" | "processing" | "succeeded" | "canceled";
  amount: number;
  currency: string;
};

Si on laisse ce modèle entrer directement dans le domaine, on risque de propager partout :

  • requires_payment_method
  • processing
  • succeeded
  • canceled

Or ce ne sont pas forcément les concepts utiles pour notre système.

Modèle interne

Notre domaine veut peut-être parler de quelque chose de plus métier :

export type PaymentState =
  | "pending"
  | "authorized"
  | "completed"
  | "failed";

Traduction dans l’anti-corruption layer

export class PaymentProviderTranslator {
  toInternalState(external: ExternalPaymentStatus): PaymentState {
    switch (external.state) {
      case "requires_payment_method":
        return "pending";
      case "processing":
        return "authorized";
      case "succeeded":
        return "completed";
      case "canceled":
        return "failed";
    }
  }
}

Ici, la traduction :

  • protège le vocabulaire interne
  • évite d’exposer les statuts externes au domaine
  • centralise la dépendance au provider

Le domaine continue à parler son propre langage.

Exemple avec un contexte métier externe

Le cas n’est pas réservé aux APIs techniques.

Imaginons un système Billing qui consomme des événements venant d’un système Sales.

Événement externe

export type ContractWonEvent = {
  contractId: string;
  accountExecutiveId: string;
  billingContactEmail: string;
  offerCode: string;
};

Modèle utile pour Billing

export type BillingAccountCreationRequest = {
  customerEmail: string;
  planCode: string;
};

Traduction

export class SalesToBillingTranslator {
  toBillingAccountCreationRequest(
    event: ContractWonEvent
  ): BillingAccountCreationRequest {
    return {
      customerEmail: event.billingContactEmail,
      planCode: event.offerCode,
    };
  }
}

Ici encore, le système Billing ne réutilise pas directement le modèle de Sales. Il traduit ce qui est utile dans son propre langage.

C’est exactement le rôle d’un anti-corruption layer.

Quand il devient utile

Un anti-corruption layer devient utile quand au moins un de ces signaux apparaît :

  • un système externe a un vocabulaire fort qui commence à envahir le domaine
  • les statuts externes ne correspondent pas au modèle interne
  • plusieurs champs externes doivent être recombinés pour faire sens chez nous
  • les erreurs externes doivent être traduites en décisions internes
  • le modèle externe change souvent
  • plusieurs intégrations différentes doivent alimenter le même concept interne

Plus l’écart entre les deux modèles est grand, plus cette couche devient précieuse.

Quand il peut rester léger

Il ne faut pas non plus sur-construire.

Si l’intégration est très simple et que :

  • le vocabulaire est déjà très proche
  • le couplage est limité
  • le mapping tient en peu de règles
  • l’intégration reste périphérique

alors l’anti-corruption layer peut rester léger.

Parfois, un adapter bien écrit suffit.

Le sujet n’est pas la taille de la couche. Le sujet est la clarté de la traduction.

Où le placer

En pratique, cette responsabilité vit souvent :

  • dans un adapter d’intégration
  • dans une couche de mapping dédiée
  • dans un module de traduction entre contextes

Exemple de structure :

src/
  billing/
    application/
      create-billing-account/
        create-billing-account.service.ts
    domain/
      billing-account.ts
    infrastructure/
      integrations/
        sales/
          sales-contract-won.handler.ts
          sales-to-billing.translator.ts

Le point important n’est pas le nom exact du dossier. Le point important est que la traduction soit visible et explicite.

Points importants

  • Un anti-corruption layer protège le modèle interne.
  • Il traduit entre deux langages, pas seulement entre deux formats techniques.
  • Il évite qu’un système externe impose ses concepts au domaine.
  • Il est utile aussi bien entre systèmes externes qu’entre bounded contexts.
  • Il peut être léger, mais sa responsabilité doit être claire.
  • Le but n’est pas de copier le modèle externe proprement, mais de préserver le modèle interne.

Erreurs fréquentes

Reprendre directement le modèle externe dans le domaine

C’est l’erreur la plus fréquente.

On commence “pour aller vite”, puis les noms, statuts et objets externes se répandent partout.

Réduire le problème à un mapping purement technique

Le sujet n’est pas seulement de convertir un JSON en objet. Le sujet est de traduire un sens métier.

Laisser les statuts externes piloter les décisions internes

Quand le domaine commence à raisonner directement en processing, requires_capture ou autres statuts d’un provider, la contamination a déjà commencé.

Multiplier les traductions implicites

Si chaque use case refait son propre mapping, la frontière devient floue et fragile.

La traduction doit être centralisée autant que possible.

Sur-construire trop tôt

Tout n’a pas besoin d’une grosse couche dédiée. Parfois, un adapter bien conçu suffit. Il faut protéger la frontière, pas fabriquer du cérémonial.

Conclusion

Un anti-corruption layer sert à empêcher qu’un modèle externe déforme le modèle interne.

Il crée une frontière de traduction claire, garde le vocabulaire local intact et limite le couplage entre systèmes.

Le réflexe utile à retenir est simple :

quand un système externe parle une autre langue métier, traduis à la frontière au lieu d’importer son modèle chez toi.