- Test-Driven Design
- Model-Driven Design
- Behavior-Driven Design
- ...-Driven Design
=> Entwurf von Software, mit Fokus auf X
-
Domain-Driven Design = Softwareentwurf / -entwicklung, die die Domain in den Vordergrund rückt
- Domain?
.com
,.net
,.org
, … => Nicht gemeint - Domain = Fachbereich, Fachsprache, Fachthematik, Fachlichkeit, …
- Domain?
-
Softwareentwicklung ist kein Selbstzweck
- Das Ziel von Softwareentwicklung ist, fachliche Probleme aus der realen Welt zu lösen.
-
Eric Evans, 2004, "Domain-Driven Design: Tackling Complexity in the Heart of Software"
- Hilfsmittel, Werkzeuge, Leitplanken, …
- "Blue Book"
-
Vaughn Vernon, "Implementing Domain-Driven Design"
- "Red Book"
-
Kunde (fachlich: was) <-> Entwickler (technisch: wie)
- Weitere Rollen: Designer, Marketing, Sales, Tester, …
- Interdisziplinäres Teams
-
Unterschiedliche Begriffs / unterschiedliche Interpretationen
- regelmäßig: stetig wiederkehrend (Alltag), gemäß einer Regel (Jura)
- Reichweite: Kilometerzahl (Automobil), Follower (Social-Media)
- …
=> Missverständnisse
-
"Language Gap"
- Neugier, Interesse, Empathie, Reflektion => untechnisch
- Ziel: Gemeinsames Verständnis, gemeinsame Sprache
- => Menschliche, fachliche Ansatz
-
Patterns, Artefakten, Prinzipien, Bausteinen, …
- Entity, Value Object, Aggregate, Aggregate Root, Shared Kernel, Bounded Context, Subdomain, Core-Domain, Supporting Domains, Anti-Corruption Layer, Domain, Repository, Factory, …
- => Technische Ansatz
-
Singleton = Einzelstück
- Idee: Klasse, von der nur genau eine einzige Instanz existiert
- Beispielcode
public class MyValue
{
private MyValue _instance;
private string value;
private MyValue ()
{
this.value = '';
}
public static MyValue getInstance ()
{
if (this._instance == null)
{
this._instance = new MyValue();
}
return this._instance;
}
}
-
Raus aus der Komfortzone
- Begriffe suchen, die einen Sachverhalt passend und treffend beschreiben
- In 99,9% der Fälle NICHT Create, Update oder Delete
-
Wie nennt man den Vorgang im Schach, eine Figur zu bewegen?
- Move piece
- Move figure
- Make move
-
Fachsprachen
- Vorgegeben: Rechtliche Gründe, Regelwerk, Fachbereich, …
- Selbst finden: Never completed game, …
- Anwender haben Intentionen, Ziele, Absichten, …
- Nicht technisch, sondern fachlich!
- Befehl (Wunsch) => Befehlsform, Imperativ
- Verb (Imperativ) + Substantiv
-
Aufgabe löschen
-
Aufgabe hinzufügen
-
Aufgabe bearbeiten
-
Aufgabe als erledigt markieren
-
Aufgabe filtern
-
Zeige nur aktive oder nicht-abgeschlossene oder abgeschlossene an
-
Lösche alle fertigen
-
Lösche alle abgeschlossenen
-
Lösche eine Aufgabe
-
Aktualisiere eine Aufgabe
-
Add todo to list => speichern => merken / nicht vergessen / erinnern
-
Create todo at list
-
Remind me for todo => impliziert Erinnerung, die mich proaktiv benachrichtigt
-
Memorize => sich merken => eher bei sich selbst verortet
-
Note todo => notieren => wie auf einem Post-It
-
Create reminder / not todo ;-)
-
Delete todo
-
Remove todo from list
-
Edit todo
-
Modify todo
-
Update todo
-
Mark todo as completed
-
Mark todo as finished
-
Check it
-
Set todo as completed
-
Did todo
-
Mark todo as done
-
Strike through todo
-
Check off todo
-
Cross out todo
-
Tick off todo
-
...
=> CRUD, unterschiedliche Wörter für (gleiche?) Bedeutung
-
Gleiche Wörter für unterschiedliche Konstrukte
-
Unterschiedliche Wörter für identische Konstrukte
-
Vorschlag
- note todo
- edit todo
- mark todo as done
- revert mark todo as done
- drop todo
-
Commands können ausgeführt oder abgelehnt werden
-
Commands folgen gewissen Regeln
-
Beschreibt einen historischen und stattgefundenen Fakt
- Kann nicht rückgängig gemacht werden
- Effekt kann kompensiert werden
-
Ereignis => Vergangenheitsform
- Substantiv + Verb (Vergangenheitsform)
-
note todo => todo noted
-
edit todo => todo edited
-
mark todo as done => todo marked as done
-
revert mark todo as done => mark todo as done reverted
-
drop todo => todo dropped
-
Command - Domain-Event = Request - Response = Aktion = Reaktion
-
Verhältnis
- 1 Command => 1 Domain-Event
- 1 Command => n Domain-Events
- m Commands => 1 Domain-Event
- m Commands => n Domain-Events
-
Beispiel für m:n
- Start group => Group started + Group joined
- Join group => Group joined
- Switch group => Group left + Group joined
- Leave group => Group left
-
Struktur
- name ("Note todo")
- data (welches Todo)
- metadata (Zeitpunkt, ID)
-
Logik, die Commands verarbeitet
- Greift auf Parameter (data, metadata) von Command zu
- State / Zustand => Überdauert einzelne Commands, existiert unabhängig
Commands => State => Domain-Events
- Anforderungen an State
- Konsistenz, Integrität
- Anforderungen an Commands
- Atomar
- Konsistent
- Isoliert
- Dauerhaft
- => ACID-Kriterien, wie bei Transaktionen in SQL
+----------------------------+
| State | Transaktionsgrenze
| / \ | = Aggregate
| / \ | = Hülle, innerhalb der Konsistenz geschützt wird
| Commands Domain-Events |
+----------------------------+
- Wie groß sind denn Aggregates?
- Größere Aggregates: Konsistenz für große Bereiche gegeben, Nebenläufigkeit leidet
- Kleine Aggregates: Hervorragende Nebenläufigkeit, Konsistenz für kleine Bereiche gewährleisten
=> Aggregates sollten so groß wie nötig sein, aber so klein wie möglich.
-
Ziel: Gemeinsames Verständnis, gemeinsame Sprache
-
Was nicht gemeint ist
- Universal language
- Global eineindeutig
-
Was gewünscht ist
- Eindeutige Sprache in einem gewissen Bereich / Kontext
-
Bounded Context
- Bounded = eingegrenzt, weil dieser Kontext/Bereich eine Sprachgrenze darstellt
- Sprache innerhalb eines solchen Kontexts nennt man "Ubiquitous Language"
-
Domain
- TodoMVC
- Bounded Context
- organizing
- Aggregate
- todo list
- State
- todos
- Commands / Domain-Events
- note todo / todo noted
- edit todo / todo edited
- …
-
Mechanismus, um Daten zu speichern (Persistenzstrategie)
-
CRUD SQL REST Bewertung
- Create INSERT POST unkritisch
- Read SELECT GET unkritisch
- Update UPDATE PUT kritisch, da Daten zerstört werden
- Delete DELETE DELETE kritisch, da Daten zerstört werden
login | secret | isDisabled | lastUpdatedAt | aBSLastUpdatAt | prevSecret | prevSecret2 | value1 | value2 | value3 |
------|----------|------------|---------------|----------------|------------|-------------|--------|--------|--------|
jane | password | false | 202012141421 | 202012141421 | oldpass | foobar | | | |
- Herausforderungen
- Verlieren Daten bei UPDATE und DELETE
- Keine historischen Daten
- Keine Information, wann was geändert wurde
- Eventuell wenig aussagekräftige Spaltennamen
- INSERT + SELECT
- Kein UPDATE oder DELETE
Datum Event-Name Event-Data
----------------------------------------------
01.12. Konto eröffnet 0 |
03.12. Gehalt eingegangen +3000 |
04.12. Miete abgebucht -2000 |
-----------------> +1000 <------ Snapshot
05.12. Essen gegangen -250 | Replay
07.12. Lotto gespielt -10 |
08.12. Essen gegangen -30 |
-----------------> +710 <------ Snapshot
11.12. Lotto gewonnen +5 v
=> Kontostand am 11.12. beträgt: 715
-
Datenbank, die so arbeitet, ist ein Event-Store
-
Event-Sourcing
- Nicht den Status-Quo speichern (den müsste man immer wieder mit UPDATE / DELETE verändern)
- Sondern die Deltas speichern, die zum Status Quo geführt haben
-
Vorteile
- Historische Daten
- Reporting
- Analyse
- Semantik
- Ad-hoc-Abfragen auf die Vergangenheit
- Alternative Realitäten
-
Nachteile
- Replay wird aufwändiger im Lauf der Zeit
- Aber: Snapshots können die Performance beliebig verbessern
- Performance wird dann ein deterministischer Sägezahn, keine linear steigende Gerade
- Speicherbedarf
- 1 TByte (Notebook) = 1.000.000.000.000 = 1 Billion
- Speicherplatz kostet nichts
- Daten vor (altem) Snapshot löschen / auslagern
- DSGVO anyone?
- a) Persönliche Daten verschlüsseln (?)
- b) Persönliche Daten auslagern, Eventstore nur Referenz ablegen
- c) Event-Store neu schreiben (Event-Store löschen)
- Events versionieren
- Event anpassen
Gehalt eingegangen
: Betrag (+ Währung)- Unglücklich, insbesondere wenn sich die Semantik verändert
- Versionen durchnummerieren
Gehalt eingegangen
: BetragGehalt eingegangen V2
: Betrag + Währung
- Fachliche Namen neu vergeben
Gehalt eingegangen
: BetragGehalt eingegangen nach Währungsumstellung
: Betrag + Währung
- Event-Upcasting
- Adapter f(v1) => v2
- Zur Laufzeit / im Event-Store (Event-Store aktualisieren)
- Event anpassen
- Replay wird aufwändiger im Lauf der Zeit
TABLE Events
Timestamp DATETIME
ObjectID UUID
ObjectOrder BIGINT
Name VARCHAR
Data JSON / BLOB / TEXT
...
TABLE Snapshots
ObjectID UUID
Revision BIGINT
Data JSON / BLOB / TEXT
# Event speichern
INSERT INTO Events (
Timestamp,
ObjectID,
ObjectOrder,
Name,
Data
) VALUES (
202012141512,
'c76b276b-8a70-4de7-80b8-44800492bd0b',
1,
'Gehalt eingegangen',
{ amount: 3000, currency: 'EUR' }
)
# Events auslesen für ein bestimmtes Objekt
SELECT * FROM Snapshots WHERE ObjectID = ?
=> snapshot (objectId, revision: 100, data: { balance: 3000, currency: 'EUR' })
SELECT * FROM Events WHERE ObjectID = ? AND ObjectOrder > snapshot.revision ORDER BY ObjectOrder
=> [ e1, e2, e3, ... ]
snapshot + e1 + e2 + e3 + ... => Status Quo
CQRS = CQS auf Anwendungsebene
- Design-Pattern, Bertrand Meyer ~2010
- Command Query Separation
- "Eine Frage zu stellen sollte die Antwort nicht verändern."
const stack = new Stack();
// Command (schreiben) = Verändert Zustand, gibt nichts zurück
stack.push(23);
// Query (lesen) = Gibt etwas zurück, verändert nicht den Zustand
console.log(stack.isEmpty());
// Query
console.log(stack.top());
// Command + Query: CQS verletzt!
console.log(stack.pop());
CQRS = Command Query Responsibility Segregation
API (Write) --- Database (Write, 5NF)
/ |
Command |
/ |
UI | Synchronisation, CAP-Theorem => Eventual Consistency
\ |
Query |
\ v
API (Read) ---- Database (Read, 1NF)
-
Normalformen
- 5 NF: Grandios zum Schreiben (Konsistenz, Integrität, …), katastrophal zum Lesen
- 1 NF: Grandios zum Lesen (SELECT * FROM Table), katastrophal zum Schreiben
- 3 NF: Schlecht für beides ;-)
-
CAP-Theorem
- Verteiltes System (Teilweiser Ausfall => Partition)
- Konsistenz: Alle Knoten liefern stets die gleiche Antwort => Preis: Verfügbarkeit nicht gegeben
- Verfügbarkeit: Alle Knoten können speichern => Preis: Sofortige Konsistenz ist nicht gegeben
P - Partition Tolerance
/ \
/ \
/ \
C ----- A - Availability
|
Consistency
- Eventual Consistency
- Zeitlicher Versatz, Asynchronität
- Abwägen von geschäftlichem Risiko und Wahrscheinlichkeit
+-> API (Command) ---> Aggregate(State)
+--> API (Command) ---> Aggregate(State) <-- Events <- Event-Store
/ | ^
Commands v |
/ Events --------------------------+
UI <------ API (Events) -----------+
\ v
Queries Projektion
\ |
\ v
+--> API (Query) ---> View-Database
+-> API (Query) ---> View-Database
+> API (Query) ---> View-Database
- API HTTP GraphQL
- Command POST Mutation
- Query GET Query
- Events WS Subscription
- Facebook, Twitter, Amazon, …
-> API(Command) -> -> Worker
-> Command -> API(Command) -> Queue -> Worker
-> API(Command) -> -> Worker
Um die Anwendung zu starten:
$ npm run dev
Command senden:
$ curl -i -X POST -H 'content-type:application/json' -d '{"...":"..."}' http://localhost:3000/command/<context>/<aggregate>
Events abonnieren:
$ curl -i http://localhost:3000/events
Query ausführen:
$ curl -i http://localhost:3000/query/<query-name>