Domain-Driven design en continu

Au-delà de l'Event-Storming

C'est quoi le Domain-Driven Design ?

C'est quoi le Domain-Driven Design ?

Favoriser le partage de connaissance métier entre les experts et les tech

C'est quoi le Domain-Driven Design ?

Le langage ubiqutaire
Le jargon commun entre les experts et les tech

C'est quoi le Domain-Driven Design ?

Langage valide au sein d'un context précis

C'est quoi L'Event Storming ?

It’s not stakeholder knowledge but developers' ignorance that gets deployed into production

— Alberto Brandolini

C'est quoi L'Event Storming ?

C'est quoi L'Event Storming ?

Loïc Broquet

lolo101

Once upon a time

Green field

Event Storming

Premiers jours

Mais après quelques mois

Mais après quelques mois

Mais après quelques mois

On a perdu l'expertise métier

Et on a des bugs

29%

31%

Rendre le code défensif

Sonar

 

- Test coverage

- Code smells

- Bogus code

- Security vulnerabilities

JetBrains Qodana

 

- Code smells

- Bogus code

- IDE integration

Rendre le code défensif

CodeScene

 

- Hot spots

- Code health

- Teams topology

Rendre le code défensif

Renforcer la compréhension du domaine métier

Quels outils ?

Renforcer la compréhension du domaine métier
Mais

J'aimerais un indicateur qui m'aide à remplacer
Ce code

public double calculatePrice(Collection<Item> items, int discount, int vat) {
  double price = 0;
  for (Item item : items) {
    price += item.price;
  }
  double discountedPrice = price * ( 1 - discount / 100.0);
  double netPrice = discounterPrice * (1 + vat / 100.0);
  return netPrice;
}

💀 Item.price <  0 ?

NaN ?

discount < 0 ? > 100 ?

On a inversé discount et vat ?

Par ce code

public record Amount(BigDecimal value) {
  public Amount {
    if (value.compareTo(BigDecimal.ZERO) < 0) {
      throw new NegativeAmount(value);
    }
  }
}

public record Price(Amount amount, Currency currency) {}

public Price calculatePrice(Basket basket, Discount discount, Vat vat) {
  Price rawPrice = basket.totalPrice();
  Price discountedPrice discount.appliedTo(rawPrice);
  Price netPrice = vat.appliedTo(discountedPrice);
  return netPrice;
}

Le nuage c'est bien mais

pour quantifier,
la liste des mots c'est mieux !

# list java files in "service" or "domain" packages, excluding tests
find "../$PROJECT" -type f -not -path "*/test/*" \( -path "*/service/*" -o -path "*/domain/*" \) -name "*.java"
# concatenate files contents
| xargs sed
# remove comment blocs
-e '/\/\*/,/\*\//d'
# remove comment lines
-e 's/\/\/.*$//g'
# remove imports section
-e '/^import /d'
# remove package declaration
-e '/^package /d'
# replace non-identifiers symbols with linefeeds
-e 's/[^A-Za-z0-9_]/\n/g'
# remove empty lines
| grep .
# convert to lowercase
| tr '[:upper:]' '[:lower:]'
# sort alphabetically
| sort
# distinct and count
| uniq -c
# sort by number of occurrences
| sort -nr

~50 ms / 30 kloc !

Introducing indddy v1

Mesurer Le lexique ubiquitaire Dans le code

Définir le lexique

Mesurer Le lexique ubiquitaire Dans le code

Catégoriser les mots

Mesurer Le lexique ubiquitaire Dans le code

Calculer un score

Score = \frac{nombre\ de\ mots\ du\ lexique}{nombre\ total\ de\ mots\ dans\ le\ code}

Attention le langage nous impose des keywords
(class, public, return, etc.)
Il faut choisir soigneusement ceux qu'on ignore

Un "bon" score est 50% ou plus

Tech Lead

Tech Lead

On a un score de 30%
mais on ne sait pas comment l'améliorer

L'indicateur DDD, on ne le suit plus parce qu'on ne tirait pas d'action

Event Storming ?

C'est quoi ?

Tech Lead

Difficile d'emporter l'adhesion

Effort récurrent
Pas automatisable

Difficile d'emporter l'adhesion

Effort récurrent
Pas automatisable

On ne maintient plus
le langage ubiquitaire

=> Supporter de nouveaux langages
=> Maintenabilité de l'indicateur

=> L'indicateur devient entièrement automatisable

Trade-off
Time

Mesure de la
primitive obsession

Abandon du script ultra rapide

On se base sur un AST JavaScript

Introducing Indddy v2

~600 ms / 30 kloc

Score = \frac{nombre\ de\ types\ primitifs}{nombre\ total\ de\ types\ dans\ le\ code}

Mesurer La primitive obsession

L'indicateur est beaucoup moins bruité puisqu'il ne sélectionne que les types

Un "bon" score est 20% ou moins

Introducing Indddy v2

Mine de rien c'est déjà une innovation !

Conclusion

Conclusion

Conclusion

# list java files in "service" or "domain" packages, excluding tests
find "../$PROJECT" -type f -not -path "*/test/*" \( -path "*/service/*" -o -path "*/domain/*" \) -name "*.java"
# concatenate files contents
| xargs sed
# remove comment blocs
-e '/\/\*/,/\*\//d'
# remove comment lines
-e 's/\/\/.*$//g'
# remove imports section
-e '/^import /d'
# remove package declaration
-e '/^package /d'
# replace non-identifiers symbols with linefeeds
-e 's/[^A-Za-z0-9_]/\n/g'
# remove empty lines
| grep .
# convert to lowercase
| tr '[:upper:]' '[:lower:]'
# sort alphabetically
| sort
# distinct and count
| uniq -c
# sort by number of occurrences
| sort -nr

Conclusion

Vision

Un indicateur de primitive obsession

Support de Kotlin, Typescript

Inférence de langage ubiquitaire avec LLM + RAG
(Retrieval-Augmented Generation)

Inclus dans la pipeline d'intégration continue (CI)

"Domain-driven Sonar"

Ressources

https://github.com/lolo101/indddy

Votre avis compte !

https://openfeedback.io/dd2024/2024-11-07/639461