@julientopcu.com
@josianchevalier.bsky.social
{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"selectionComplete" : true,
"totalPrice" : "230€",
"paymentMean": {
"type": "CREDIT_CARD",
"number": "***********"
},
"travelInsurance" : "40€",
"ships": [
{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "AISLE", "WINDOW", "PANORAMIC_VIEW", "LOUNGE" ],
"deckOptions": [ "ANY", "UPPER_DECK", "MIDDLE_DECK", "LOWER_DECK" ],
"preferences": {
"seat": "LOUNGE",
"deck" : "UPPER_DECK"
},
"selected": true,
"booked" : true
},
{
"comfortClass" : "SECOND",
"price": "50€",
"seatOptions": [ "ANY", "AISLE", "WINDOW" ],
"deckOptions": [ "ANY", "MIDDLE_DECK", "LOWER_DECK" ],
"selected": false,
"booked" : false
}
]
},
{
"number": "B",
"bound": "outbound",
"departureDate": "2025-10-06T15:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "80€",
"selected": false,
"booked" : false
}
]
},
{
"number": "C",
"bound": "inbound",
"departureDate": "2025-10-16T12:00",
"fares": [
{
"comfortClass" : "FIRST",
"price": "90€",
"seatOptions": [ "ANY", "AISLE", "WINDOW", "PANORAMIC_VIEW" ],
"deckOptions": [ "ANY", "UPPER_DECK", "LOWER_DECK" ],
"preferences": {
"seat": "PANORAMIC_VIEW",
"deck" : "UPPER_DECK"
},
"selected": true,
"booked": true
}
]
},
{
"number": "D",
"bound": "inbound",
"departureDate": "2025-10-16T14:00",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "AISLE", "WINDOW", "PANORAMIC_VIEW" ],
"deckOptions": [ "ANY", "UPPER_DECK", "LOWER_DECK" ],
"selected": false,
"booked" : false
},
{
"comfortClass" : "SECOND",
"price": "50€",
"seatOptions": [ "ANY", "AISLE", "WINDOW" ],
"deckOptions": [ "ANY", "LOWER_DECK" ],
"selected": false,
"booked" : false
}
]
}
]
}
}{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"selectionComplete" : true,
"totalPrice" : "230€",
"paymentMean": {
"type": "CREDIT_CARD",
"number": "***********"
},
"travelInsurance" : "40€",
"ships": [
{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "AISLE", "WINDOW", "PANORAMIC_VIEW", "LOUNGE" ],
"deckOptions": [ "ANY", "UPPER_DECK", "MIDDLE_DECK", "LOWER_DECK" ],
"preferences": {
"seat": "LOUNGE",
"deck" : "UPPER_DECK"
},
"selected": true,
"booked" : true
},
{
"comfortClass" : "SECOND",
"price": "50€",
"seatOptions": [ "ANY", "AISLE", "WINDOW" ],
"deckOptions": [ "ANY", "MIDDLE_DECK", "LOWER_DECK" ],
"selected": false,
"booked" : false
}
]
},
{
"number": "B",
"bound": "outbound",
"departureDate": "2025-10-06T15:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "80€",
"selected": false,
"booked" : false
}
]
},
{
"number": "C",
"bound": "inbound",
"departureDate": "2025-10-16T12:00",
"fares": [
{
"comfortClass" : "FIRST",
"price": "90€",
"seatOptions": [ "ANY", "AISLE", "WINDOW", "PANORAMIC_VIEW" ],
"deckOptions": [ "ANY", "UPPER_DECK", "LOWER_DECK" ],
"preferences": {
"seat": "PANORAMIC_VIEW",
"deck" : "UPPER_DECK"
},
"selected": true,
"booked": true
}
]
},
{
"number": "D",
"bound": "inbound",
"departureDate": "2025-10-16T14:00",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "AISLE", "WINDOW", "PANORAMIC_VIEW" ],
"deckOptions": [ "ANY", "UPPER_DECK", "LOWER_DECK" ],
"selected": false,
"booked" : false
},
{
"comfortClass" : "SECOND",
"price": "50€",
"seatOptions": [ "ANY", "AISLE", "WINDOW" ],
"deckOptions": [ "ANY", "LOWER_DECK" ],
"selected": false,
"booked" : false
}
]
}
]
}
}public record Search(UUID id, Criteria criteria, SearchType searchType, List<Ship> ships) {
public Search {
+ if(!hasBookedShips()){
assert ships.stream()
.noneMatch(ship -> ship.departureDate().isBefore(now()))
: "Some ships are departing in the past";
+ }
}
}cruising:main+ 532
- 385
public record Search(UUID id, Criteria criteria, SearchType searchType, List<Ship> ships) {
public Search {
- if(!hasBookedShips()){
- assert ships.stream()
- .noneMatch(ship -> ship.departureDate().isBefore(now()))
- : "Some ships are departing in the past";
}
}
}cruising:main+ 1278
- 843
Besoin Métier
guide
Exploration
&
Expérimentation
Besoin Métier
Croisière
Réserver
Destination
Tarif
Définir
sélectionner
guide
Exploration
&
Expérimentation
Besoin Métier
façonne
Modèle Métier
Croisière
Réserver
Destination
Tarif
Définir
sélectionner
doit prendre en compte un nouveau
guide
Exploration
&
Expérimentation
Besoin Métier
façonne
Modèle Métier
guide
Exploration
&
Expérimentation
Besoin Métier
façonne
doit prendre en compte un nouveau
Modèle Métier
guide
Exploration
&
Expérimentation
Besoin Métier
étend
doit prendre en compte un nouveau
Modèle Métier
guide
Besoin Métier
Exploration
&
Expérimentation
Modèle Métier
étend
doit prendre en compte un nouveau
guide
Exploration
&
Expérimentation
Besoin Métier
Modèle Métier
doit prendre en compte un nouveau
étend
guide
Exploration
&
Expérimentation
Besoin Métier
Obsolète
Modèle Métier
étend
doit prendre en compte un nouveau
guide
Exploration
&
Expérimentation
Besoin Métier
Modèle Métier
doit prendre en compte un nouveau
façonne
guide
Exploration
&
Expérimentation
Besoin Métier
Modèle Métier
façonne
doit prendre en compte un nouveau
Pas un biais de confirmation
guide
Exploration
&
Expérimentation
Besoin Métier
Modèle Métier
doit prendre en compte un nouveau
façonne
Besoin Métier
en façonnant
Modèle Métier
Nouveau Modèle
peut
déprécier
Exploration
&
Expérimentation
{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"ships": [{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [{
"comfortClass" : "FIRST",
"price": "100€",
}]
}]
}
{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"ships": [{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "..." ],
"deckOptions": [ "ANY", "..." ],
"preferences": {
"seat": "LOUNGE",
"deck" : "UPPER_DECK"
},
"selected": true,
}]
}],
"selectionComplete" : true,
"totalPrice" : "230€",
}{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"ships": [{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "..." ],
"deckOptions": [ "ANY", "..." ],
"preferences": {
"seat": "LOUNGE",
"deck" : "UPPER_DECK"
},
"selected": true,
"booked" : true,
}]
}],
"selectionComplete" : true,
"totalPrice" : "230€",
"paymentMean": {
"type": "CREDIT_CARD",
"number": "***********"
},
"travelInsurance" : "40€",
}
Modèle
génère via des décisions inadéquates
se manifeste en complexité dans
Dette de Conception
sabote la cohésion
augmente la complexité
& érode la fiabilité des modèles !
introduit du couplage
moins de cohérence, consistence & de cohésion
résultats imprévisibles
régressions inattendues
Entropie de Modèle
génère par accumulation
Dette de Conception
dissipe la structure et la clarté de
Modèle
génère via des
décisions inadéquates
Code
Spaghetti
dissout le modèle en
Entropie de Modèle
génère par accumulation
Dette de Conception
dissipe la structure et la clarté de
Modèle
génère via des
décisions inadéquates
{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"selectionComplete" : true,
"totalPrice" : "230€",
"paymentMean": {
"type": "CREDIT_CARD",
"number": "***********"
},
"travelInsurance" : "40€",
"ships": [
{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "..." ],
"deckOptions": [ "ANY", "..." ],
"preferences": {
"seat": "LOUNGE",
"deck" : "UPPER_DECK"
},
"selected": true,
"booked" : true
manifests as
Refactoring
KISS
DRY
YAGNI
Pression des Deadlines
Biais des coûts irrécupérables
{}
Couplage intrinsèque
favorisent
favorisent
Refactoring
KISS
DRY
YAGNI
{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"selectionComplete" : true,
"totalPrice" : "230€",
"paymentMean": {
"type": "CREDIT_CARD",
"number": "***********"
},
"travelInsurance" : "40€",
"ships": [
{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "..." ],
"deckOptions": [ "ANY", "..." ],
"preferences": {
"seat": "LOUNGE",
"deck" : "UPPER_DECK"
},
"selected": true,
"booked" : true
Couplage intrinsèque
{}
se traduit par
Dette de Conception
Couplage de Responsabilité
Couplage intrinsèque
peut causer
"Effet Cuichette"
"Effet Cuichette"
cause
Fragilité de Modèle
Couplage intrinsèque
se traduit par
peut causer
entraine
Rigidité de Modèle
Entropie de Modèle
augmente
produit par
accumulation
{}
Dette de Conception
Couplage de Responsabilité
Intrinsic
Coupling
{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"selectionComplete" : true,
"totalPrice" : "230€",
"paymentMean": {
"type": "CREDIT_CARD",
"number": "***********"
},
"travelInsurance" : "40€",
"ships": [
{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "..." ],
"deckOptions": [ "ANY", "..." ],
"preferences": {
"seat": "LOUNGE",
"deck" : "UPPER_DECK"
},
"selected": true,
"booked" : true
{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"selectionComplete" : true,
"totalPrice" : "230€",
"paymentMean": {
"type": "CREDIT_CARD",
"number": "***********"
},
"travelInsurance" : "40€",
"ships": [
{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "..." ],
"deckOptions": [ "ANY", "..." ],
"preferences": {
"seat": "LOUNGE",
"deck" : "UPPER_DECK"
},
"selected": true,
"booked" : true
est le résultat de
Dette de Conception
Accidentelle
Couplage Intrinsèque
provoque
est un smell de design qui signale que l'on tord le modèle au delà de son intégrité conceptuelle
Tension
de Modèle
intégrité conceptuelle
Intégrité
Conceptuelle
érode
évite
Tension
de Modèle
est le résultat de
Dette de Conception
Accidentelle
{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"selectionComplete" : true,
"totalPrice" : "230€",
"paymentMean": {
"type": "CREDIT_CARD",
"number": "***********"
},
"travelInsurance" : "40€",
"ships": [
{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "..." ],
"deckOptions": [ "ANY", "..." ],
"preferences": {
"seat": "LOUNGE",
"deck" : "UPPER_DECK"
},
"selected": true,
"booked" : true
Tension
de Modèle
{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"selectionComplete" : true,
"totalPrice" : "230€",
"paymentMean": {
"type": "CREDIT_CARD",
"number": "***********"
},
"travelInsurance" : "40€",
"ships": [
{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "..." ],
"deckOptions": [ "ANY", "..." ],
"preferences": {
"seat": "LOUNGE",
"deck" : "UPPER_DECK"
},
"selected": true,
"booked" : true
{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"selectionComplete" : true,
"totalPrice" : "230€",
"paymentMean": {
"type": "CREDIT_CARD",
"number": "***********"
},
"travelInsurance" : "40€",
"ships": [
{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "..." ],
"deckOptions": [ "ANY", "..." ],
"preferences": {
"seat": "LOUNGE",
"deck" : "UPPER_DECK"
},
"selected": true,
"booked" : true
Tension
de Modèle
{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"selectionComplete" : true,
"totalPrice" : "230€",
"paymentMean": {
"type": "CREDIT_CARD",
"number": "***********"
},
"travelInsurance" : "40€",
"ships": [
{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"seatOptions": [ "ANY", "..." ],
"deckOptions": [ "ANY", "..." ],
"preferences": {
"seat": "LOUNGE",
"deck" : "UPPER_DECK"
},
"selected": true,
"booked" : true
Tension
de Modèle
compromet
"fibrose"
non résolue
mène à
Intégrité
Conceptuelle
érode
Entropie
Modèle
augmente
Tension
de Modèle
Tension
Cristallisée
Obsolescence
Signale
non résolue
mène à
Tension
de Modèle
Tension
Cristallisée
visual elements © Alberto Brandolini
Search
Bound
Select
a Fare on a Ship
Ship + Fare
Selected On Bound
Compute Total Price
Total Price Computation
Total Price Computed
Check
Completion
Fares Selection
COmpleted
Selected Fares
Selected Fares
Fares Selection Found Incomplete
Check Selected Fares
Completion
visual elements © Alberto Brandolini
Search
Bound
Select
a Fare on a Ship
Ship + Fare Selected On Bound
Compute Total Price
Total Price Computation
Total Price Computed
Check
Completion
Check Selected Fares
Completion
Fares Selection
COmpleted
Selected Fares
Fares Selection Found Incomplete
Selected Fares
visual elements © Alberto Brandolini
Search
Bound
Select
a Fare on a Ship
Ship + Fare
Selected On Bound
Compute Total Price
Total Price Computation
Total Price Computed
Check
Completion
Fares Selection
COmpleted
Selected Fares
Selected Fares
Fares Selection Found Incomplete
Check Selected Fares
Completion
visual elements © Alberto Brandolini
Search
Bound
Select
a Fare on a Ship
Ship + Fare
Selected On Bound
Compute Total Price
Total Price Computation
Total Price Computed
Check
Completion
Fares Selection
COmpleted
Selected Fares
Fares Selection Found Incomplete
Check Selected Fares
Completion
Selected Fares
visual elements © Alberto Brandolini
Search
Bound
Select
a Fare on a Ship
Ship + Fare
Selected On Bound
Compute Total Price
Total Price Computation
Total Price Computed
Check
Completion
Fares Selection
COmpleted
Selected Fares
Selected Fares
Fares Selection Found Incomplete
Check Selected Fares
Completion
Un biais cognitif qui prédispose à se concentrer sur les informations les plus proéminentes ou visibles.
visual elements © Alberto Brandolini
Search
Bound
Select
a Fare on a Ship
Ship + Fare
Selected On Bound
Compute Total Price
Total Price Computation
Total Price Computed
Check
Completion
Fares Selection
COmpleted
Selected Fares
Selected Fares
Fares Selection Found Incomplete
Check Selected Fares
Completion
Search
Bound
Select
a Fare on a Ship
Ship + Fare
Selected On Bound
Compute Total Price
Total Price Computation
Total Price Computed
Check
Completion
Fares Selection
COmpleted
Selected Fares
Selected Fares
Fares Selection Found Incomplete
Ancillaries
Retrieval
List
Ancillaries
Ship
Fare
Ancillaries
Listed
Ancillaries
visual elements © Alberto Brandolini
Check Selected Fares
Completion
Ancillary
Ship
Select
Ancillary
Ancillary
Selected
Search
Bound
Ship + Fare
Selected On Bound
Compute Total Price
Total Price Computation
Total Price Computed
Check
Completion
Fares Selection
COmpleted
Selected Fares
Selected Fares
Fares Selection Found Incomplete
Ancillaries
Retrieval
List
Ancillaries
Ship
Fare
Ancillaries
Listed
Ancillaries
Select
a Fare on a Ship
visual elements © Alberto Brandolini
Check Selected Fares
Completion
Ancillary
Ship
Select
Ancillary
Ancillary
Selected
Search
Bound
Ship + Fare
Selected On Bound
Compute Total Price
Total Price Computation
Total Price Computed
Check
Completion
Fares Selection
COmpleted
Selected Fares
Selected Fares
Fares Selection Found Incomplete
Ancillaries
Retrieval
List
Ancillaries
Ship
Fare
Ancillaries
Listed
Ancillaries
Select
a Fare on a Ship
Ancillaries
visual elements © Alberto Brandolini
Check Selected Fares
Completion
Ancillary
Ship
Select
Ancillary
Ancillary
Selected
Search
Bound
Ship + Fare
Selected On Bound
Compute Total Price
Total Price Computation
Total Price Computed
Check
Completion
Fares Selection
COmpleted
Selected Fares
Fares Selection Found Incomplete
Ancillaries
Retrieval
List
Ancillaries
Ship
Fare
Ancillaries
Listed
Ancillaries
Select
a Fare on a Ship
visual elements © Alberto Brandolini
Check Selected Fares
Completion
Selected
Fares
Ancillaries
Selected
Fares +
Ancillaries
Selected
Fares +
Ancillaries
Selection
Ancillary
Ship
Select
Ancillary
Ancillary
Selected
Search
Bound
Ship + Fare
Selected On Bound
Compute Total Price
Total Price Computation
Total Price Computed
Check
Completion
Fares Selection
COmpleted
Selected Fares
Fares Selection Found Incomplete
Ancillaries
Retrieval
List
Ancillaries
Ship
Fare
Ancillaries
Listed
Ancillaries
Select
a Fare on a Ship
Selection
visual elements © Alberto Brandolini
Check Selected Fares
Completion
Ancillary
Ship
Select
Ancillary
Ancillary
Selected
Search
Bound
Ship + Fare
Selected On Bound
Price
Selection
Total Price Computation
Selection
Priced
Check
Completion
Fares Selection
COmpleted
Selected Fares
Fares Selection Found Incomplete
Ancillaries
Retrieval
List
Ancillaries
Ship
Fare
Ancillaries
Listed
Ancillaries
Select
a Fare on a Ship
Selection
Priced
Selection
visual elements © Alberto Brandolini
Check Selected Fares
Completion
Ancillary
Ship
Select
Ancillary
Ancillary
Selected
Search
Bound
Ship + Fare
Selected On Bound
Check
Completion
Fares Selection
COmpleted
Selected Fares
Fares Selection Found Incomplete
Ancillaries
Retrieval
List
Ancillaries
Ship
Fare
Ancillaries
Listed
Ancillaries
Select
a Fare on a Ship
Price
Selection
Total Price Computation
Selection
Priced
Priced
Selection
Selection
visual elements © Alberto Brandolini
Check Selected Fares
Completion
Ancillary
Ship
Select
Ancillary
Ancillary
Selected
Search
Bound
Ship + Fare
Selected On Bound
Check
Selection
Completion
Check
Selection
Completion
Selection
COmpleted
Selection
Selection Found Incomplete
Ancillaries
Retrieval
List
Ancillaries
Ship
Fare
Ancillaries
Listed
Ancillaries
Select
a Fare on a Ship
Incomplete
Selection
complete
Selection
Price
Selection
Total Price Computation
Selection
Priced
Priced
Selection
Selection
visual elements © Alberto Brandolini
Ancillary
Ship
Select
Ancillary
Ancillary
Selected
Search
Bound
Ship + Fare
Selected On Bound
Check
Selection
Completion
Check
Selection
Completion
Selection
COmpleted
Selection
Selection Found Incomplete
Ancillaries
Retrieval
List
Ancillaries
Ship
Fare
Ancillaries
Listed
Ancillaries
Select
a Fare on a Ship
Incomplete
Selection
complete
Selection
Price
Selection
Total Price Computation
Selection
Priced
Priced
Selection
Selection
visual elements © Alberto Brandolini
Search
Bound
Select
a Fare on a Ship
Ship + Fare
Selected On Bound
Compute Total Price
Total Price Computation
Total Price Computed
Check
Completion
Fares Selection
COmpleted
Selected Fares
Selected Fares
Fares Selection Found Incomplete
Ancillaries
Retrieval
List
Ancillaries
Ship
Fare
Ancillaries
Listed
Ancillaries
Incomplete
Selection
complete
Selection
Priced
Selection
visual elements © Alberto Brandolini
Check Selected Fares
Completion
Ancillary
Ship
Select
Ancillary
Ancillary
Selected
Search
Bound
Ship + Fare
Selected On Bound
Check
Selection
Completion
Check
Selection
Completion
Selection
COmpleted
Selection Found Incomplete
Ancillaries
Retrieval
List
Ancillaries
Ship
Fare
Ancillaries
Listed
Ancillaries
Select
a Fare on a Ship
Incomplete
Selection
complete
Selection
Price
Selection
Total Price Computation
Selection
Priced
Priced
Selection
Selection
visual elements © Alberto Brandolini
Selection
Select
Ancillary
Check
Selection
Completion
Selection
COmpleted
Selection Found Incomplete
Price
Selection
Selection
Priced
Select
a Fare on a Ship
Ancillary
Selected
Ship + Fare
Selected On Bound
SELECTION
visual elements © Alberto Brandolini
{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"selectionComplete" : true,
"totalPrice" : "230€",
"paymentMean": {
"type": "CREDIT_CARD",
"number": "***********"
},
"ships": [
{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"selected": true,
"booked" : true
{
"search": {
"id": "XXX",
"ships": [
{
"number": "A",
"bound": "outbound",
"fares": [
{
"comfortClass": "FIRST",
"price": "100€"
},
{
"comfortClass": "SECOND",
"price": "50€"
}
]
},
{
"number": "B",
"bound": "outbound",
"fares": [
{
"comfortClass": "FIRST",
"price": "80€"
}
]
},
{
"number": "C",
"bound": "inbound",
"fares": [
{
"comfortClass": "FIRST",
"price": "90€"
}
]
},
{
"number": "D",
"bound": "inbound",
"fares": [
{
"comfortClass": "FIRST",
"price": "100€"
},
{
"comfortClass": "SECOND",
"price": "50€"
}
]
}
]
}
}{
"selection": {
"id": "YYYY",
"isComplete": true,
"totalPrice": "190€",
"outbound": {
"number": "A",
"fare": {
"comfortClass": "FIRST",
"price": "100€"
}
},
"inbound": {
"number": "C",
"fare": {
"comfortClass": "FIRST",
"price": "90€"
}
}
}
}
class Selection {
TripType tripType;
Map<Bound, Fare> selectedFares = new HashMap();
void selectFare(Bound bound, Fare selectedFare) {
selectedFares.put(bound, selectedFare);
}
Price totalPrice() {
return selectedFares.values().stream()
.reduce(Fare::add)
.get();
}
boolean isSelectionComplete() {
if (tripType == ONEWAY){
return selectedFares.size() == 1;
} else {
return selectedFares.size() == 2;
}
}
}public record Search(UUID id, Criteria criteria, SearchType searchType, List<Ship> ships, PaymentMean paymentMean) {
public Search {
assert ships.stream().noneMatch(ship -> ship.departureDate().isBefore(now()))
: "Some ships are departing in the past";
}
/*...*/
}
public record Search(UUID id, Criteria criteria, SearchType searchType, List<Ship> ships, PaymentMean paymentMean) {
public Search {
if (!hasBookedShips()) {
assert ships.stream().noneMatch(ship -> ship.departureDate().isBefore(now()))
: "Some ships are departing in the past";
}
}
}
public record Search(UUID id, Criteria criteria, SearchType searchType, List<Ship> ships, PaymentMean paymentMean) {
public Search {
if (!hasBookedShips()) {
assert ships.stream().noneMatch(ship -> ship.departureDate().isBefore(now()))
: "Some ships are departing in the past";
}
}
public boolean hasBookedShips() {
Map<Bound, Long> bookedShipsPerBound =
ships.stream()
.flatMap(ship -> ship.fares().stream().filter(Fare::booked).map(_ -> ship))
.collect(groupingBy(Ship::bound, counting()));
if (searchType == SearchType.ONEWAY) {
return bookedShipsPerBound.get(OUTBOUND) == 1;
} else { //RoundTrip
return bookedShipsPerBound.get(OUTBOUND) == 1 && bookedShipsPerBound.get(INBOUND) == 1;
}
}
public List<Ship> getBookedShips() {
var bookedShips = ships.stream().filter(ship -> ship.fares().stream().anyMatch(Fare::booked)).toList();
if (bookedShips.isEmpty()) {
return Collections.emptyList();
}
List<Ship> cleanedShips = new ArrayList<>();
for (var bookedShip : bookedShips) {
Fare bookedFare = getBookedFare(bookedShip);
Ship cleanedShip = new Ship(bookedShip.number(), bookedShip.bound(), bookedShip.departureDate(), asList(bookedFare));
cleanedShips.add(cleanedShip);
}
return cleanedShips;
}
private Fare getBookedFare(Ship bookedShip) {
Fare bookedFare = null;
int i = 0;
while(i < bookedShip.fares().size()) {
Fare fare = bookedShip.fares().get(i);
if(fare.booked()){
bookedFare = fare;
}
i++;
}
return bookedFare;
}
public void book() {
if(paymentMean == null) {
throw new IllegalStateException("Payment mean is missing");
}
if (hasBookedShips()) {
throw new IllegalStateException("Ships are already booked");
}
var selectedFaresPerBound = new ArrayList<Entry<Bound, Fare>>();
for (Ship ship : ships) {
for (Fare fare : ship.fares()) {
if (fare.selected()) {
selectedFaresPerBound.add(entry(ship.bound(), fare));
}
}
}
if (selectedFaresPerBound.isEmpty()) {
throw new IllegalStateException("Ships must be selected before booking");
}
var numberOfSelectedFarePerBound = selectedFaresPerBound.stream().collect(groupingBy(Entry::getKey, counting()));
if (searchType == SearchType.ONEWAY) {
assert numberOfSelectedFarePerBound.get(OUTBOUND) == 1 && numberOfSelectedFarePerBound.get(INBOUND) == 0 : "Inconsistent number of selected fares";
} else { //RoundTrip
assert numberOfSelectedFarePerBound.get(OUTBOUND) == 1 && numberOfSelectedFarePerBound.get(INBOUND) == 1 : "Inconsistent number of selected fares";
}
for (Entry<Bound, Fare> selectedFare : selectedFaresPerBound) {
selectedFare.getValue().book();
}
}
public boolean isSelectionComplete() {
Map<Bound, Long> selectedFaresPerBound = ships.stream().flatMap(ship -> ship.fares().stream().filter(Fare::selected).map(_ -> ship)).collect(groupingBy(Ship::bound, counting()));
var selectedOutBounds = selectedFaresPerBound.get(OUTBOUND);
var selectedInBounds = selectedFaresPerBound.get(INBOUND);
if (searchType == SearchType.ONEWAY) {
//This should never happen 👇
assert selectedOutBounds <= 1 && selectedInBounds <= 0 : "Too many fares are selected";
return selectedOutBounds == 1;
} else { //RoundTrip
//This should never happen 👇
assert selectedOutBounds <= 1 && selectedInBounds <= 1 : "Too many fares are selected";
return selectedOutBounds == 1 && selectedInBounds == 1;
}
}
public Price getTotalPrice() {
return ships.stream().flatMap(ship -> ship.fares().stream().filter(Fare::selected).map(Fare::price)).reduce(Price::add).orElse(Price.ZERO);
}
public void selectFareOfShip(Bound bound, String shipNumber, ComfortClass comfortClass) {
var fareToSelect = ships.stream().filter(ship -> ship.number().equals(shipNumber) && ship.bound() == bound).flatMap(ship -> ship.fares().stream().filter(fare -> fare.comfortClass() == comfortClass)).findFirst().orElseThrow();
var previousSelectionOnBound = ships.stream().filter(ship -> ship.bound() == bound).flatMap(ship -> ship.fares().stream()).filter(Fare::selected).findFirst();
previousSelectionOnBound.ifPresent(fare -> fare.setSelected(false));
fareToSelect.setSelected(true);
}
}
{
"search": {
"id": "XXXX",
"criteria": {
"outBoundDate": "2025-10-06T12:00",
"inBoundDate": "2025-10-16T11:00"
},
"selectionComplete" : true,
"totalPrice" : "230€",
"paymentMean": {
"type": "CREDIT_CARD",
"number": "***********"
},
"ships": [
{
"number": "A",
"bound": "outbound",
"departureDate": "2025-10-06T13:30",
"fares": [
{
"comfortClass" : "FIRST",
"price": "100€",
"selected": true,
"booked" : true
public record Search(UUID id, Criteria criteria, SearchType searchType, List<Ship> ships) {
public Search {
assert ships.stream().noneMatch(ship -> ship.departureDate().isBefore(now()))
: "Some ships are departing in the past";
}
/*...*/
}
public record Booking(UUID id, List<Ship> ships, PaymentMean paymentMean, Boolean finalized) {
public Booking {
assert ships.stream().noneMatch(ship -> ship.departureDate().isBefore(now()))
: "Some ships are departing in the past";
}
}
public record Search(UUID id, Criteria criteria, SearchType searchType, List<Ship> ships) {
public Search {
assert ships.stream().noneMatch(ship -> ship.departureDate().isBefore(now()))
: "Some ships are departing in the past";
}
/*...*/
}
public record Booking(UUID id, List<Ship> ships, PaymentMean paymentMean, Boolean finalized) {
public Booking {
if(!finalized){
assert ships.stream().noneMatch(ship -> ship.departureDate().isBefore(now()))
: "Some ships are departing in the past";
}
}
}
CRUISING
POST-BOOKING
Text
décharge
Modèle Spaghetti
Nouveau
Modèle
Cruising
Post-Booking
POST-BOOKING
CRUISING
Search
Booking
Selection
POST-BOOKING
CRUISING
Booking
Selection
Search
POST-BOOKING
CRUISING
Search
Booking
Selection
POST-BOOKING
CRUISING
Search
Booking
Selection
noDepartureInThePast()
noDepartureInThePast()
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
POST-BOOKING
CRUISING
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation
Booking
Exchanged
Booking
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Cancelled
Booking
POST-BOOKING
CRUISING
Booking
Exchanged
Booking
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Cancelled
Booking
Completed
Booking
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Created
Completed
Exchanged
Cancelled
POST-BOOKING
CRUISING
SHOPPING
ORDER
DELIVERY
POST-BOOKING
CRUISING
POST-BOOKING
CRUISING
correction des croisières qui partent dans le passé
POST-BOOKING
CRUISING
APPLICATION MOBILE
Déterminer le statut actuel d'un booking
vérifie si le booking existe
vérifie si l'échange ou l'annulation existe
Completed
POST-BOOKING
CRUISING
vérifie si l'échange ou l'annulation existe
vérifie si le booking existe
BOOKING
potentiellement les deux
APPLICATION MOBILE
Déterminer le statut actuel d'un booking
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
POST-BOOKING
CRUISING
Cancellation
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation
POST-BOOKING
CRUISING
casse de la logique dans
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Text
décharge
Cruising
Post-Booking
Modèle Spaghetti
Nouveau
Modèle
Text
contourne
Cruising
Post-Booking
Modèle Spaghetti
Modèle
Excroissant
Cruising
Post-Booking
contourne
devient un fragment de
Modèle
Fragmenté
est un fragment de
Modèle Spaghetti
Modèle
Excroissant
engendre
Couplage
Extrinsèque
Fuite de
Logique Métier
Modèle
Fragmenté
induit
Modèle
Excroissant
favorise
aggrave
est une forme de
Couplage
Extrinsèque
Evolutions
Synchronisées
Couplage
Extrinsèque
contraint le système à des
Échecs
Synchronisés
engendre
Monolithe
Distribué
signalent
signalent
Synced
Evolution
results in
Synced
Failures
leads to
System
Entropy
raises
binds the fragments into
Distributed
BBOM
entangles
into
crystallizes
into
Fragmented
Model
Rigidity
causes
Fragmented
Model
Fragility
POST-BOOKING
CRUISING
Booking
Release 464
Release 52
Insurance
Policy
Insurance
Policy
POST-BOOKING
CRUISING
ignore
Release 464
Release 52
Booking
Insurance
Policy
Insurance
Policy
Cancellation
POST-BOOKING
CRUISING
ignore
Release 464
Release 52
Booking
Insurance
Policy
Insurance
Policy
Incompatibles
Cancellation
POST-BOOKING
CRUISING
Insurance
Policy
Release 464
Release 53
Déploiements simultanés
Booking
Insurance
Policy
Insurance
Policy
Cancellation
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Tension Process
Tension Org.
Constrainte
Structurante
POST-BOOKING
CRUISING
Release 465
Release 57
Incompatibles
Meal
POST-BOOKING
CRUISING
Release 465
Release 57
Compatibles
Meal
Toggle: OFF
POST-BOOKING
CRUISING
Release 465
Release 58
Compatibles
Toggle: OFF
Toggle: OFF
Meal
Meal
POST-BOOKING
CRUISING
Release 465
Release 58
Toggle: ON
Toggle: ON
Meal
Meal
Compatibles
POST-BOOKING
CRUISING
Meal
Meal
Interface
Toggle: OFF
Toggle: OFF
POST-BOOKING
CRUISING
Meal
Meal
Interface
Toggle: OFF
Toggle: OFF
prêt ?
prêt ?
POST-BOOKING
CRUISING
Meal
Meal
Interface
Toggle: OFF
Toggle: OFF
prêt ?
prêt ?
POST-BOOKING
CRUISING
Meal
prêt ?
Meal
Interface
prêt ?
Toggle: OFF
Toggle: OFF
Couplage
Extrinsèque
mène à
Livraisons
Synchronisées
Équipes
Interdépendantes
mène à
Équipes
Interdépendantes
Livraisons
Synchronisées
compensées par
compensées par
gérés par
Couplage
Extrinsèque
mène à
mène à
Processus
Rustines
Rôles Palliatifs
Couplage
Extrinsèque
mène à
mène à
Livraisons
Synchronisées
compensées par
compensées par
Équipes
Interdépendantes
Processus
Rustines
Rôles Palliatifs
Contre-Mesures
Compensatoires
Palliative Roles
handled by
Band-aid processes
Tension Process
Tension Org.
Shared & Distributed
DataSets
Tests Envs Proliferations
Feature Flags
Overuse
Multi
Versionning
Synced
plannings
& backlogs
Couplage
Extrinsèque
mène à
mène à
Livraisons
Synchronisées
compensées par
Contre-Mesures
Compensatoires
compensées par
Équipes
Interdépendantes
repousse le problème de
repousse le problème de
Shared & Distributed
DataSets
Tests Envs Proliferations
Feature Flags
Overuse
Multi
Versionning
Synced
plannings
& backlogs
Couplage
Extrinsèque
mène à
mène à
n'adresse jamais
Livraisons
Synchronisées
repousse le problème de
repousse le problème de
compensées par
Contre-Mesures
Compensatoires
compensées par
Équipes
Interdépendantes
Shared & Distributed
DataSets
Tests Envs Proliferations
Feature Flags
Overuse
Multi
Versionning
Synced
plannings
& backlogs
Couplage
Extrinsèque
mène à
mène à
n'adresse jamais
Livraisons
Synchronisées
compensées par
Contre-Mesures
Compensatoires
compensées par
Équipes
Interdépendantes
repousse le problème de
repousse le problème de
Shared & Distributed
DataSets
Tests Envs Proliferations
Feature Flags
Overuse
Multi
Versionning
Synced
plannings
& backlogs
Couplage
Extrinsèque
mène à
mène à
n'adresse jamais
Livraisons
Synchronisées
compensées par
Contre-Mesures
Compensatoires
compensées par
Équipes
Interdépendantes
repousse le problème de
repousse le problème de
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Tension Process
Système de
Compensations
Tension Org.
Contrainte
Structurante
Cristallisation
Tensions de Modèle
Booking
Exchange
POST-BOOKING
CRUISING
Cancellation
Seat Preferences
Seat Preferences
ToDo
ToDo
In Progress
ToDo
Contrainte Structurante
Synchronized
Releases
InterTeam
Work Coupling
Système de
Compensations
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Tension Org.
Tension Org.
Catalyseur
Model
Fragmentation
Organizational
Fragmentation
Synced
Evolution
Process
overhead
???
Business
Impact
Socio-technical system entropy
Model
Fragmentation
Organizational
Fragmentation
Synced
Evolution
Process
overhead
Modèle Spaghetti
Outgrowth
Model
Modèle Spaghetti
contourne
Modèle Excroissant
Historical
Team
New Team
offloads the
work of
Équipe Historique
décharge
Nouvelle Équipe
Modèle Spaghetti
Modèle Excroissant
contourne
Fragmentation
Organisationnelle
Historical
Team
New Team
Modèle Excroissant
contourne
Modèle Spaghetti
Historical
Team
New Team
Loi de Conway
Fragmentation
Organisationnelle
Fragmentation de Modèle
Modèle Excroissant
contourne
Modèle Spaghetti
Historical
Team
New Team
Fragmentation
Organisationnelle
Loi de Conway
Fragmentation de Modèle
Modèle Excroissant
contourne
Modèle Spaghetti
Équipe Historique
décharge
Nouvelle Équipe
Modèle Spaghetti
Modèle Excroissant
contourne
Structure eats Strategy
Jan Bosch - 2017
Organizational Tensions
produce
Architectural
Tensions
structure
Process
Tensions
compensate
Business
Tensions
reflects as
SHOPPING
ORDER
DELIVERY
...n'indiquent pas toujours des tensions.
Détecter les frictions
à travers les strates du système
Mettre en évidence
les couplages
noDepartureInThePast()
noScheduleOverlap()
noDepartureInThePast()
noScheduleOverlap()
Constituer un faisceau d'indices pour trouver des corrélations
Évaluer leur localisation, profondeur & étendue
Identifier les causes possibles avec les heuristiques
Une fois un faisceau d'indices constitué, agissez sur les signaux en menant des petites expériences réversibles pour tester vos hypothèses et vos solutions potentielles
POST-BOOKING
CRUISING
Exchange
Cancellation
Search
Booking
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation
Contrainte Structurante
Synchronized
Releases
InterTeam
Work Coupling
Système de
Compensations
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Tension Org.
Tension Org.
Catalyseur
Heuristiques
détectent
Tensions de Modèle
préviennent
Entropie Organisationnelle
@julientopcu.com
@josianchevalier.bsky.social