← Retour à l'accueil
14 min de lecturev1.0

ARC-DDD : Une approche Domain-Driven Design née en Australie

Arda Akgür

DDDARC-DDDARC DDDAustralian DDDDomain-Driven DesignBounded ContextSoftware Architecture

La plupart des idées en architecture logicielle n'apparaissent pas entièrement formées à partir de rien.

Un principe qu'un ingénieur apprend prend une autre forme lorsqu'il rencontre un autre problème. Parfois une petite idée grandit pendant des années — appliquée, brisée, redesignée — et finit par devenir sa propre approche architecturale.

C'est ainsi qu'a commencé l'histoire d'ARC-DDD.

J'ai commencé à développer les premières versions de cette approche pendant que je travaillais comme Associate Engineer en Australie.

Mais lorsque j'explique d'où vient l'idée, je dois particulièrement créditer une personne :

Victorian Software CEO Adrian Budiman.

Adrian a été la première personne à me montrer l'idée de diviser un système en domaines séparés horizontalement, au lieu de penser les domaines uniquement comme des couches architecturales classiques.

Cette idée est devenue mon point de départ.

J'ai ensuite poussé cette approche beaucoup plus loin dans des systèmes que j'ai construits moi-même.

Aujourd'hui ARC-DDD, ou comme je l'appelle parfois Australian DDD, est le résultat de ce processus.

Quel est le problème du DDD classique ?

Le Domain-Driven Design classique divise généralement un système en couches verticales :

  • Presentation
  • Application
  • Domain
  • Infrastructure

Cette approche est extrêmement précieuse, et ARC-DDD n'essaie pas de l'éliminer.

Ce que j'ai ajouté, c'est un second axe.

Tandis que le système est découpé en couches verticales, le modèle de domaine lui-même est aussi découpé en bounded contexts horizontaux.

Par exemple :

Plain Text
Core├── Users├── Events├── Transactions├── Vendors└── Payments

La règle fondamentale ici est la suivante :

Un domaine ne devrait pas connaître le monde entier d'un autre domaine.

C'est là que le vrai caractère d'ARC-DDD apparaît.

Un User n'est pas le même User pour chaque domaine

L'un des problèmes les plus courants que je vois dans les grands systèmes, ce sont les modèles d'entités énormes.

Par exemple, il y a une seule classe User dans le système.

Avec le temps, tout y est ajouté :

Plain Text
PasswordPhoneAddressBalanceIBANKYCPermissionsProfileSecurityTransactionsPreferences...

Puis le domaine Events commence à utiliser le modèle User entier alors qu'il veut seulement afficher le nom et la photo de l'utilisateur.

Le domaine Transactions utilise le même modèle pour le solde et l'IBAN.

Le système Vendor utilise encore le même modèle.

Au final, un système qui semble séparé par domaines en théorie devient, en pratique, une structure géante liée par les mêmes entités.

ARC-DDD adopte ici une approche différente.

Par exemple :

Plain Text
Users.UserEvents.User2Transactions.User3

Ce sont des modèles de domaine différents.

Mais ils peuvent représenter la même table physique de base de données.

Plain Text
Users.User          ──┐Events.User2        ──┼──> Users tableTransactions.User3 ──┘

Si le domaine Events n'a besoin que de :

Plain Text
UserIdFirstNameFamilyNamePhoto

User2 ne contient que cela.

Si le domaine Transactions a besoin des champs :

Plain Text
UserIdBalanceIBANVerified

alors User3 est défini selon les besoins de son propre domaine.

Chaque domaine ne voit le monde que dans la mesure où il en a besoin.

C'est exactement l'approche centrale d'ARC-DDD : les bounded contexts sont séparés, et au lieu de transporter l'agrégat complet d'un autre domaine, vous créez une projection locale au domaine qui contient les champs dont vous avez besoin. Si nécessaire, cette projection est mappée sur la même table physique.

Même base de données, réalités de domaine différentes

Une caractéristique importante d'ARC-DDD est qu'il ne traite pas les bases de données séparées comme obligatoires pour créer des bounded contexts.

Par exemple, avec Entity Framework Core :

C#
modelBuilder    .Entity<User2>()    .ToTable("Users")    .HasKey(x => x.UserId);

User2 dans le domaine Events peut toujours se lier à la table physique Users.

Mais comme le modèle ne contient que les propriétés dont le domaine Events a besoin, ce domaine n'entraîne pas de champs utilisateur inutiles dans son propre modèle.

Dans l'implémentation de référence publique (eski-dev), l'exemple Goldtag montre User, User2 et User3 liés à la même table Users depuis différentes perspectives de domaine. User2 représente les données et le comportement dont Events a besoin ; User3 représente ce dont Transactions a besoin.

Cela nous donne un équilibre important :

La base de données peut être partagée physiquement, mais les modèles de domaine n'ont pas à l'être.

C'est l'un des principes fondamentaux d'ARC-DDD.

Chaque domaine possède son propre monde de persistance

Cette séparation ne s'arrête pas au niveau des dossiers.

Chaque bounded context a sa propre structure DbContext.

Par exemple :

Plain Text
UserDbContextEventsDbContextTransactionDbContextVendorsDbContext

Le domaine Events connaît ses propres modèles de projection.

Le domaine Transactions connaît ses propres modèles.

Le domaine Users possède le modèle User canonique.

Dans l'implémentation de référence publique (eski-dev), par exemple, UserDbContext utilise le modèle User canonique, EventsDbContext utilise la projection User2, et TransactionDbContext utilise la projection User3 dans son propre contexte.

Pour cette raison, un domaine n'importe pas directement les modèles Core d'un autre domaine.

À l'intérieur d'Events :

C#
using Gold.Core.Users;

au lieu de tirer tout l'agrégat User dans le système, Events crée sa propre projection.

C'est une duplication délibérée.

Parce qu'en ARC-DDD, parfois :

Un peu de duplication coûte moins cher qu'un couplage fort.

L'une des règles centrales au niveau Core est que les dossiers de domaine ne s'importent pas directement les uns les autres. Par exemple, au lieu d'utiliser Gold.Core.Users, Events crée sa propre représentation locale telle que User2.

Où les domaines se rencontrent-ils ?

Dans les applications réelles, les bounded contexts ne peuvent pas vivre en isolement complet.

Une seule opération peut avoir besoin de plusieurs domaines à la fois, comme :

  • Ordering
  • Payments
  • Users

ARC-DDD ne connecte pas ici les couches de domaine entre elles. À la place, il déplace la composition vers le bord extérieur du système.

Par exemple :

Plain Text
API | +--> OrderingRepository | +--> PaymentsRepository | +--> UsersRepository

Autrement dit, l'orchestration inter-domaines se produit au niveau API ou application.

Les domaines Core ne s'importent pas mutuellement et ne créent pas de chaîne de dépendances.

Dans eski-dev aussi, la composition inter-domaines se fait côté API. Un controller ou un application service peut travailler avec plusieurs repositories si besoin, mais les domaines Core ne sont pas câblés les uns aux autres.

Cette séparation devient très précieuse à mesure que le système grandit.

Trois projets centraux

Si je créais aujourd'hui un nouveau système ARC-DDD, je garderais la structure de base assez simple :

Plain Text
Product.CoreProduct.DomainProduct.Api

Direction des dépendances :

Plain Text
Product.ApiProduct.DomainProduct.Core

Core contient des modèles de domaine purs.

Il n'a aucune dépendance à Entity Framework ni à l'infrastructure.

Domain contient la couche de persistance, les DbContexts et les structures de repository.

Api agit comme composition root et orchestre différents bounded contexts dans le même cas d'usage lorsque c'est nécessaire.

La direction des dépendances dans la structure de référence actuelle d'eski-dev est aussi Gold.Api → Gold.Domain → Gold.Core. Garder la couche Core libre de packages d'infrastructure est l'une des règles fondamentales.

Une définition d'ARC-DDD en une phrase

Si je devais décrire cette architecture en une seule phrase aujourd'hui :

ARC-DDD est une approche d'architecture logicielle qui étend le DDD classique en couches avec des bounded contexts horizontaux et des projections d'entités spécifiques au domaine sur des tables partagées.

Je le définis de façon plus technique ainsi :

DDD classique en couches + dossiers de bounded context horizontaux dans Core + projections d'entités par contexte sur des tables partagées.

Mais pour moi, l'idée la plus importante derrière cette approche est celle-ci :

Un domaine ne devrait pas voir toute la connaissance qui existe dans le système — seulement le monde dont il a besoin pour faire son propre travail.

Pourquoi « Australian DDD » ?

Parce que pour moi, l'origine de l'approche remonte à l'Australie.

L'idée de séparation horizontale des domaines que j'ai apprise d'Adrian Budiman en tant qu'Associate Engineer a été le point de départ.

J'ai ensuite élargi cette idée en l'appliquant dans mes propres systèmes — avec des modèles d'entités locaux au domaine, des projections sur tables partagées, des DbContexts par domaine, et des principes de composition inter-domaines contrôlée.

Donc lorsque je parle d'ARC-DDD, je ne pense pas qu'il soit juste de le présenter comme « j'ai tout inventé de zéro ».

L'ingénierie ne progresse généralement pas ainsi de toute façon.

Quelqu'un vous donne une idée forte.

Vous l'appliquez à d'autres problèmes.

Vous voyez ce qui manque.

Vous ajoutez de nouveaux principes par-dessus.

Et des années plus tard, un système assez différent de l'idée d'origine émerge.

Pour moi, ARC-DDD s'est développé exactement ainsi.

La pensée horizontale des domaines qu'Adrian m'a montrée était l'étincelle.

Ma contribution a été de transformer cette étincelle en un modèle architectural pratique que j'appelle aujourd'hui ARC-DDD.

Implémentation de référence publique

ARC-DDD n'est pas seulement une approche architecturale que je décris en théorie.

Pour quiconque veut voir comment l'approche est appliquée dans un vrai système, j'ai publié publiquement l'une de mes anciennes codebases de développement :

GitHub:
https://github.com/Arcnaboo/eski-dev

Le dépôt est public, basé sur C#, et publié sous licence MIT.

Ce dépôt est l'implémentation de référence publique Goldtag legacy backend d'ARC-DDD.

Le dépôt définit explicitement l'implémentation de référence comme :

Plain Text
Gold.CoreGold.DomainGold.Api

Dans le dépôt, je recommande particulièrement de regarder ces modèles :

Plain Text
Gold.Core/Users/User.csGold.Core/Events/User2.csGold.Core/Transactions/User3.cs

Ils montrent directement l'idée la plus importante d'ARC-DDD.

Le User canonique appartient au bounded context Users.

Events utilise un modèle User2 réduit, façonné pour ses propres besoins concernant le même utilisateur.

Transactions utilise un modèle User3 adapté à ses propres opérations.

Vous pouvez voir la même approche dans des modèles comme Event et Event2, Notification et Notification2. Regardez surtout ces fichiers dans le dépôt pour apprendre l'implémentation de référence.

Une distinction importante doit être faite ici.

Ce dépôt porte les traces d'une ancienne codebase de production réelle.

Je n'utiliserais donc pas chaque détail d'implémentation tel quel dans un système que j'écrirais aujourd'hui à partir de zéro.

Par exemple, d'anciennes approches de connection strings, certaines structures de controller, un usage incomplet de l'injection de dépendances, et des morceaux legacy devenus inutiles ne sont pas ARC-DDD en soi.

Je souligne particulièrement cette différence :

Prenez les idées, modernisez la mécanique.

L'idée durable :

Bounded contexts horizontaux + projections par domaine + DbContexts par domaine.

Pas les détails historiques d'implémentation de l'ancien code.

Pourquoi est-ce que je partage cela publiquement ?

Je ne pense pas qu'il suffise d'expliquer une approche architecturale uniquement avec des diagrammes.

Les gens ont besoin de pouvoir :

  • ouvrir du vrai code,
  • comparer des entités,
  • voir les mappings DbContext,
  • inspecter les frontières des repositories,
  • critiquer l'approche

C'est pourquoi je veux que la documentation architecturale et une vraie implémentation de référence publique pour ARC-DDD restent accessibles.

Dépôt :

https://github.com/Arcnaboo/eski-dev

Mais je ne veux pas que vous copiiez chaque ligne du dépôt pour comprendre ARC-DDD.

Bien au contraire.

Séparez les parties qui viennent de l'âge du code.

Regardez les frontières de domaine.

Regardez comment la même entité physique est modélisée différemment selon les bounded contexts.

Regardez comment les contexts sont séparés.

Regardez pourquoi Core n'importe pas les modèles des autres domaines.

Parce que l'essence d'ARC-DDD n'est ni un framework ni une technologie spécifique.

Entity Framework peut changer.

.NET peut changer.

La base de données peut changer.

Le pattern repository peut changer.

Dans des années, la plupart des outils que nous utilisons aujourd'hui auront peut-être changé aussi.

Mais le principe fondamental ne change pas :

Faites des frontières de domaine de vraies frontières dans le code.

Et ne montrez à chaque domaine que le monde dont il a besoin.