DDD et Clean Architecture en Go : Une approche pragmatique sans sur-ingénierie
La dette technique due aux problèmes d'architecture consomme jusqu'à 40 % des budgets informatiques des entreprises. Dans les projets Go, cela se manifeste souvent par des structures trop complexes : 200 fichiers remplis d'interfaces vides et des couches où la logique métier est enfouie. La cause principale est une mauvaise application des patterns DDD et Clean Architecture empruntés à Java ou C#. Mais utilisés de manière pragmatique, ces patterns résolvent les problèmes de complexité. Voici comment éviter la sur-ingénierie et construire un système maintenable.
Pourquoi les projets Go deviennent-ils un labyrinthe de fichiers ?
Les développeurs arrivant à Go depuis d'autres langages transportent souvent des habitudes architecturales qui ne conviennent pas au Go idiomatique. Selon le Go Developer Survey 2024, le principal problème est le maintien de standards de code cohérents en raison de niveaux d'expérience variés et de patterns non idiomatiques. Par exemple, créer des structures de dossiers comme domain, application et infrastructure en imitant Java, sans saisir les principes fondamentaux.
Des recherches de Carnegie Mellon confirment : les problèmes architecturaux sont la source principale de dette technique. En Go, cela ressemble à ceci :
- Un dossier
dtoavec des structs dupliqués. - Des
mappersentre les couches pour des conversions triviales. - Des interfaces dans
domainqui dépendent de l'infrastructure (par ex.,database/sql). - Des cas d'usage qui appellent simplement les repositories sans aucune logique métier.
Le résultat : 200 fichiers, mais aucun ne décrivant les règles métier réelles. Clean Architecture et DDD deviennent non pas la solution, mais le problème lui-même.
DDD : Pas une question de dossiers, mais de langage et de frontières
Domain-Driven Design n'est pas une question d'ensemble de dossiers — c'est une approche pour modéliser le domaine du problème. Concepts clés :
Ubiquitous Language
Les développeurs et les parties prenantes métier doivent utiliser les mêmes termes partout : dans le code, la documentation et les discussions. Si un manager dit « confirmer la commande », le code doit avoir une méthode Confirm(), pas SetStatus(). Cela élimine la confusion et accélère l'onboarding.
Bounded Context
Un grand système est composé de multiples contextes. Par exemple, « customer » dans les ventes (un prospect avec un funnel) et dans le support (tickets) sont des modèles différents. Bounded Context définit des frontières claires où le modèle reste cohérent. Dans les microservices, un service s'aligne typiquement sur un contexte.
Entity et Value Object
- Entity a un ID unique et maintient son identité (par ex.,
Order). Elle contient les règles métier. - Value Object est immuable et défini par ses attributs (par ex.,
Money). Deux objets avec des champs identiques sont équivalents.
Exemple de Value Object en Go :
type Money struct {
Amount int64
Currency string
}
func (m Money) Add(other Money) (Money, error) {
if m.Currency != other.Currency {
return Money{}, errors.New("currency mismatch")
}
return Money{Amount: m.Amount + other.Amount, Currency: m.Currency}, nil
}
Aggregate
Un groupe d'objets traités comme une unité unique. Il a une racine (Aggregate Root) par laquelle passe tout accès. Par exemple, Order (racine) inclut OrderItem. Règle : les changements ne se font que par la racine, assurant la cohérence.
Clean Architecture : La règle de dépendance comme fondation
L'essence de Clean Architecture est que la logique métier ne doit pas dépendre des détails d'implémentation (bases de données, frameworks). La règle unique : les dépendances pointent vers l'intérieur (Dependency Rule).
Comment ça fonctionne en Go
- domain : Contient le modèle métier (Entity, Value Object, interfaces de repository). Aucune importation externe sauf stdlib.
- application : Cas d'usage qui orchestrent le domain. Dépend uniquement du domain.
- infrastructure : Implémentations d'adaptateurs (bases de données, clients HTTP). Dépend du domain via interfaces.
- delivery : Points d'entrée (HTTP, gRPC). Dépend de l'application.
Si domain importe database/sql, l'architecture est cassée. Exemple correct : interface de repository déclarée dans domain, implémentation dans infrastructure/postgres.
Avantages de combiner DDD et Clean Architecture
| Aspect | Effet | Métrique d'amélioration |
|---------------------|------------------------------------|-------------------------|
| Testabilité | Isolation du domain de l'infrastructure | +40 % de couverture |
| Flexibilité | Échange d'adaptateurs en heures | -90 % de temps |
| Compréhensibilité | Frontières de composants claires | -70 % d'onboarding |
Structure de projet réelle : Éviter les 200 fichiers
Structure optimale pour un service Go :
internal/
domain/
order.go # Aggregate Order
order_repo.go # Repository interface
application/
create_order.go # Use case
infrastructure/
postgres/
order_repo.go # Implementation
delivery/
http/
order_handler.go
Principes clés :
- Interfaces de repository dans
domain, pas dansinfrastructure. - Pas de dossiers
dtooumapperssauf si vraiment nécessaire : réutiliser les mêmes structs à travers les couches si cela ne viole pas les frontières. - Logique métier uniquement dans
domain. Par exemple, méthodeCancel()pourOrdervérifie le statut :
func (o *Order) Cancel() error {
if o.status == StatusShipped {
return errors.New("cannot cancel shipped order")
}
o.status = StatusCancelled
return nil
}
Points clés à retenir
- Ubiquitous Language est la fondation du DDD. Si les termes du code ne correspondent pas au jargon métier, le modèle sera erroné.
- Dependency Rule est le cœur de Clean Architecture. Les violations mènent à du code spaghetti.
- Pragmatisme — ne créez pas de couches sans besoin. En Go, on peut souvent éviter
dtosi les structs s'alignent. - Aggregates définissent les frontières de transaction. Changez uniquement par la racine pour la cohérence.
- Testing — les tests du domain s'exécutent sans infrastructure, accélérant le développement.
— Editorial Team
Aucun commentaire pour le moment.