Quantcast
Channel: Publicis Sapient Engineering – Engineering Done Right
Viewing all articles
Browse latest Browse all 1865

Craft – Le Supple Design en DDD

$
0
0

Cet article est le deuxième épisode de la série sur le Domain Driven Design, approche de conception logicielle décrite dans le Blue Book d’Eric Evans écrit en 2003 (Domain-Driven Design: Tackling Complexity in the Heart of Software), puis dans son condensé, le Yellow Book (Domain-Driven Design Reference: Definitions and Pattern Summaries), écrit en 2014.

Utilisant certains termes et concepts décrits dans le premier article de cette série, je vous invite à le lire en premier lieu.

Nous avons précédemment présenté les principes tactiques du DDD, permettant notamment de créer les différentes briques (agrégats, Value Objects…) que nous utiliserons pour créer notre application.
Ce nouvel article présente les principes du Supple Design, principes qui nous permettront d’assembler nos briques précédemment créées et à développer un tout cohérent et pratique à utiliser.

Pour rappel, cet article retranscrit en partie le travail effectué lors d’un atelier organisé pendant un XKE. Le format de cet atelier a été inspiré par celui utilisé lors du Meetup DDD Paris de Novembre 2017. Ainsi, plusieurs groupes ont dû réfléchir sur les différents principes du Supple Design afin d’en expliquer leur compréhension et interprétation au reste des personnes présentes.

Supple Design

Dans tout projet de développement logiciel, il est important que la vélocité d’une équipe de développement augmente au fur et à mesure de l’avancement du projet et que celle-ci travaille de manière efficace. La clé permettant d’atteindre cet objectif repose sur une conception simple à appréhender, offrant des possibilités d’évolutions cohérentes et pertinentes avec le métier, et avec laquelle il est agréable de travailler, autrement dit, une conception souple (ou supple design).
Un développeur a deux rôles :

  • écrire et appeler du code
  • modifier le code existant

Pour répondre efficacement au premier rôle, le développeur doit entremêler le domaine et le code. Ce dernier doit refléter au maximum le domaine, révélant de manière fiable les fonctionnalités et permettant de décrire, juste en le visualisant, les scénarios d’exécution et les résultats que nous devrions obtenir. Le code est donc prédictif et les points d’entrée sont clairs et non ambigus.
Pour qu’un développeur ayant le second rôle soit efficace, le code doit être clair, facile à comprendre et doit explicitement présenter les fonctionnalités métier et le domaine. Ainsi, le développeur va réutiliser ce domaine pré-établi et en suivre les contours et les règles, rendant les conséquences de ses modifications totalement prédictibles.

Nous allons explorer à travers cet article les différents principes proposés dans le Yellow Book d’Eric Evans, afin de réaliser une conception souple pour nos applications.

Intention-Revealing Interfaces

Une interface est un contrat exposant des comportements pouvant être suivis par un programme.
Ces interfaces servent à définir nos actions et nos résultats, fonctionnellement parlant, sans s’occuper de leur implémentation. L’objectif est de connaître le « Quoi » et non le « Comment ». Il faut bien différencier l’aspect fonctionnel de l’aspect technique. L’interface définit ce que notre code fait fonctionnellement, alors que l’implémentation s’occupe des détails techniques.

Plusieurs langages de programmation tels que Java ou C# offrent la possibilité de créer des interfaces qui seront ensuite implémentées par des classes.
Dans ces cas là, les interfaces ont une utilité pouvant être fonctionnelle mais offrent également des fonctionnalités techniques comme utiliser plusieurs implémentations d’une interface ou en changer rapidement. Elles ne sont cependant que la matérialisation de notre concept et nous pouvons donc nous en passer tout en utilisant les interfaces au sens du DDD.

Il arrive régulièrement qu’il soit impossible de savoir ce qu’une classe fait grâce à l’interface, nous sommes obligés d’explorer l’implémentation pour le déterminer.
La notion de « Intention-Revealing » entre alors en jeu. Les interfaces doivent être une description d’intentions précises permettant de deviner très clairement ce que fera son implémentation mais pas comment elle le fera. Elles doivent être suffisamment précises pour que cette implémentation n’apporte aucune information supplémentaire sur l’action réalisée et le résultat obtenu.

L’Ubiquitous Language prend ici toute son importance. Un vocabulaire fonctionnel clair et déterminé en collaboration entre le métier et les développeurs permet de créer des interfaces parlantes. Le développeur doit absolument comprendre quelles actions fonctionnelles seront réalisées par son code. La communication entre le développeur et l’expert métier est primordiale.

Une autre pratique permettant de rendre nos interfaces « Intention-Revealing » est la mise en œuvre du TDD. En effet, les tests par lesquels la production de code est dirigée vont utiliser les interfaces et permettront de rapidement mettre en avant ses défauts. D’autre part, cette pratique encourage à penser le code en termes fonctionnels.

Illustrons tout cela grâce à un exemple parlant : la pâtisserie.

En tant que client dans une pâtisserie, on n’a pas besoin de connaître les détails d’une recette, la quantité de sucre ou d’œufs à ajouter, etc. Nous voulons simplement savoir quelle pâtisserie nous allons acheter.

public Gateau faireUnGateau() //On ne sait pas ce qu'on va obtenir, on peut aussi bien obtenir un gâteau aux fraises qu"au chocolat, ce qui ne conviendrait pas à tous les clients

public Gateau faireUnGateauAuxFraises() //On sait qu'on aura un gâteau aux fraises

public Gateau faireUnGateauAvec45Fraises() //Trop de détails, un simple client n'a pas besoin de les connaître, en plus de rendre la méthode moins évolutive

Il faut toutefois noter que le niveau de détail dépend toujours de notre contexte. Pierre Hermé voudrait sans doute plus d’informations sur notre gâteau que le simple client d’une pâtisserie.

D’un point de vue purement développeur, nous pouvons citer AssertJ qui propose des interfaces abstrayant les objets que nous testons (objets, méthodes, collections…). Peu importent les implémentations des tests d’égalité entre deux String ou entre deux List, nous sommes uniquement intéressés par le fait que l’on compare deux objets de même type.

assertThat(maVoiture).isNotEqualTo(saVoiture); //on compare deux objets Voiture
assertThat(uneString).isEqualTo(autreString); //on compare deux String

//Même interface pour les deux comparaisons


Side-Effect-Free Functions

Une fonction à effet de bord est une fonction modifiant d’autres valeurs que celles retournées et/ou dont le résultat dépend d’autres données que ses paramètres d’entrée.

Pour illustrer ce principe, permettez-moi de citer le tweet suivant :

L’exécution des fonctions à effet de bord peut entraîner une imprédictibilité des résultats obtenus et de l’état du système, obligeant ainsi à explorer les détails de l’implémentation pour comprendre cette exécution.
Cela se traduit par des modifications inattendues du modèle ou des différences de résultats à chaque exécution.

public Gateau faireUnGateauAuxFraises() {
	if(stockDeFraises >= 45) {
		//faire le gâteau
		stockDeFraises -= 45;
		return leGateau;
	} else {
		return faireUnMiniGateauAuxFraises();
	}
}

La méthode faireUnGateauAuxFraises a pour effet de bord de diminuer le stock de fraises total. D’autre part, la création du gâteau dépend de ce stock de fraises. Si nous exécutons plusieurs fois la méthode, le résultat finira par ne pas être le même puisque le gâteau réalisé ne sera qu’un mini gâteau. En plus de modifier l’état du système, le résultat risque d’être inattendu et imprédictible puisque le nom de notre méthode ne laisse aucunement penser qu’elle puisse retourner un mini gâteau. L’interface associée n’est donc pas suffisamment claire.

Pour éviter ce genre de problème, il faut extraire notre logique métier dans des fonctions pures, c’est-à-dire une fonction dont le résultat ne dépendra que des paramètres en entrée et ne modifiant aucune autre valeur que celles fournies en retour.
Une fonction pure est une fonction mathématique sans effet de bord. Par exemple l’addition prend deux nombres en entrée, les additionne et retourne le résultat. Rien n’est modifié.

public Optional<Gateau> faireUnGateauAuxFraises(int stockDeFraises) {
	if(stockDeFraises >= 45) {
		//faire le gâteau
		return Optional.of(leGateau);
	}
	return Optional.empty();
}

public Optional<MiniGateau> faireUnMiniGateauAuxFraises(int stockDeFraises) {
	if(stockDeFraises >= 20) {
		//faire le mini gâteau
		return Optional.of(leMiniGateau);
	}
	return Optional.empty();
}

//c'est ensuite à l'appelant de gérer lui même la valeur de stockDeFraises en fonction des retours des 
//fonctions faireUnGateauAuxFraises() et faireUnMiniGateauAuxFraises()

Les Value Objects sont particulièrement adaptés pour implémenter nos fonctions pures, ces derniers ne devant par définition créer aucun effet de bord lors de leur utilisation.

Il n’est cependant pas toujours possible de supprimer toutes les fonctions avec effets de bord, notamment au niveau des I/O ou encore lors de la génération d’objets contenant une composante aléatoire. Il faut alors séparer ces fonctions dans des unités étanches et les plus petites possibles. Ces unités seront alors des commandes dont le résultat peut dépendre de la modification de l’état de notre système, voire l’engendrer. Ces commandes ne doivent renvoyer aucune information du domaine.

Closure of Operations

De nombreux objets permettent de réaliser des opérations non exprimables par de simples types primitifs, bien qu’approchant de leur comportement. À l’instar de ces types primitifs, ces objets doivent rester simples et pratiques à utiliser.

Si nous prenons comme exemple une addition de deux nombres, il est très facile d’effectuer une simple addition :

int x = y + z

Cependant, si nous choisissons d’additionner deux objets Amount, cet objet ayant une valeur (prenons un entier) et une devise, nous pouvons choisir différentes manières de le coder.

amount.add(number, devise); //modifie l'objet 'amount'

amount.add(otherAmount); //modifie l'objet 'amount'

Amount bigAmount = amount.add(otherAmount);

La dernière solution est équivalente à notre addition d’entiers précédente et est très pratique à utiliser. Nous ne manipulons que des objets ayant un sens métier (des montants) et non plus un mélange d’objets métier et de types primitifs sans réelle signification.

En suivant cette manière de faire, nous utilisons des objets offrant des opérations dites fermées sur le type de l’objet. Une opération fermée offre une interface de haut niveau sans introduction d’une quelconque dépendance ou d’autres concepts. De plus, nous obtenons un objet immuable, plus simple à utiliser grâce à de nombreux avantages techniques (pas de problème d’accès concurrents par exemple) et surtout ne provoquant aucun effet de bord qui, comme nous l’avons vu précédemment, sont à éviter au maximum.

Les opérations fermées apportent de la lisibilité au code, permettant de chaîner les appels de méthodes, réduire le nombre de lignes et pratiquer la programmation fonctionnelle.

Il faut toutefois noter qu’elles concernent essentiellement les Value Object. En effet, comme présenté dans l’article précédent, les VO n’ont pas d’identité propre. Obtenir un nouveau montant en en additionnant deux autres présente donc du sens. Ce ne serait pas le cas avec des entités dont la création et le cycle de vie sont régis par des règles métier strictes, nous empêchant de créer de nouvelles instances pour effectuer de simples opérations.

Il est possible d’utiliser ce principe de manière partielle, en créant des méthodes dont le type retourné est différent mais dont les paramètres correspondent, ou l’inverse. Ces méthodes ne respectent pas totalement le principe mais apportent tout de même une partie des avantages des fonction fermées.

class Amount {
	public Amount convertir(Devise devise){
		//implémentation
	}
}

Assertions

Les fonctions à effet de bord sont souvent présentes dans d’anciens projets, et risquent continuellement d’apparaître dans de nouveaux, provoquant des incohérences dans les objets manipulés.
Il faut donc pouvoir les détecter, que ce soit dans un ancien code, ou en vue des futures évolutions d’un projet.

Les assertions permettent cela en s’assurant de la cohérence de nos objets et agrégats et en contrôlant les I/O.
En effet, celles-ci permettent le contrôle des pré-conditions et des post-conditions pour une opération donnée, ainsi que ses invariants. Elles permettent également de vérifier que notre exécution est bornée dans le domaine, et n’a aucune interaction avec une partie du domaine extérieure à nos opérations.
Nous vérifions ainsi qu’une méthode n’entraîne pas d’effet de bord.

Bien que très peu utilisées, Java offre nativement la possibilité d’utiliser des assertions directement dans notre code, répondant ainsi parfaitement au principe tel que présenté à l’origine.

Prenons l’exemple suivant :

public Score() {
	private int value;

	public Score increment(Score scoreToAdd) {
		assert scoreToAdd.getValue() > 0;
		return new Score(scoreToAdd.getValue() + value);
	}
}

Notre objet Score possède un attribut « value » dont la valeur est obligatoirement positive. Un score ne peut qu’augmenter et jamais diminuer, nous voulons donc vérifier que la valeur fournie en paramètre n’est pas négative. Notre assertion déclenchera ici une erreur dans le cas où la valeur serait négative. Ces erreurs se déclenchent en fonction du paramétrage de la JVM, celui-ci pouvant différer entre les environnements. Il est en effet possible de débrayer les assertions natives.
Nous remarquons qu’en plus de contrôler nos conditions, les assertions présentent des règles métier très explicites pour un futur développeur. Il faut noter que ces assertions doivent être utilisées pour contrôler uniquement les règles métier et l’état du système et non de simples paramètres d’une méthode.

Nous pouvons également citer la bibliothèque Guava dont la classe PreConditions permet de contrôler nos entrées en vérifiant qu’elles respectent un ensemble de règles.

Cependant, malgré ces possibilités d’utilisation des assertions directement dans le code, celles-ci sont très souvent utilisées uniquement dans les tests unitaires. Cela ne les empêche pas pour autant d’être utiles et efficaces, à condition de ne pas uniquement contrôler le cas passant mais de tester également des exécutions non attendues.

Standalone Classes

Il vous est sûrement déjà arrivé de devoir jongler avec plusieurs fichiers pour comprendre le comportement d’une seule classe, -celle-ci dépendant d’autres. Cela ne pose en général pas de problème, mais peut devenir rapidement compliqué lorsque des comportements sont répartis à travers plusieurs classes liées par de multiples niveaux d’abstraction. On finit par ne plus retrouver dans quelles classes sont réellement implémentées les différentes parties de notre comportement.

Cela révèle de fortes dépendances entre ces classes, entraînant un problème de lisibilité, d’autant plus important que le nombre de fichiers est grand.
Autre problème, la modification du code à un endroit peut entraîner une réaction en chaîne de modifications lourdes à effectuer.

Il faut alors diminuer ces dépendances et revoir le découpage du programme, ce dernier comportant des liaisons superflues. Par exemple, nous pouvons considérer une entité « commande » et une entité « client » dans un projet utilisant Hibernate. Ce framework pourrait nous encourager à créer une liaison entre ces deux classes, les rendant chacune dépendante de l’autre. Cette liaison est purement technique et n’a pas forcément d’intérêt métier, il faut donc la supprimer.

De manière générale, Il faut s’assurer du besoin métier derrière le découpage des classes et des liaisons entre elles. S’il est inexistant, il faudra soit supprimer ces liaisons, soit regrouper les classes entre elles.

Citons Vavr qui a implémenté tout ce qui concerne les List (interfaces, factory, implémentations) dans un seul fichier List.java.

Il ne faut cependant pas supprimer nos dépendances à l’excès. Chaque concept métier indépendant doit être dans son propre fichier.

Nous en concluons que le découpage de notre application doit être fait en fonction du domaine et du contexte métier. Nous réduirons ainsi les dépendances inutiles et ayant un intérêt purement technique, augmentant ainsi la lisibilité du code.

Nous pouvons faire le parallèle avec le « Single Responsibility Principle », principe formalisé la même année que la sortie du Blue Book par Robert C. Martin et très répandu de nos jours.
Ce principe de responsabilité unique comporte deux notions :

  • une classe comporte une seule responsabilité
  • une responsabilité est concentrée dans une seule classe

Prenons l’exemple donné par Robert C. Martin pour l’illustrer.

public interface Modem {
      public void Dial(String pno);
      public void Hangup();
      public void Send(char c);
      public char Recv();
}

Nous remarquons des méthodes liées à la gestion de la connexion et d’autres à la celle des messages. Nous pouvons donc distinguer deux responsabilités différentes. Si des futures évolutions ne portent que sur l’une des deux, il faut alors les séparer dans deux classes différentes.

Ce principe est plus complet que le pattern Standalone Classes. En effet, ce dernier se concentre plutôt sur la partie regroupement des responsabilités tandis que le Single Responsibility Principle traite à la fois de la séparation et du regroupement, mettant l’accent sur le découpage adéquat et sur le juste milieu.

Declarative Design

La conception déclarative offre une voie pour spécifier un programme ou une partie. C’est une sorte de spécification fonctionnelle du programme mais exécutable.
L’objectif est de produire du code lisible par n’importe quelle personne experte dans le domaine métier, de la même manière qu’il lirait les spécifications de l’application. L’accent est ainsi mis sur ce que le code produit, voire pourquoi il le produit, mais nous masquons comment il le fait, autrement dit, le code nous révèle le « Quoi » mais pas le « Comment ».

Le principe de la conception déclarative a pour but d’indiquer un chemin à suivre pour écrire un programme. Il est alors possible d’imaginer de nombreuses solutions permettant d’appliquer ce principe et ainsi orienter la manière de produire le code. Elles risquent cependant d’être limitées, n’offrant pas une rigueur formelle de programmation.
En outre, certaines de ces solutions seront corruptibles dans le sens où un développeur pourra outrepasser ses directives, volontairement ou non. Cela arrive essentiellement si la solution utilisée est trop compliquée ou trop restrictive.

C’est pour éviter ces solutions décevantes que le DDD nous présente tous les principes décrits jusqu’à présent. Leur combinaison permet de mettre en place une conception déclarative efficace révélant tous ses bénéfices.
Les deux derniers principes du Supple Design permettront d’ailleurs d’aller encore plus loin dans l’aboutissement de notre conception déclarative.

Un bel exemple de conception déclarative est les DSL (Domain Specific Language) ou langages dédiés. Les DSL sont des langages de programmation répondant aux contraintes d’un domaine métier particulier. Ainsi, il utilise les concepts, fonctionnalités et idiomes du domaine. Ces langages correspondent parfaitement à une conception déclarative puisqu’il est impossible de les utiliser sans une certaine connaissance du domaine, et un expert du domaine, bien que non développeur, comprendra ce que fait le code produit.

Un exemple bien connu de DSL est le langage HTML, dédié à l’écriture de pages WEB. En effet, les balises HTML et tous les éléments de syntaxe utilisent un langage fortement lié au domaine d’une page WEB (header, body, ul…) et obligent le développeur à s’immerger totalement dans ce domaine.

Drawing on Established Formalisms

Dans de nombreux cas, lors de la définition des concepts du domaine, notamment lors de la définition de l’Ubiquitous Language et des entités, Value Objects, etc, nous avons tendance à vouloir partir de zéro. Cela prend énormément de temps et aboutit rarement à un résultat concluant.
A l’instar des frameworks que nous utilisons dans nos applications sans les recréer à chaque fois, il est généralement préférable d’utiliser ce qui a déjà été formalisé sur notre domaine métier.
Dans de nombreux cas, une partie du domaine que nous implémentons dépend de concepts existants depuis des décennies voire des siècles, avec un vocabulaire et des règles déjà bien définis. Ce savoir exposé dans des livres, sur Internet, dans des bibliothèques, peut être normalisé par des organismes.
Nous obtenons ainsi un socle à réutiliser pour notre domaine, dont l’Ubiquitous Language et nos entités découlent naturellement.

Nous pouvons résumer ce principe par l’adage suivant : « Il ne faut pas réinventer la roue ».

Il arrivera par moment que le domaine défini entre en conflit avec le nôtre, certains aspects étant plus ou moins différents. Il ne faut cependant pas pour autant hésiter à utiliser l’existant et l’adapter, celui-ci ne servant que de socle sur lequel construire.

Illustrons tout cela par un exemple courant : les mathématiques.
De la plus simple arithmétique aux opérations les plus complexes, les mathématiques sont utilisées dans énormément de domaines. Or, elles existent depuis des siècles. Les règles, concepts, terminologies sont clairement définis et exposés pour que personne n’en dévie. De plus, de nombreuses bibliothèques les implémentent dans tous les langages de programmation, permettant de profiter à la fois d’un domaine clairement défini, et d’un code déjà utilisable. Elles réutilisent des termes et concepts déjà définis dans les mathématiques et n’en imposent pas de nouveaux.

Conceptual Contours

De la même manière que le Single Responsibility Principle décrit le découpage d’une application d’un point de vue plutôt bas niveau (découpage des classes), Conceptuel Contours en décrit le découpage d’un point de vue haut niveau, à l’échelle de l’application ou d’un service.

Une difficulté lors de la conception d’une application est le découpage de nos fonctionnalités.
En effet, une application peut être très peu découpée, avec quelques gros blocs monolithiques contenant énormément de concepts qui n’ont pas de liens entre eux. Ces blocs sont de vrais sacs de nœuds très complexes et impossibles à lire et à maintenir.

À l’inverse, l’application peut être découpée beaucoup trop finement, entraînant un modèle, bien qu’évolutif, très complexe et avec beaucoup de dépendances. Chaque concept nécessite alors de comprendre comment fonctionnent plusieurs sous-parties, créant une grande difficulté dans la compréhension de l’application et provoquant la perte d’information. D’autre part, ce découpage trop fin entraîne l’existence d’une multitude d’opérations inutiles dans notre contexte fonctionnel mais masquant les fonctionnalités importantes qui sont alors noyées dans la masse.
Par exemple, dans l’architecture en couche avec des DAO, nous offrons beaucoup de possibilités pour chaque entité, certaines indispensables à notre logique métier, et beaucoup inutiles et jamais utilisées.

Il faut donc chercher le bon niveau de détail dans notre découpage fonctionnel et trouver le juste équilibre entre l’encapsulation de la complexité, la flexibilité et l’adaptabilité. Il ne sera jamais parfait et pourra évoluer dans le futur.
Pour arriver à ce résultat, il faut avant tout chercher à rester cohérent avec le domaine en suivant sa propre granularité, ceci en écoutant notre intuition concernant le domaine, elle-même découlant de notre expérience.
D’autre part, il faut laisser les contours de l’application émerger d’eux-mêmes et ne pas hésiter à modifier le découpage tout au long du développement de notre application. En effet, le premier découpage ne sera pas le bon, il sera même naïf. Il devra donc évoluer au fur et à mesure de la compréhension et de l’évolution de notre contexte fonctionnel. Il faut le remettre en question continuellement, ne pas hésiter à rendre nos fonctionnalités plus verbeuses si cela les rend plus compréhensibles et lisibles, et à l’inverse supprimer le superflu s’il n’a pas lieu d’être.

Il faut retenir que pour appliquer ce principe, les contours de notre application ne doivent surtout pas être figés, notamment au début des développements, ce qui peut aller à l’encontre des décisions prises par l’entreprise qui peut imposer un découpage standardisé sans chercher à aller plus loin.

Prenons le domaine automobile avec une classe Vehicule. Nous pouvons la découper de plusieurs manières différentes :

class Vehicule {
	long vitesseMax;
	long accelerationMax;
	long tempsDeFreinage;
	Couleur couleur;
}
class Vehicule {
	Frein frein;
	Moteur moteur;
	Couleur couleur;

	class Frein {
		long tempsDeFreinage;
	}

	class Moteur {
		long vitesseMax;
		long accelerationMax
	}
}
class Vehicule {
	Frein frein;
	Moteur moteur;
	Couleur couleur;

	class Frein {
		long tempsDeFreinage;
	}

	class Moteur {
		long puissance;
		Couple couple;
	}
}

Tous ces découpages, qui utilisent par ailleurs le principe de Standalone Classes, sont corrects et dépendent uniquement du contexte de l’application. Il est d’ailleurs fort probable que l’équipe de développement commence par créer la classe Vehicule avec le premier découpage, puis, au fur et à mesure de l’avancement du projet, se rende compte que ce n’est pas assez précis et modifie la classe avec le découpage numéro 2 puis numéro 3.
Il est également possible que, voulant détailler au maximum, l’équipe découpe la classe Véhicule directement avec l’exemple numéro 3, puis, remarquant que la plupart des informations sont inutiles, la redécoupe comme dans le premier exemple.

Nous pouvons ainsi remarquer que le découpage doit évoluer au fur et à mesure de la compréhension et de l’évolution du domaine et contexte métier. Il ne faut surtout pas hésiter à le modifier.
L’utilisation du principe de Standalone Classes dans notre exemple, comme nous l’avons présenté dans son chapitre dédié, est également sujette à adaptation en fonction de notre contexte fonctionnel.

Conclusion

Les principes du Supple Design forment un tout extrêmement cohérent et s’entremêlent les uns les autres, rendant leurs différences parfois subtiles. On remarque que leur objectif commun est de produire un code très lisible, prédictible, se concentrant sur ce que fait l’application et non comment elle le fait. Ce code est très entremêlé avec le domaine métier, rendant sa compréhension possible même par un expert métier profane des langages informatiques.

On peut remarquer que certains principes du Supple Design se rapprochent des principes tactiques du DDD présentés dans l’article précédent, puisque leur première utilisation sera au niveau même du code.

  • Side-Effect free functions
  • Closure of Operations
  • Standalone Classes
  • Assertions

Les autres principes correspondent plutôt à une philosophie de code à adopter pour créer nos applications et s’orientent plus vers le domaine métier. Ils sont plus proches des principes stratégiques du DDD qui font l’objet du troisième et dernier article de cette série.

  • Draw on Established Formalisms
  • Intention Revealing Interfaces
  • Declarative Design
  • Conceptual Contours

Certains de ces principes (Side-Effect free functions par exemple) s’utilisent assez naturellement après la mise en œuvre des principes tactiques et font d’ailleurs partie des bonnes pratiques de développement d’applications logicielles, notamment avec le mouvement Software Craftsmanship. On peut d’ailleurs les retrouver dans certaines techniques de développement bien établies telles que le TDD.

D’autres (Declarative Design par exemple) sont plus compliqués à mettre en œuvre de par leur complexité et interprétation. Ils nécessitent alors plus de pratique pour pouvoir les utiliser efficacement. Ils restent cependant très utilisés et ont amené à des solutions logicielles reconnues, notamment les DSL, particulièrement robustes et expressifs, qui sont l’exemple parfait de l’application du Declarative Design et des Intention Revealing Interfaces.

Pour finir, il ne faut pas oublier que ces principes ont été présentés il y a maintenant un certain temps et qu’ils ont eu le temps de gagner en maturité et d’être exploités, voire améliorés, dans de nombreuses applications, offrant ainsi une grande quantité d’exemples sur lesquels s’appuyer. Il ne faut donc pas hésiter à se lancer dans le DDD et le Supple Design.

Je vous invite également à lire le dernier épisode de cette série qui présente les principes Stratégiques du DDD.


Viewing all articles
Browse latest Browse all 1865

Trending Articles