FOR CONTRACT 3 RISK MANAGER CONTRACT PASSES ON TO 4 CONTRACT VO TES CHECKS CALCU LATES 5 6 7 CALCU- LATES TO 8 2 CAR CREDIT RATING INSTALLMENT CAR RESALE VALUE CONTRACT
WISH FOR 1 SALES- PERSON SIGNS TO GIVES FOR CONTRACT 3 RISK MANAGER CONTRACT PASSES ON TO 4 CONTRACT VOTES CHECKS CALCULATES 5 6 7 CALCU- LATES TO 8 2 CAR CREDIT RATING INSTALLMENT CAR RESALE VALUE CONTRACT SALES RISK ASSESSMENT
§ Points of no Return (persistent work results) § Boundaries within the process § State changes, that affect the “nature” of a work object (e.g. “contract legally binding,” “shopping cart ordered”) § Departments of the organisation § Contextual language § Different usage of work objects § Triggers at different points in time § NOT: by work objects! HOW TO CUT THE DOMAIN? Foto: Wikipedia/PD-ScottForesman
CONTRACT 3 RISK MANAGER CONTRACT PASSES ON TO 4 CONTRACT VO TES CHECKS CALCU LATES 5 6 7 CALCU- LATES TO 8 2 CAR CREDIT RATING INSTALLMENT CAR RESALE VALUE CONTRACT SALES RISK ASSESSMENT
CONTRACT 3 RISK MANAGER CONTRACT PASSES ON TO 4 CONTRACT VO TES CHECKS CALCU LATES 5 6 7 CALCU- LATES TO 8 2 CAR CREDIT RATING INSTALLMENT CAR RESALE VALUE CONTRACT SALES Group the sentences • Boundary around activites and work objects • Keep actors outside boundaries Give a name to the group RISK ASSESSMENT
informa$on flow 3) Different triggers ($me vs. on demand) 4) Ac$vi$es suppor$ng something that is not in the picture 5) Difference in language 6) Different use of the same thing Ask your domain experts!
context map 2) How is it? 1) Architecture Analysis 2) As-is context map 3) How to move the “is” to the “ideal”? 1) Compare 2) Create List of Refactorings 4) Do the move 1) Extract a suppor$ng domain to learn 2) Then extract core(s)
§ Fachexperten verstehen keine Begriffe zu technischen Umsetzungen § Fachexperten sprechen den Jargon ihrer Domäne, der für Außenstehende wiederum schwer verständlich sein kann è Eine gemeinsame Sprache ist notwendig! § Welche soll es sein? § Die der Entwickler? § Die der Fachexperten? § Etwas dazwischen? èPrinzip von DDD: „Verwende eine Sprache die auf dem Domänenmodell basiert“
§ Verwende die gemeinsame Sprache in.. § der Kommunikation § mündlich § schriftlich § grafisch § im Code § Im Grunde genommen überall è Deswegen nennt sich die Sprache allgegenwärtig.
NICHT EINFACH AUF § Es braucht Wochen bis Monate... § harter Arbeit § und scharfem Fokus § … um die Schlüsselkonzepte offenzulegen. § Die ersten Wörter einer allgegenwärtigen Sprache kommen üblicherweise direkt aus der Domäne § Im Laufe der Entwicklung werden neue Begriffe definiert und hinzugefügt
LEBENDIG § Experimentiere mit alternativen Ausdrucksformen § Das Modell und die Sprache entwickeln sich weiter § Überarbeite dann den Code § Benenne Klassen, Methoden, Module § Entspreche dem neuen Modell § Eine Sprache will gesprochen werden: § Beseitige Unklarheiten durch Konversation
Fachsprache der Benutzer/Ubiquitous Language •bereits existierende Begriffe •rekonstruierte Begriffe •neue Begriffsbildungen • Wer tut was damit wozu? § Kernkonzepte § Wichtiger am Anfang des Projektes § Oft Wegwerfprodukt
BEISPIELDOMÄNE SCHACH SPIELER EINE VON ZWEI PERSONEN, DIE FIGUREN AUF DEM BRETT BEWEGT. KÖNIG EINE EINMALIGE FIGUR, DIE NUR EIN FELD PRO ZUG BEWEGT WERDEN KANN. WENN DER KÖNIG SCHACHMATT GESETZT WURDE, IST DAS SPIEL VORBEI. Beschreibung Begriff Oberbegriff Unterscheidungs- merkmale
Team spricht eine eigene Sprache § è Familiensprache, Dialekt § Gleiche Begriffe können (leicht) unterschiedliche Bedeutung haben § Sprache und Modell sind eng gekoppelt JEDER BOUNDED CONTEXT HAT SEINE EIGENE UBIQUITOUS LANGUAGE TIEFEN- MESSUNG PEIL- SCHIFF TIEFEN- ZAHL MARKIER- UNG PEIL- PLAN TIEFEN- ZAHL MANÖVERPLANUNG
CONTEXT HAT SEIN EIGENES GLOSSAR TIEFENZAHL TIEFE AN EINEM BESTIMMTEN ORT UNTER BERÜCKSICHTIGUNG VON EBBE UND FLUT TIEFENZAHL PER ECHOLOT GEMESSENE TIEFE BEI NORMALNULL MANÖVER- PLANUNG TIEFENMESSUNG
DAS KINO Saalplan Vorstellung Reservierungs- nummer Kinokarte Saalplanstapel Liste der Reservierungs- nummern Gesamtablauf- plan Tagesablaufplan Film Platz/Sitzplatz Saal/Kinosaal Werbung Veranstaltung Eisverkauf Vorführung ? ?
FÜR DAS KINO Saalplan Vorstellung Reservierungs- nummer Kinokarte Saalplanstapel Liste der Reservierungs- nummern Gesamtablauf- plan Tagesablaufplan Film Platz/Sitzplatz Saal/Kinosaal Werbung Veranstaltung Eisverkauf Vorführung Kartenverkauf Ablaufplanung
BOUNDED CONTEXT »KARTENVERKAUF« Saalplan Vorstellung Reservierungs- nummer Kinokarte Saalplanstapel Liste der Reservierungs- nummern Gesamtablauf- plan Tagesablaufplan Film Platz/Sitzplatz Saal/Kinosaal Werbung Veranstaltung Eisverkauf Vorführung Kartenverkauf Ablaufplanung
BOUNDED CONTEXT »KARTENVERKAUF« Saalplan Vorstellung Reservierungs- nummer Kinokarte Saalplanstapel Liste der Reservierungs- nummern Film Platz/Sitzplatz Saal/Kinosaal
BOUNDED CONTEXT »KARTENVERKAUF« Saalplan Vorstellung Reservierungs- nummer Kinokarte Saalplanstapel Liste der Reservierungs- nummern Film Platz/Sitzplatz Saal/Kinosaal
BOUNDED CONTEXT »KARTENVERKAUF« Saalplan Vorstellung Reservierungs- nummer Kinokarte Saalplanstapel Liste der Reservierungs- nummern Film Platz/Sitzplatz Saal/Kinosaal
die Fachexperten (domain experts) § Sie wissen § Worum es bei ihrer Arbeit geht § Wo die Software sie unterstützen kann èWer die Fachlichkeit nicht versteht, kann ihr nicht helfen èSoftwareentwickler und Fachexperten bilden zusammen das Team WIE LERNEN WIR DIE FACHLICHKEIT? Foto: Brandon Raile/Wikipedia
WIR AN DAS WISSEN DER FACHEXPERTEN? Techniken: § Interviews § Szenarios § Event Storming § Domain Storytelling Das wollen wir extrahieren! Foto: Brandon Raile/Wikipedia Fach- wissen
ANWENDERN UND PRODUCT OWNERS “Georgia?” by The Library of Congress, flickr.com • Teilnehmer aus verschiedenen Bereichen (Business, IT, Management, …) • ein Moderator für den Workshop à direktes Feedback von allen Beteiligten
CONTRACT 3 RISIK MANAGER CONTRACT PASSES ON TO 4 CONTRACT VOTES CHECKS CALCULATES 5 6 7 CALCU- LATES TO 8 2 CAR CREDIT RATING INSTALLMENT CAR RESALE VALUE CONTRACT
ARCHITECTURE § User interface layer § Receives input and user commands and presents information. § Application layer (Fowler: Service Layer) § Describes and coordinates application processes. § Domain layer § Represents the business domain logic. § Infrastructure layer § Provides technical services such as persistence or communication with other systems. User Interface Application Domain Infrastructure
– CRITIQUE MANEUVER PLANNING § Domain layer is linked to the database layer § Technology “bubbles up” into domain layer § No clean separation of domain & technology Alistair Cockburn Foto: Fotograf Dennis Hamilton/Alistair Cockburn/flickr/CC BY 2.0 User Interface Application Domain Infrastructure
– WHAT IS STRIKING? insurance policy vacation request building activity purchase contract order zip code GPS coordinate IBAN container number IATA code
Objects of a domain that the user works with. § The results of the work are reflected by their state! § Can be composed of other entities. § Have an (immutable) identity. § Maintain their own consistency and integrity! § Have a clearly defined life cycle. § Have a (mostly mutable) state. § Describe their state by value objects. § Synonyms: Business Object / Domain Object / Work Material § NOT TO BE confused with the term “ENTITY” from Entity-Relationship-Model! Foto: Bundesrepublik Deutschland/Wikipedia/PD Germany Foto: Kaz/pixabay/CC0 Foto: Thomas G./Wikipedia/CC BY-SA 3.0 Foto: OpenClipart-Vectors/pixabay/CC0
§ Value objects are symbols for values of a certain type in the domain. § Symbolize the same value if they are equal. § They are not edited by the user and cannot be modified. § Can be calculated (from other value objects), if necessary. § Can consist of other value objects, but never of entities! ValueObject 2.5 ValueObject ValueObject ValueObject ValueObject two and a half
Are the access points to the aggregates! § Encapsulate the technical details of the technical infrastructure layer ... § ... and map external data to entities and value objects. § Store aggregates (e. g. in databases). Repository Aggregate Aggregate Aggregate Aggregate Aggregate
Aggregates are entities that are relevant in themselves (and not just as part of another entity). § Protect the consistency and integrity of their inner entities. § They don't necessarily have to hide them to do this! § Always have a designated entity as an entry point (root). § Usually stored. § As a whole! § Not necessarily in a relational way! Root Entity VO VO VO Aggregate
ACCOUNT—FIRST DRAFT @Entity public class Account { private int _balance; public int getBalance() { return _balance; } public void setBalance(int balance) { _balance = balance; } } ✘ Bad: The account balance can be set to any value
ACCOUNT—SECOND VERSION @Entity public class Account { private int _balance; public int balance() { return _balance; } public void deposit(int amount) { _balance += amount; } public void withdraw(int amount) { _balance -= amount; } } Better: Operations with domain-specific behavior and names
ACCOUNT—THIRD VERSION @Entity public class Account { private int _balance; public int balance() { return _balance; } public void deposit(int amount) { _balance += amount; } public void withdraw(int amount) { if (amount > balance()) { throw new IllegalArgumentException("Amount too big"); } _balance -= amount; } Even better: Preconditions can be checked
ACCOUNT—DESIGN BY CONTRACT USING “ASSERT” @Entity public class Account { // ... public void withdraw(int amount) { assert amount <= balance(); _balance -= amount; } } Assertion using keyword assert
ACCOUNT—DESIGN BY CONTRACT USING “VALID4J” import static org.valid4j.Assertive.*; @Entity public class Account { // ... public void withdraw(int amount) { require(amount <= balance()); _balance -= amount; } } Hamcrest matchers can be used
@Entity public class Account { // ... public void withdraw(int amount) { assert amount <= balance(); _balance -= amount; } } Can I withdraw a negative amount? In EUR or GBP or…?
TYPE @ValueObject public class Amount { private final int _amount; private final Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } }
TYPE @ValueObject public class Amount { private final int _amount; private final Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } public Amount add(Amount otherAmount) { return Amount.of(_amount + otherAmount._amount, _currency); } } The new value object type behaves correctly
ACCOUNT—FOURTH VERSION @Entity public class Account { private Amount _balance; public Amount balance() { return _balance; } public void deposit(Amount amount) { _balance = _balance.add(amount); } public void withdraw(Amount amount) { assert amount.isLessOrEqual(balance()); _balance = _balance.subtract(amount); } } Now we can use the amount type
ACCOUNT—EVENT SOURCED @Entity public class Account { private List<FinancialTransaction> _transactions; public Amount balance() { // sum up _transactions } public void deposit(Amount amount) { _transactions.append(new Deposition(amount)); } public void withdraw(Amount amount) { assert amount.hasSameCurrency(balance()); assert amount.isLessOrEqual(balance()); _transactions.append(new Withdrawal(amount)); } } History instead of current status
ENTITIES § In Java, every object has an identity § Namely its reference. § It's a technical identity. § An entity has a identity from a domain perspective § Different technical objects can represent the same entity § In different processes: e.g. on the client / on the server § When they are stored and later read from the database. § The identity can be implemented in two ways: § explicitly (e.g. IBAN) by using a unique key the user is aware of § implicitly (e.g. UUID / GUID) by using a technical key hidden from the user ?
ACCOUNT—WITH EXPLICIT IDENTITY @Entity public class Account { @Identity private final IBAN _iban; public Account(IBAN iban) { _iban = iban; } @Override public boolean equals(Object other) { return _iban.equals(((Account)other)._iban); } // ... } The identity is immutable The key is of course a value object The implementation of equals() is based on the identity
A NEW IDENTITY BE CREATED? § Database § Pro: Central § Can sometimes be created “lazily” § A system-wide value type factory § Domain-specific keys known to the user § Often implemented by the repository of a corresponding aggregate type the key is used for § A UUID / GUID generator § Pro: can be generated independently on any server or client § Con: String in a special format
DOMAIN SERVICE @Service public class CreditTransferService { public void transfer(Account source, Account destination, Amount amount) { source.withdraw(amount); destination.deposit(amount); } } No fields à stateless
CONTEXT HAS ITS OWN GLOSSARY—REVISITED DEPTH DEPTH AT A SPECIFIC GEO POSITION CONSIDERING THE CURRENT TIDAL LEVEL DEPTH DEPTH BELOW SEA LEVEL MEASURED BY ECHO SOUNDER MANEUVER PLANNING DEPTH MEASUREMENT
CONTEXT HAS ITS OWN IMPLEMENTATION public struct Depth { public Depth( int depthBelowSeaLevel, int tideLevel) { // ... } } MANEUVER PLANNING DEPTH MEASUREMENT public class Depth { public Depth( int centimeter, LocalDate soundingDate) { // ... } }
ACCOUNT—SECOND VERSION [Entity] public class Account { public int Balance { get; private set; } public void Deposit(int amount) { Balance += amount; } public void Withdraw(int amount) { Balance -= amount; } } Better: Operations with domain-specific behavior and names
ACCOUNT—THIRD VERSION [Entity] public class Account { public int Balance { get; private set; } public void Deposit(int amount) { Balance += amount; } public void Withdraw(int amount) { if (amount > balance()) { throw new ArgumentException("Amount too big"); } Balance -= amount; } Even better: Preconditions can be checked
ACCOUNT—DESIGN BY CONTRACT USING “ASSERT” using static System.Diagnostics.Debug; [Entity] public class Account { // ... public void Withdraw(int amount) { Assert(amount <= balance()); Balance -= amount; } } Assertion using Debug.Assert
using static System.Diagnostics.Debug; [Entity] public class Account { // ... public void Withdraw(int amount) { Assert(amount <= balance()); Balance -= amount; } } Can I withdraw a negative amount? In EUR or GBP or…?
TYPE [ValueObject] public class Amount { private readonly int _amount; private readonly Currency _currency; // ... public override bool Equals(object other) => other is Amount && _amount == ((Amount) other)._amount && _currency.Equals(((Amount) other)._currency); }
TYPE [ValueObject] public class Amount { private readonly int _amount; private readonly Currency _currency; // ... public static bool operator ==(Amount a, Amount b) => a.Equals(b); }
TYPE [ValueObject] public class Amount { private readonly int _amount; private readonly Currency _currency; // ... public static bool operator ==(Amount a, SignDate b) => a.Equals(b); public static bool operator !=(Amount a, SignDate b) => !a.Equals(b); }
TYPE [ValueObject] public struct Amount { private readonly int _amount; private readonly Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } // Equals() does not have to be overridden }
TYPE [ValueObject] public struct Amount { private readonly int _amount; private readonly Currency _currency; public Amount(int amount, Currency currency) { _amount = amount; _currency = currency; } // but the operators have to be overridden }
TYPE—C# 9 AND HIGHER [ValueObject] public record Amount(int amount, Currency currency); // automatically readonly // Equals(), GetHashCode(), ToString(), operators // do not have to be overloaded and work as expected
TYPE [ValueObject] public readonly record struct Amount(int _amount, Currency _currency) { public Amount Add(Amount otherAmount) { return Amount.of(_amount + otherAmount._amount, _currency); } } The new value object type behaves correctly
ACCOUNT—FOURTH VERSION [Entity] public class Account { public Amount Balance { get; private set; } public void Deposit(Amount amount) { Balance += amount; } public void Withdraw(Amount amount) { Assert(amount <= Balance); Balance -= amount; } } Now we can use the amount type
ACCOUNT—EVENT SOURCED @Entity public class Account { private List<FinancialTransaction> _transactions; public Amount balance() { // sum up _transactions } public void deposit(Amount amount) { _transactions.append(new Deposition(amount)); } public void withdraw(Amount amount) { assert amount.hasSameCurrency(balance()); assert amount.isLessOrEqual(balance()); _transactions.append(new Withdrawal(amount)); } } History instead of current status
ENTITIES § In Java, every object has an identity § Namely its reference. § It's a technical identity. § An entity has a identity from a domain perspective § Different technical objects can represent the same entity § In different processes: e.g. on the client / on the server § When they are stored and later read from the database. § The identity can be implemented in two ways: § explicitly (e.g. IBAN) by using a unique key the user is aware of § implicitly (e.g. UUID / GUID) by using a technical key hidden from the user ?
ACCOUNT—WITH EXPLICIT IDENTITY @Entity public class Account { @Identity private final IBAN _iban; public Account(IBAN iban) { _iban = iban; } @Override public boolean equals(Object other) { return _iban.equals(((Account)other)._iban); } // ... } The identity is immutable The key is of course a value object The implementation of equals() is based on the identity
A NEW IDENTITY BE CREATED? § Database § Pro: Central § Can sometimes be created “lazily” § A system-wide value type factory § Domain-specific keys known to the user § Often implemented by the repository of a corresponding aggregate type the key is used for § A UUID / GUID generator § Pro: can be generated independently on any server or client § Con: String in a special format
DOMAIN SERVICE @Service public class CreditTransferService { public void transfer(Account source, Account destination, Amount amount) { source.withdraw(amount); destination.deposit(amount); } } No fields à stateless
CONTEXT HAS ITS OWN GLOSSARY—REVISITED DEPTH DEPTH AT A SPECIFIC GEO POSITION CONSIDERING THE CURRENT TIDAL LEVEL DEPTH DEPTH BELOW SEA LEVEL MEASURED BY ECHO SOUNDER MANEUVER PLANNING DEPTH MEASUREMENT
CONTEXT HAS ITS OWN IMPLEMENTATION public struct Depth { public Depth( int depthBelowSeaLevel, int tideLevel) { // ... } } MANEUVER PLANNING DEPTH MEASUREMENT public class Depth { public Depth( int centimeter, LocalDate soundingDate) { // ... } }
ACCOUNT—FIRST DRAFT #[Entity] class Account { private int $balance; public function getBalance(): int { return $this->balance; } public function setBalance(int $balance): void { $this-> balance = $balance; } } ✘ Bad: The account balance can be set to any value
ACCOUNT—SECOND VERSION #[Entity] class Account { private int $balance; public funtion balance(): int { return $this->balance; } public function deposit(int amount): void { $this->balance += amount; } public function withdraw(int amount): void { $this->balance -= amount; } } Better: Operations with domain-specific behavior and names
ACCOUNT—THIRD VERSION Even better: Preconditions can be checked #[Entity] class Account { private int $balance; public funtion balance(): int { return $this->balance; } public function deposit(int amount): void { $this->balance += amount; } public function withdraw(int amount): void { assert(amount <= $this->balance()); $this->balance -= amount; }
#[Entity] class Account { // ... public function withdraw(int $amount): void { assert($amount <= $this->balance()); $this->balance -= $amount; } } Can I withdraw a negative amount? In EUR or GBP or…?
TYPE—PHP 8.1 AND HIGHER #[ValueObject] class Amount { public function __construct( private readonly int $amount, private readonly Currency $currency ) {} }
TYPE #[ValueObject] readonly class Amount { private function __construct( private int $amount, private Currency $currency ) {} public static function of(int $amount, Currency $currency): Amount { return new Amount($amount, $currency); } }
TYPE #[ValueObject] readonly class Amount { public function __construct( private int $amount; private Currency $currency; ) {} public function equals(Amount $other): bool { return $this->amount === $other->amount && $this->currency->equals($other->currency); } }
TYPE #[ValueObject] readonly class Amount { public function __construct( private int $amount, private Currency $currency ) {} public function add(Amount $otherAmount): Amount { return Amount.of($this->amount + $otherAmount->amount, $this->currency); } } The new value object type behaves correctly
TYPE #[ValueObject] readonly class Amount { public function __construct( private int $amount, private Currency $currency ) {} public function add(Amount $otherAmount): Amount { assert($this->hasSameCurrencyAs($otherAmount)); return Amount.of($this->amount + $otherAmount->amount, $this->currency); } public function hasSameCurrencyAs(Amount $otherAmount): bool { return $this->currency === $otherAmount->currency; } } ... and contracts ensure the correct currency
ACCOUNT—FOURTH VERSION Now we can use the amount type #[Entity] class Account { private Amount $balance; public function balance(): Amount { return $this->balance; } public function deposit(Amount $amount): void { $this->balance = $this->balance->add($amount); } public function withdraw(Amount $amount): void { assert($amount.isLessOrEqual($this->balance()); $this->balance = $this->balance->subtract($amount); }
Account { private Amount $balance; public function balance(): Amount { return $this->balance; } public function deposit(Amount $amount): void { $this->balance = $this->balance->add($amount); } public function withdraw(Amount $amount): void { assert $amount.hasSameCurrency($this->balance()); assert($amount.isLessOrEqual($this->balance()); $this->balance = $this->balance->subtract($amount); A BANK ACCOUNT—FOURTH VERSION ... and define new contracts
ACCOUNT—EVENT SOURCED @Entity public class Account { private List<FinancialTransaction> _transactions; public Amount balance() { // _transactions aufsummieren } public void deposit(Amount amount) { _transactions.append(new Deposition(amount)); } public void withdraw(Amount amount) { assert amount.hasSameCurrency(balance()); assert amount.isLessOrEqual(balance()); _transactions.append(new Withdrawal(amount)); } } History instead of current status
ENTITIES § In Java, every object has an identity § Namely its reference. § It's a technical identity. § An entity has a identity from a domain perspective § Different technical objects can represent the same entity § In different processes: e.g. on the client / on the server § When they are stored and later read from the database. § The identity can be implemented in two ways: § explicitly (e.g. IBAN) by using a unique key the user is aware of § implicitly (e.g. UUID / GUID) by using a technical key hidden from the user ?
ACCOUNT—USING A UNIQUE KEY @Entity public class Account { @Identity private final IBAN _iban; public Account(IBAN iban) { _iban = iban; } @Override public boolean equals(Object other) { return _iban.equals(((Account)other)._iban); } // ... } The identity is immutable The key is of course a value object The implementation of equals () is based on the unique key
A NEW UNIQUE KEY BE CREATED? § Database § Pro: Central § Can sometimes be created “lazily” § A system-wide value type factory § Domain-specific keys known to the user § Often implemented by the repository of a corresponding aggregate type the key is used for § A UUID / GUID generator § Pro: can be generated independently on any server or client § Con: String in a special format
DOMAIN SERVICE @Service public class CreditTransferService { public void transfer(Account source, account destination, Amount amount) { source.withdraw(amount); destination.deposit(amount); } } No fields à stateless
CONTEXT HAS ITS OWN GLOSSARY—REVISITED DEPTH DEPTH AT A SPECIFIC GEO POSITION CONSIDERING THE CURRENT TIDAL LEVEL DEPTH DEPTH BELOW SEA LEVEL MEASURED BY ECHO SOUNDER MANEUVER- PLANNING SOUNDING SERVICE
CONTEXT HAS ITS OWN IMPLEMENTATION public struct Depth { public Depth( int depthBelowSeaLevel, int tideLevel) { // ... } } MANEUVER- PLANNING SOUNDING SERVICE public class Depth { public Depth( int centimeter, LocalDate soundingDate) { // ... } }
“Hexagonal Architecture.” January 4, 2005. https://alistair.cockburn.us/hexagonal-architecture/. Conway, Melvin E. “How Do Committees Invent?” Datamation 14, no. 5 (April 1968): 28–31. Evans, Eric. Domain-Driven Design: Tackling Complexity in the Heart of Software. Boston: Addison-Wesley, 2004. Foote, Brian and Joseph Yoder. “Big Ball of Mud.” PLoP ’97, Monticello, IL, September 1997. Fowler, Martin. Patterns of Enterprise Application Architecture. Boston: Addison- Wesley, 2003. Fowler, Martin. “Strangler Fig Application.” Bliki, June 29, 2004. Hofer, Stefan and Henning Schwentner. Domain Storytelling: a Collaborative, Visual, and Agile Way to Develop Domain-Driven Software. Boston: Addison-Wesley, 2022.