@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
}
]
}
]
}
}
drives
Exploration
&
Experimentation
Business Need
shape
Model
drives
Exploration
&
Experimentation
Business Need
shape
Model
needs to account for new
drives
Exploration
&
Experimentation
Business Need
shape
Model
needs to account for new
drives
Exploration
&
Experimentation
Business Need
shape
Model
needs to account for new
drives
Exploration
&
Experimentation
Business Need
shape
Model
needs to account for new
drives
Exploration
&
Experimentation
Business Need
shape
Model
needs to account for new
drives
Exploration
&
Experimentation
Business Need
expand
Model
needs to account for new
drives
Exploration
&
Experimentation
Business Need
Model
needs to account for new
expand
drives
Exploration
&
Experimentation
Business Need
Model
BBOM
needs to account for new
collapses into
expand
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 -985
drives
Exploration
&
Experimentation
Business Need
BBOM
Solution-Push
focuses on
Obsolete
Model
needs to account for new
collapses into
expand
drives
Exploration
&
Experimentation
Business Need
shape
Model
needs to account for new
Exploration
&
Experimentation
Business Need
Model
Business Need
Model
Not a confirmation bias
Exploration
&
Experimentation
Business Need
by shaping
Model
New Model
may
Depreciate
Exploration
&
Experimentation
Model
Accidental
Design Debt
generates through
misguided decisions
manifests
as complexity in
lower their cohesion and flexibility,
increase their complexity,
and erode their correctness.
introducing coupling in models,
Model
Accidental
Design Debt
generates through
misguided decisions
manifests
as complexity in
Model
Accidental
Design Debt
generates through
misguided decisions
manifests
as complexity in
Model
Accidental
Design Debt
generates through
misguided decisions
manifests
as complexity in
Model
Accidental
Design Debt
Model
Entropy
produces through
accumulation
generates through
misguided decisions
manifests
as complexity in
low coherency, consistency, cohesiveness
unclear outcomes
unexpected regressions
dissipates the structure
and clarity of
Model
Entropy
produces through accumulation
Model
Accidental
Design Debt
generates through
misguided decisions
dissolves the model into a
BBOM
Model
Entropy
produces through accumulation
dissipates the structure
and clarity of
Model
Sclerosis
hardens into
locking the model into
Model
Accidental
Design Debt
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
Accidental
Design Debt
Responsibilities
Coupling
may induce
strong
manifests as
{
"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
Accidental
Design Debt
Responsibilities
Coupling
may induce
manifests as
strong
{
"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
Accidental
Design Debt
Responsibilities
Coupling
may induce
manifests as
strong
{
"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
Accidental
Design Debt
Responsibilities
Coupling
may induce
manifests as
strong
Intrinsic
Coupling
manifests as
strong
Accidental
Design Debt
Responsibilities
Coupling
may induce
"Spork Effect"
"Spork Effect"
causes
Model
Fragility
Intrinsic
Coupling
manifests as
strong
Accidental
Design Debt
Responsibilities
Coupling
may induce
Model
Rigidity
leads to
Model
Entropy
increases
produces
through accumulation
{}
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
"Spork Effect"
Intrinsic
Coupling
Refactoring
Habits
fosters
KISS
DRY
YAGNI
{}
Sunk Cost
Fallacies
fosters
Deadlines
Pressure
"Spork Effect"
Intrinsic
Coupling
{}
Refactoring
Habits
fosters
Sunk Cost
Fallacies
fosters
Deadlines
Pressure
KISS
DRY
YAGNI
is the path to
BBOM
"Spork Effect"
Intrinsic
Coupling
{}
BBOM
leads to
Model
Entropy
dissolves the model into a
"Spork Effect"
Intrinsic
Coupling
{}
BBOM
Model
Entropy
dissolves the model into a
leads to
locking the
model into
hardens into
Model
Sclerosis
{
"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 a structural stress within a model that signals it is being stretched beyond its conceptual integrity.
conceptual integrity
Model
Tension
is the result
of
Accidental
Design Debt
Intrinsic
Coupling
may
induce
is the degree to which a model maintains clarity, coherence, cohesion
and its purpose.
is the result
of
Accidental
Design Debt
Model
Tension
Conceptual
Integrity
erodes
prevents
{
"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
"fibrosing"
left unresolved
leads to
Model
Tension
Conceptual
Integrity
erodes
Crystallized
Model Tension
Model
Entropy
raises
Model
Sclerosis
hardens into
shapes
left unresolved
leads to
Model
Tension
Crystallized
Model Tension
Model
Obsolescence
signals
Entropy
Model Rigidity
Entropy
visual elements © Alberto Brandolini
Search
Bound
Select
a Fare on a Ship
Compute Total Price
Total Price Computation
Total Price Computed
Check
Completion
Check Selected Fares
Completion
Fares Selection
COmpleted
Selected Fares
Selected Fares
Fares Selection Found Incomplete
Ship + Fare Selected On Bound
visual elements © Alberto Brandolini
Ship
& Fare
Selected On Bound
Select
a Fare on a Ship
Selected Fares
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
Selected Fares
Fares Selection Found Incomplete
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
Selected Fares
Ancillaries
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
Selected Fares
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
(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
Selected Fares
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
(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
Selected Fares
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
(Selection)
visual elements © Alberto Brandolini
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 selectedFare(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 {
if(!finalized){
assert ships.stream().noneMatch(ship -> ship.departureDate().isBefore(now()))
: "Some ships are departing in the past";
}
}
boolean isFinalized(){/**/}
}
CRUISING
POST-BOOKING
Text
works around
Sclerosed
Model
Outgrowth
Model
POST-BOOKING
CRUISING
Search
Fare
Booking
Selection
POST-BOOKING
CRUISING
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
POST-BOOKING
CRUISING
Booking
Exchange
POST-BOOKING
CRUISING
Seat Preferences
Seat Preferences
Cancellation
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation
POST-BOOKING
CRUISING
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation
Text
works around
Sclerosed
Model
Outgrowth
Model
Sclerosed
Model
Outgrowth
Model
works around
Distributed
Model
becomes a fragment of
is a fragment of
causes
Extrinsic
Coupling
Business
Logic
Leak
favorises
induces
exacerbates
is a form of
Extrinsic
Coupling
Outgrowth
Model
Model
Fragmentation
Model
Evolutions
Model
Fragmentation
Extrinsic
Coupling
is a form of
distributes
Synced
Failures
may induce
Fragmented
Model
Fragility
Synced
Failures
Fragmented
Model
Rigidity
Distributed
BBOM
cause
cause
Model
Evolutions
Distributed
leads to
System
Entropy
raises
binds the fragments into
entangles
into
???
A capability varies in scope, maturity, or behavior across the system, causing gaps and inconsistencies.
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation
POST-BOOKING
CRUISING
POST-BOOKING
CRUISING
chooseSeatPreferences()
When separate bounded-contexts handle different states of the same aggregate, while sharing many concepts, personae, and rules.
POST-BOOKING
CRUISING
MOBILE APP
Determining current booking status
check booking exists
check if exchange or cancellation exists
POST-BOOKING
CRUISING
MOBILE APP
check if exchanged/cancelled
check booking exists
Determining current booking status
BOOKING
Understanding the state of a concept requires consolidating data from multiple bounded-contexts
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
POST-BOOKING
CRUISING
Behaviors, invariants, lifecycles, etc. are scattered across bounded-contexts
POST-BOOKING
CRUISING
New rule for enabling them to a user
Ancillaries
Ancillaries
Every model evolution breaks collaborating bounded-contexts.
POST-BOOKING
CRUISING
Booking
Release 463
Release 52
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
Deploy Together
Booking
Insurance
Policy
Insurance
Policy
Cancellation
Necessity to synchronize heavily around releases and model evolutions across systems
POST-BOOKING
CRUISING
Release 465
Release 57
Incompatible
Special Meal
POST-BOOKING
CRUISING
Release 465
Release 57
Compatible
Toggle: OFF
Special Meal
POST-BOOKING
CRUISING
Release 465
Release 58
Compatible
Toggle: OFF
Toggle: OFF
Special Meal
Special Meal
POST-BOOKING
CRUISING
Release 465
Release 58
Toggle: ON
Toggle: ON
Special Meal
Special Meal
Compatible
Unresolved design problems are compensated by processes such as multi-versioning, double run, feature flags to limit impact on another team
POST-BOOKING
CRUISING
Special Meal
Interface
POST-BOOKING
CRUISING
Special Meal
Add Support
Special Meal
Interface
Interfaces between systems are heavily negotiated by teams
When the work of a stream-aligned team is systematically on the critical path of another team
Teams need to constantly synchronize their plans and priorities despite their scope of autonomy
orders
When a need for feature debugging or development arises, debates often break out to determinate which team should be in charge
Extrinsic
Coupling
Frequent
Breaking
Changes
leads to
Synchronized
Releases
leads to
Extrinsic
Coupling
Synchronized
Releases
Frequent
Breaking
Changes
Feature
Toggles
Tests Envs Proliferations
Synced
plannings
& backlogs
Feature Flags
Overuse
Multi
Versionning
Shared & Distributed
DataSets
leads to
leads to
buffered by
buffered by
Contract
Over
Negotiation
Extrinsic
Coupling
leads to
leads to
buffered by
buffered by
Feature
Toggles
Tests Envs Proliferations
Synced
plannings
& backlogs
Feature Flags
Overuse
Multi
Versionning
Shared & Distributed
DataSets
enabling
cross-team activation of
Synchronized
Releases
Frequent
Breaking
Changes
Extrinsic
Coupling
Architectural
Tension
leads to
leads to
buffered by
buffered by
Feature
Toggles
Tests Envs Proliferations
Synced
plannings
& backlogs
Feature Flags
Overuse
Multi
Versionning
Shared & Distributed
DataSets
enabling
cross-team activation of
Synchronized
Releases
Contract
Over
Negotiation
Extrinsic
Coupling
Process
Tension
Architectural
Tension
leads to
leads to
buffered by
buffered by
Feature
Toggles
Tests Envs Proliferations
Synced
plannings
& backlogs
Feature Flags
Overuse
Multi
Versionning
Shared & Distributed
DataSets
enabling
cross-team activation of
Contract
Over
Negotiation
Extrinsic
Coupling
Process
Tension
Architectural
Tension
leads to
leads to
buffered by
buffered by
Compensatory
Process
Measures
Tests Envs Proliferations
Synced
plannings
& backlogs
Feature Flags
Overuse
Multi
Versionning
Shared & Distributed
DataSets
enabling
cross-team activation of
offset
offset
Contract
Over
Negotiation
Compensatory
Organizational
Measures
Extrinsic
Coupling
Process
Tension
Architectural
Tension
leads to
leads to
buffered by
buffered by
Compensatory
Process
Measures
Tests Envs Proliferations
Synced
plannings
& backlogs
Feature Flags
Overuse
Multi
Versionning
Shared & Distributed
DataSets
offset
offset
offset
Compensatory
Organizational
Measures
Extrinsic
Coupling
Process
Tension
Architectural
Tension
leads to
leads to
buffered by
buffered by
Compensatory
Process
Measures
Tests Envs Proliferations
Synced
plannings
& backlogs
Feature Flags
Overuse
Multi
Versionning
Shared & Distributed
DataSets
offset
offset
offset
never address
Compensatory
Organizational
Measures
Extrinsic
Coupling
Process
Tension
Architectural
Tension
leads to
leads to
buffered by
buffered by
Compensatory
Process
Measures
Tests Envs Proliferations
Synced
plannings
& backlogs
Feature Flags
Overuse
Multi
Versionning
Shared & Distributed
DataSets
offset
offset
offset
never address
Business
Tensions
Architectural
Tensions
reflects as
Process
Tensions
arise by trying to compensate
produce
Extrinsic
Coupling
Organizational Tensions
structure
produce
Post-Booking Team creation
Model Fragmentation
Structure eats Strategy
Jan Bosch - 2017
Model Tension
Heuristics
Model
Tension
spot
prevents
Model
Entropy
prevents
Model
Tension
Model Tension
Heuristics
spot
System
Entropy
... but are only heuristics. Not surefire rules.
SHOPPING
ORDER
DELIVERY
...do not always reflect tensions.
This is not a fragmented lifecyle
In the code
In the code
In conversations
In the code
In conversations
In processes
In the code
In countermeasures
In conversations
In processes
Gather a body of evidences revolving around:
parts of the model
parts of the organization
specific processes
Gather a body of evidences revolving around:
parts of the model
parts of the organization
specific processes
parts of the socio-technical system
Once you have a body of evidences, act on the signals by running limited, safe to fail experiments to test your heuristics
POST-BOOKING
CRUISING
Exchange
Cancellation
Search
Booking
POST-BOOKING
CRUISING
Exchange
Cancellation
Search
Booking
POST-BOOKING
CRUISING
Booking
Exchange
Cancellation
Search
Shared
Kernel
POST-BOOKING
CRUISING
Booking
Exchange
Cancellation
Search
SK
POST-BOOKING
CRUISING
Booking
Exchange
Cancellation
Search
BOOKING
CRUISING
Booking
Exchange
Cancellation
Search
@julientopcu.com
@josianchevalier.bsky.social