Pourquoi DDD et Kotlin forment un duo idéal
Lorsqu'un projet s'agrandit et que les exigences métier commencent à ressembler à un écheveau embrouillé, l'approche CRUD classique cesse de fonctionner. Vous vous surprenez à penser que la moitié du code n'est qu'une série interminable de vérifications if-else, et que toute modification de la logique entraîne la correction d'une dizaine de fichiers. Cela vous parle ?
C'est là qu'intervient le Domain-Driven Design (DDD) — une approche qui place la logique métier au centre de l'univers. Et Kotlin, avec sa syntaxe concise, son système de types puissant et son support des concepts fonctionnels, devient l'outil idéal pour implémenter le DDD. Dans cet article, nous allons voir comment combiner ces deux forces pour que votre code devienne non seulement fonctionnel, mais expressif et flexible.
Si vous codez en Kotlin et souhaitez que votre architecture respire plutôt que de grincer — vous êtes au bon endroit. Aujourd'hui, nous ne nous contenterons pas de parler théorie, nous écrirons des exemples de code concrets.
Domain-Driven Design : une brève introduction pour les pressés
Avant de plonger dans le code, mettons-nous d'accord sur les termes. Le DDD n'est ni un framework ni une bibliothèque. C'est un ensemble de principes qui aident les développeurs et les métiers à parler le même langage.
Concepts clés :
- Ubiquitous Language (Langage omniprésent) — les termes du code doivent correspondre à ceux utilisés par les analystes métier et les clients.
- Bounded Context (Contexte délimité) — une frontière claire à l'intérieur de laquelle le modèle a un sens. Par exemple, « Client » dans le contexte des ventes et « Client » dans le contexte du support sont des entités différentes.
- Entity (Entité) — un objet avec un identifiant unique qui évolue dans le temps.
- Value Object (Objet-valeur) — un objet immuable défini par ses attributs, et non par un identifiant.
- Aggregate (Agrégat) — un groupe d'objets liés considéré comme un tout cohérent.
- Repository (Référentiel) — une abstraction pour l'accès aux données, masquant les détails de stockage.
Cela semble abstrait ? Traduisons-le maintenant en Kotlin.
Value Objects en Kotlin : grâce et sécurité
Commençons par le plus agréable — les Value Objects (VO). En Kotlin, leur implémentation est un vrai plaisir grâce à data class.
Imaginez que nous travaillons dans un système de réservation d'hôtels. Nous avons le concept de « Période de séjour ». Ce n'est pas une entité — c'est simplement un ensemble de dates. Un candidat idéal pour un VO.
data class StayPeriod( val checkIn: LocalDate, val checkOut: LocalDate) { init { require(checkIn.isBefore(checkOut)) { "La date d'arrivée doit être antérieure à la date de départ" } }
val totalNights: Int get() = ChronoUnit.DAYS.between(checkIn, checkOut).toInt()
fun overlapsWith(other: StayPeriod): Boolean { return !(this.checkOut.isBefore(other.checkIn) || this.checkIn.isAfter(other.checkOut)) }}Pourquoi est-ce génial ?
- Immutabilité — tous les champs sont
val. Personne ne modifiera accidentellement la date d'arrivée. - Validation intégrée — le bloc
initgarantit que l'objet n'est créé que dans un état valide. - Comportement — la méthode
overlapsWithencapsule une règle métier. Vous ne cherchez pas le chevauchement des dates dans les services, vous interrogez directement l'objet.
En DDD, nous appelons cela un modèle riche — l'objet ne se contente pas de stocker des données, il sait effectuer des opérations métier.
Entity : quand l'identité compte
Passons maintenant aux Entity. Contrairement aux VO, une entité change et possède une identité. En Kotlin, nous utilisons une classe ordinaire, mais en mettant l'accent sur l'identifiant.
Exemple — une réservation d'hôtel (Booking) :
class Booking( val id: BookingId, val roomNumber: Ro