Model Tension
Heuristics
Preventing Accidental Design Debt
@julientopcu.com

@josianchevalier.bsky.social















CHAPTER I
When model exploration
Whirlpool
turns into model sinking
TODo
Current Quest
Done

Overlapping ships can be selected
Bug

[Paid Option] Wireless pidgin connexion
Feature
JIRA



Ships departing in the past appear in Search

Bug

Add cancellation insurance

Feature
TODo
Current Quest
Done

Overlapping ships can be selected
Bug

[Paid Option] Wireless pidgin connexion
Feature
JIRA



Ships departing in the past appear in Search

Bug

Add cancellation insurance

Feature

{
"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
Searching




drives
Exploration
&
Experimentation
Business Need
shape
Model
needs to account for new
Selecting




drives
Exploration
&
Experimentation
Business Need
shape
Model
needs to account for new
Selecting




drives
Exploration
&
Experimentation
Business Need
shape
Model
needs to account for new
Selecting




drives
Exploration
&
Experimentation
Business Need
shape
Model
needs to account for new
Booking




drives
Exploration
&
Experimentation
Business Need
shape
Model
Model Exploration
Whirlpool
needs to account for new




drives
Exploration
&
Experimentation
Business Need
expand
Model
needs to account for new














The Mighty
Big Ball Of Mud




drives
Exploration
&
Experimentation
Business Need
Model
needs to account for new
expand
Model Exploration
Whirlpool




drives
Exploration
&
Experimentation
Business Need
Model
BBOM
needs to account for new
collapses into
expand
Model
Sinking
Accidental Design Debt:
the invisible engine of BBOMs


















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";
+ }
}
}
Fix Booking history regression #2097
Josian merged 10 commits into
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";
}
}
}
Fix "Previous Days Ships" feature #2099
Josian merged 57 commits into
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




No model is Inherently extensible !




Exploration
&
Experimentation
Business Need
Model
needs to account for new




Business Need
Model
Model Exploration
Whirlpool
challenged by new
Not a confirmation bias
Exploration
&
Experimentation
needs to account for new




Business Need
by shaping
Model
New Model
may
Depreciate
Exploration
&
Experimentation
challenged by new
One does not simply
challenge the model














Accidental
Design Debt
The Ruthless




Accidental Design Debt
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,
from Decisions that inadvertently




Features taking longer to develop?
Model
Accidental
Design Debt
generates through
misguided decisions
manifests
as complexity in




Features taking longer to develop?
Simple change causing widespread model updates?
Model
Accidental
Design Debt
generates through
misguided decisions
manifests
as complexity in




Features taking longer to develop?
More bugs in unrelated parts of the model?
Simple change causing widespread model updates?
Model
Accidental
Design Debt
generates through
misguided decisions
manifests
as complexity in




Model
Accidental
Design Debt
Model
Entropy
produces through
accumulation
Features taking longer to develop?
More bugs in unrelated parts of the model?
Simple change causing widespread model updates?
generates through
misguided decisions
manifests
as complexity in














The Unyielding
Model entropy
Features taking longer to develop?
More bugs in unrelated parts of the model?
Simple change causing widespread model updates?




Model entropy
High Entropy
Design effort is dissipated on navigating accidental complexity
Degree of design disorder
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
Spork Effect:
unveiling the roots of model fragility and rigidity















{
"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



Booking
Search
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
Booking
Search
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
Search
Booking
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



Booking
Search
Intrinsic
Coupling
Accidental
Design Debt
Responsibilities
Coupling
may induce
manifests as
strong
Spork effect




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
{}




Booking
Search
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
Booking
Search
Booking




Search
Booking
Distinct Models!
Search
Booking
Booking




Search
Booking
Distinct Models!
Search
Booking
Booking




"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




Suffering...




Suffering...




Suffering...
Model tension as signals 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














ModelTension
The Fearsome




ModelTension
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




Conceptual Integrity
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




Booking
Search
{
"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
Booking
Search
Booking
Selection
Selection
Model
Tension














Crystallized
ModelTension
The Unstoppable




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






ModelTension
as signal of accidental design debt
The Fearsome








Entropy
Tensions
Resolution
Accidental
Design
Debt
Model Rigidity
Entropy
Accidental
Design
Debt










Exploring (intrinsic)
model tensions heuristics
















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














Linguistic Tension
Salience Bias
Salience Bias
Linguistic Tension
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














Life cycle Tension
Implicit Life cycle













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(){/**/}
}














CHAPTER 2
When model sclerosis
turns into model
Fragmentation












CRUISING
POST-BOOKING




Text
works around
Sclerosed
Model
Outgrowth
Model












POST-BOOKING
CRUISING
Search
Exchange
Cancellation
Search
Fare
Booking
Selection












Selection
Booking
POST-BOOKING
CRUISING
Search
Exchange
Cancellation












Selection
Booking
POST-BOOKING
CRUISING
Search
Exchange
Cancellation
Search
Booking
Selection












Selection
Booking
Search
Exchange
Cancellation
noDepartureInThePast()
noDepartureInThePast()
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
POST-BOOKING
CRUISING












Selection
Booking
Search
Exchange
Cancellation
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange












Selection
Booking
Search
Exchange
Cancellation
Cancellation
Booking
Exchanged
Booking
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Cancelled
Booking












Selection
Booking
Search
Exchange
Cancellation
POST-BOOKING
CRUISING
Booking
Exchanged
Booking
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Cancelled
Booking
Completed
Booking












Selection
Booking
Search
Exchange
Cancellation
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Created
Completed
Exchanged
Cancelled












Selection
Booking
Search
Exchange
Cancellation
POST-BOOKING
CRUISING
Booking Lifecycle
Created
Completed
Exchanged
Cancelled
POST-BOOKING
CRUISING












Booking
Exchange
POST-BOOKING
CRUISING
Seat Preferences
Seat Preferences
Cancellation












Selection
Booking
Search
Exchange
Cancellation
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation
Extrinsic Coupling












noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation
POST-BOOKING
CRUISING
Fragmented Model












POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation












Selection
Booking
Search
Exchange
Cancellation
POST-BOOKING
CRUISING
noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
Booking
Exchange
Cancellation












Selection
Booking
Search
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 fragmentation (extrinsic coupling):
the insidious builder of distributed BBOMs


















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
Exploring (extrinsic)
model tensions heuristics




















???














Business Tension
Business Capability Discrepancy
Business Tension
Business Capability Discrepancy
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
Fragmented Model












Booking Lifecycle
Created
Completed
Exchanged
Cancelled
POST-BOOKING
CRUISING












chooseSeatPreferences()














Architectural Tension
Fragmented Lifecycle
When separate bounded-contexts handle different states of the same aggregate, while sharing many concepts, personae, and rules.
Architectural Tension
Fragmented Lifecycle




POST-BOOKING
CRUISING












MOBILE APP
Booking
Exchange
Cancellation
Determining current booking status
check booking exists
check if exchange or cancellation exists
Created
Completed
Exchanged
Cancelled
POST-BOOKING
CRUISING












MOBILE APP
check if exchanged/cancelled
check booking exists
Determining current booking status
BOOKING














Architectural Tension
Distributed States
Understanding the state of a concept requires consolidating data from multiple bounded-contexts
Architectural Tension
Distributed States




noDepartureInThePast()
noDepartureInThePast()
noScheduleOverlap()
noScheduleOverlap()
POST-BOOKING
CRUISING












Selection
Booking
Search
Exchange
Cancellation














Architectural Tension
Business Logic Leaks
Behaviors, invariants, lifecycles, etc. are scattered across bounded-contexts
Architectural Tension
Business Logic Leaks




POST-BOOKING
CRUISING












New rule for enabling them to a user
Ancillaries
Ancillaries
Architectural Tension
Frequent
Breaking Changes




Every model evolution breaks collaborating bounded-contexts.
Architectural Tension
Frequent
Breaking Changes




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














Process Tension
Synchronized Releases
Necessity to synchronize heavily around releases and model evolutions across systems
Process Tension
Synchronized Releases




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














Process Tension
Compensatory Countermeasures
Unresolved design problems are compensated by processes such as multi-versioning, double run, feature flags to limit impact on another team
Process Tension
Compensatory Countermeasures




POST-BOOKING
CRUISING












Special Meal
Interface
POST-BOOKING
CRUISING












Special Meal
Add Support
Special Meal
Interface














Organizational Tension
Contract
Over-Negotiation
Interfaces between systems are heavily negotiated by teams
Organizational Tension
Contract
Over-Negotiation




TODo
Current Quest
Done
JIRA


???

Add a
Konami Code
Bug


Bug

Special meals
cancellation
Bug


Handle cancellation insurance

Overlapping ships can be selected

Deprecated seat preference shown during exchange
Bug

Feature
Feature
[Paid Option] Wireless pidgin connexion














Organizational Tension
Backlog Takeover
When the work of a stream-aligned team is systematically on the critical path of another team
Organizational Tension
Backlog Takeover


















Organizational Tension
Inter-Team
Work Coupling
Teams need to constantly synchronize their plans and priorities despite their scope of autonomy
Organizational Tension
Inter-Team Work Coupling




TODo
Current Quest
Done
JIRA




Bug
Overlapping ships can be selected

Deprecated seat preference shown during exchange
Bug

Feature
Feature
[Paid Option] Wireless pidgin connexion


Handle cancellation insurance

Special meals
cancellation
Bug

???
TODo
Current Quest
Done



Inconsistent
special meal
orders
Bug

???
JIRA














Organizational Tension
Unclear
Ownership
Organizational Tension
Unclear Ownership
When a need for feature debugging or development arises, debates often break out to determinate which team should be in charge




Extrinsic coupling signals are mainly socio-technical


















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
Organizational Entropy




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
B.A.P.O Model




Extrinsic coupling Signals are mainly socio-technical




Intrinsic Coupling
Model Tension
Heuristics
Model
Tension
spot
prevents
Model
Entropy




Extrinsic Coupling
prevents
Model
Tension
Model Tension
Heuristics
spot
System
Entropy
Conclusion


















... but are only heuristics. Not surefire rules.
Model Tension Heuristics
prevent accidental design debt
Signals












Cart
SHOPPING
Order
ORDER
Parcel
DELIVERY
...do not always reflect tensions.
This is not a fragmented lifecyle




Look out for signals
In the code




Look out for signals
In the code
In conversations




Look out for signals
In the code
In conversations
In processes




Look out for signals
In the code
In countermeasures
In conversations
In processes




Build a Case
Gather a body of evidences revolving around:
parts of the model
parts of the organization
specific processes




Build a Case
Gather a body of evidences revolving around:
parts of the model
parts of the organization
specific processes
parts of the socio-technical system




Experiment on the 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












Selection
Booking
Search
Cancellation
Search
Booking
POST-BOOKING
CRUISING
Exchange












Selection
Booking
Search
Cancellation
Search
Booking
POST-BOOKING
CRUISING
Booking
Exchange












Selection
Booking
Search
Cancellation
Search
Shared
Kernel
Model Mitosis
POST-BOOKING
CRUISING
Booking
Exchange












Selection
Booking
Search
Cancellation
Search
SK
Model Mitosis
POST-BOOKING
CRUISING
Booking
Exchange












Selection
Booking
Search
Cancellation
Search
Model Mitosis
BOOKING
CRUISING
Booking
Exchange












Selection
Booking
Search
Cancellation
Search







SHODO
Model Tension
Heuristics
Preventing Accidental Design Debt


Julien Topçu
Josian Chevalier
Tech Coach
CTO / Coach
@julientopcu.com
@josianchevalier.bsky.social

Model Tension Heuristics: Preventing Accidental Design Debt
By Julien Topçu
Model Tension Heuristics: Preventing Accidental Design Debt
”All models are wrong, but some are useful.” This insight from George Box has become a cornerstone of software modeling. While exploration and experimentation are essential for shaping models, it is often challenging to know when a model is wrong or, worse, when an existing model has become obsolete and is no longer as useful. As a result, unsuitable models may be still used for a long time, leading to different issues not always identified as forms of design debt. These include the spork effect (intrinsic coupling), model fragmentation (extrinsic coupling), and model sclerosis, which ultimately contribute to entropy or the "Big Ball of Mud" phenomenon. These problems arise from undetected and unresolved model tensions. In this talk, we will explore what model tensions are, the complications they cause and why they occur. We will also present a set of heuristics to identify and address different kind of model tensions.
- 280