@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
Business Need
guides
Exploration
&
Experimentation
Business Need
Cruise
Book
Destination
Fare
Define
select
guides
Exploration
&
Experimentation
Business Need
shapes
Domain Model
Cruise
Book
Destination
Fare
Define
select
must account for a new
guides
Exploration
&
Experimentation
Business Need
shapes
Domain Model
guides
Exploration
&
Experimentation
Business Need
shapes
must account for a new
Domain Model
guides
Exploration
&
Experimentation
Business Need
extends
must account for a new
Domain Model
guides
Business Need
Exploration
&
Experimentation
Domain Model
extends
must account for a new
guides
Exploration
&
Experimentation
Business Need
Domain Model
must account for a new
extends
guides
Exploration
&
Experimentation
Business Need
Obsolete
Domain Model
extends
must account for a new
guides
Exploration
&
Experimentation
Business Need
Domain Model
must account for a new
shapes
guides
Exploration
&
Experimentation
Business Need
Domain Model
shapes
must account for a new
Not a confirmation bias
guides
Exploration
&
Experimentation
Business Need
Domain Model
must account for a new
shapes
Business Need
by shaping
Domain Model
New Model
can
deprecate
Exploration
&
Experimentation
{
"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€",
}
Model
generates through misguided decisions
manifests as complexity in
Design Debt
undermines cohesion
increases complexity
& erodes model reliability!
introduces coupling
less coherence, consistency & cohesion
unclear outcomes
unexpected regressions
Model Entropy
generates through accumulation
Design Debt
dissipates the structure and clarity of
Model
generates through
misguided decisions
Spaghetti
Code
dissolves the model into
Model Entropy
generates through accumulation
Design Debt
dissipates the structure and clarity of
Model
generates through
misguided decisions
{
"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
Deadline Pressure
Sunk cost Fallacies
{}
Intrinsic coupling
fosters
fosters
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
Intrinsic coupling
{}
translates into
Accidental Design Debt
Responsibility Coupling
Intrinsic coupling
may induce
"Spork Effect"
"Spork Effect"
causes
Model Fragility
Intrinsic coupling
translates into
may induce
leads to
Model Rigidity
Model Entropy
increases
produced by
accumulation
{}
Accidental Design Debt
Responsibility Coupling
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
is the result of
Accidental
Design Debt
Intrinsic Coupling
may
induce
is a design smell that signals that we are stretching the model beyond its conceptual integrity
Model
Tension
conceptual integrity
Conceptual
Integrity
erodes
prevents
Model
Tension
is the result of
Accidental
Design Debt
{
"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
Model
Tension
{
"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
Model
Tension
{
"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
Model
Tension
compromises
"fibrosis"
unresolved
leads to
erodes
Model
Entropy
raises
Model
Tension
Crystallized
Tension
Conceptual
Integrity
Obsolescence
Signals
unresolved
leads to
Model
Tension
Crystallized
Tension
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
A cognitive bias that predisposes people to focus on information that are more prominent or visible
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
offloads
Spaghetti
Model
New
Model
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
fix ship departing in the past
POST-BOOKING
CRUISING
MOBILE APP
Determining the current status of a booking
checks if the booking exists
checks if the exchange or cancellation exists
Completed
POST-BOOKING
CRUISING
checks if the exchange or cancellation exists
checks if the booking exists
BOOKING
MOBILE APP
Determining the current status of a booking
can be both
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
breaks logic in
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Text
offloads
Cruising
Post-Booking
Spaghetti
Model
New
Model
Text
works around
Cruising
Post-Booking
Spaghetti
Model
Model
Outgrowth
Cruising
Post-Booking
works around
becomes a fragment of
Fragmented
Model
is a fragment of
Spaghetti
Model
Outgrowth
Model
causes
Extrinsic
Coupling
Business Logic
Leak
Fragmented
Model
induces
Outgrowth
Model
favorises
exacerbates
is a form of
Extrinsic
Coupling
Synchronized
Evolutions
Extrinsic
Coupling
forces
the system into
Synchronized
Failures
causes
Distributed
Monolith
signal
signal
POST-BOOKING
CRUISING
Booking
Release 464
Release 52
Insurance
Policy
Insurance
Policy
POST-BOOKING
CRUISING
ignores
Release 464
Release 52
Booking
Insurance
Policy
Insurance
Policy
Cancellation
POST-BOOKING
CRUISING
ignores
Release 464
Release 52
Booking
Insurance
Policy
Insurance
Policy
Incompatible
Cancellation
POST-BOOKING
CRUISING
Insurance
Policy
Release 464
Release 53
Booking
Insurance
Policy
Insurance
Policy
Cancellation
Deploy Together
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Process Tension
Org. Tension
Structuring
Constraint
POST-BOOKING
CRUISING
Release 465
Release 57
Incompatible
Meal
POST-BOOKING
CRUISING
Release 465
Release 57
Compatible
Meal
Toggle: OFF
POST-BOOKING
CRUISING
Release 465
Release 58
Compatible
Toggle: OFF
Toggle: OFF
Meal
Meal
POST-BOOKING
CRUISING
Release 465
Release 58
Toggle: ON
Toggle: ON
Meal
Meal
Compatible
POST-BOOKING
CRUISING
Meal
Meal
Interface
Toggle: OFF
Toggle: OFF
POST-BOOKING
CRUISING
Meal
Meal
Interface
Toggle: OFF
Toggle: OFF
ready?
ready?
POST-BOOKING
CRUISING
Meal
Meal
Interface
Toggle: OFF
Toggle: OFF
ready?
ready?
POST-BOOKING
CRUISING
Meal
ready?
Meal
Interface
ready?
Toggle: OFF
Toggle: OFF
Extrinsic
Coupling
leads to
Synchronized
Releases
InterTeam
Work Coupling
leads to
InterTeam
Work Coupling
Synchronized
Releases
buffered by
buffered by
handled by
Extrinsic
Coupling
leads to
leads to
Band-aid Processes
Palliative Roles
Extrinsic
Coupling
leads to
leads to
Synchronized
Releases
buffered by
buffered by
InterTeam
Work Coupling
Patch
Processes
Palliative Roles
Compensatory
Counter-Measures
Palliative Roles
handled by
Band-aid processes
Process Tension
Org. Tension
Shared & Distributed
DataSets
Tests Envs Proliferations
Feature Flags
Overuse
Multi
Versionning
Synced
plannings
& backlogs
Extrinsic
Coupling
leads to
leads to
Synchronized
Releases
Compensatory
Counter-Measures
offset
offset
buffered by
buffered by
InterTeam
Work Coupling
Shared & Distributed
DataSets
Tests Envs Proliferations
Feature Flags
Overuse
Multi
Versionning
Synced
plannings
& backlogs
Extrinsic
Coupling
leads to
leads to
never addresses
Synchronized
Releases
offset
offset
buffered by
buffered by
InterTeam
Work Coupling
Compensatory
Counter-Measures
Shared & Distributed
DataSets
Tests Envs Proliferations
Feature Flags
Overuse
Multi
Versionning
Synced
plannings
& backlogs
Extrinsic
Coupling
leads to
leads to
never addresses
Synchronized
Releases
offset
offset
buffered by
buffered by
InterTeam
Work Coupling
Compensatory
Counter-Measures
Shared & Distributed
DataSets
Tests Envs Proliferations
Feature Flags
Overuse
Multi
Versionning
Synced
plannings
& backlogs
Extrinsic
Coupling
leads to
leads to
never addresses
Synchronized
Releases
offset
offset
buffered by
buffered by
InterTeam
Work Coupling
Compensatory
Counter-Measures
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Process Tension
Compensations
System
Org. Tension
Structuring
Constraint
Crystallization
Model Tensions
Booking
Exchange
POST-BOOKING
CRUISING
Cancellation
Seat Preferences
Seat Preferences
ToDo
ToDo
In Progress
ToDo
GoverningConstraint
Synchronized
Releases
InterTeam
Work Coupling
Compensations
Systems
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Org. Tension
Org. Tension
Catalyst
Spaghetti Model
Outgrowth
Model
Spaghetti Model
works around
Outgrowth Model
Historical
Team
New Team
offloads the
work of
New Team
Spaghetti Model
Outgrowth Model
works around
offloads the
work of
Historical
Team
Historical
Team
New Team
Outgrowth Model
Spaghetti Model
works around
Organizational
Fragmentation
Historical
Team
New Team
Model Fragmentation
Outgrowth Model
bypasses
Spaghetti Model
Conway's Law
Organizational
Fragmentation
Historical
Team
New Team
Model Fragmentation
Outgrowth Model
bypasses
Spaghetti Model
Organizational
Fragmentation
New Team
Spaghetti Model
Outgrowth Model
works around
offloads the
work of
Historical
Team
SHOPPING
ORDER
DELIVERY
...don't always indicate tensions.
Detect frictions
across the layers of the system
Highlight
the couplings
noDepartureInThePast()
noScheduleOverlap()
noDepartureInThePast()
noScheduleOverlap()
Build up a body of evidence to find correlations
Assess their location, depth & scope
Identify possible causes with the heuristics
POST-BOOKING
CRUISING
Exchange
Cancellation
Search
Booking
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation
Synchronized
Releases
InterTeam
Work Coupling
Compensations
Systems
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Org. Tension
Org. Tension
Catalyst
GoverningConstraint
@julientopcu.com
@josianchevalier.bsky.social