Domänenmodellierung

Im Herzen jeder domΓ€nenorientierten Anwendung steht das Modell, das das zentrale Wissen und die GeschΓ€ftsregeln des Systems reprΓ€sentiert. Das DomΓ€nenmodell gut zu gestalten bedeutet, Konzepte der realen Welt in ausdrucksstarke, zusammenhΓ€ngende und konsistente Softwarestrukturen zu ΓΌbersetzen.

EntitΓ€ten

Eine EntitΓ€t ist ein Objekt, das hauptsΓ€chlich durch seine IdentitΓ€t definiert ist und nicht nur durch seine Attribute. Auch wenn sich die Attribute im Laufe der Zeit Γ€ndern, bleibt die IdentitΓ€t einer EntitΓ€t gleich.

Hauptmerkmale:

  • Hat eine eindeutige IdentitΓ€t (normalerweise ein Id).
  • Wichtig ist, wer die EntitΓ€t ist, nicht nur was sie enthΓ€lt.
  • Die Attribute kΓΆnnen sich im Laufe der Zeit Γ€ndern.

Erstellen einer EntitΓ€t mit Lino

Um eine neue EntitΓ€t mit Lino zu erstellen, fΓΌhren Sie aus:

lino entity new

Der CLI-Assistent fragt nach:

  • Dienst – Dienst, in dem die EntitΓ€t erstellt wird.
  • Modul – Modul, in dem die EntitΓ€t erstellt wird (nur bei modularen Diensten).
  • EntitΓ€tsname – Name, der in der DomΓ€ne und der Datenbanktabelle verwendet wird.

Dann definieren Sie die Felder, aus denen die EntitΓ€t besteht, und konfigurieren jedes einzelne.

VerfΓΌgbare Feldtypen

Typ Beschreibung Bereich / Anmerkungen
short16-Bit Ganzzahl-32β€―768 β†’ 32β€―767
int32-Bit Ganzzahl-2β€―147β€―483β€―648 β†’ 2β€―147β€―483β€―647
long64-Bit Ganzzahl-9β€―223β€―372β€―036β€―854β€―775β€―808 β†’ 9β€―223β€―372β€―036β€―854β€―775β€―807
stringTextBis zu ~2 Milliarden Zeichen
boolBoolescher Werttrue oder false
GuidEindeutiger globaler BezeichnerVerteilte Eindeutigkeit
decimalDezimalzahl mit hoher GenauigkeitIdeal fΓΌr monetΓ€re Werte
floatGleitkommazahl (32 Bit)β‰ˆ 6–9 Stellen Genauigkeit
doubleGleitkommazahl (64 Bit)β‰ˆ 15–17 Stellen Genauigkeit
DateTimeDatum und UhrzeitBeinhaltet Zeitzone
DateOnlyNur Datum (C# 10+)–
TimeOnlyNur Uhrzeit (C# 10+)–
EntityVerweis auf eine andere EntitΓ€t1:1 oder 1:N
Value ObjectUnverΓ€nderliches Value Objectz.B. Adresse, CPF
EnumAufzΓ€hlungFeste Menge von Werten
List<Entity>Liste von EntitΓ€ten1:N
ManyToManyViele-zu-vieleErfordert VerknΓΌpfungstabelle

Beispiel

Erstellen der EntitΓ€t Person:

β”Œβ”€β”€β”€β”€β”¬β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ PK β”‚ FK β”‚ Property name β”‚ Type   β”‚ Length β”‚ Required  β”‚ Auto-increment β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ x  β”‚    β”‚ Id            β”‚ int    β”‚        β”‚     x     β”‚       x        β”‚
β”œβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚    β”‚    β”‚ Name          β”‚ string β”‚  100   β”‚     x     β”‚                β”‚
β””β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Vom Lino generierte Struktur:

<ProjectName>/
└── src/
    └── Services/
        └── <ServiceName>/
            └── Domain/
                β”œβ”€β”€ <ProjectName>.<ServiceName>.Domain.csproj
                └── Aggregates/
                    └── People/
                        β”œβ”€β”€ Person.cs
                        β”œβ”€β”€ Errors/
                        β”‚   └── PersonErrors.cs
                        β”œβ”€β”€ Repositories/
                        β”‚   └── IPersonRepository.cs
                        └── Resources/
                            └── Person/
                                β”œβ”€β”€ PersonResources.resx
                                β”œβ”€β”€ PersonResources.en.resx
                                └── PersonResources.pt-BR.resx

Nachdem Sie Ihre EntitΓ€ten definiert haben, verwenden Sie Lino, um die Migrations zu verwalten und die Datenbank synchron zu halten. Dieser Prozess wird im Abschnitt Persistenzschicht ausfΓΌhrlich behandelt.

Aktueller Evolutionsablauf

Neben dem interaktiven Ablauf mit lino entity new kann die CLI die Absicht auch direkt im Befehl ausdrΓΌcken, wenn Service, Modul und Name bereits bekannt sind:

lino entity new --name <EntityName> --service <ServiceName> --module <ModuleName>
lino entity edit --service <ServiceName> --module <ModuleName> --entity <EntityName>
lino entity list --service <ServiceName> --module <ModuleName>

Verwenden Sie entity list, bevor Sie in großen Modulen neue Konzepte erstellen. PrΓΌfen Sie bei Γ„nderungen Identifier, Properties, Pflichtangaben, LΓ€nge, Beziehungen, Indizes, Ownership, Tenant und Auswirkungen auf Migrations.

Strongly Typed IDs, Ownership und Invarianten

Wenn Strongly Typed IDs aktiviert sind, wird der Identifier zu einem dedizierten Typ, etwa ProductId, wodurch versehentliche Vertauschungen von IDs verschiedener EntitΓ€ten reduziert werden. Beziehungen sollten das tatsΓ€chliche Ownership im DomΓ€nenmodell widerspiegeln: Nicht jede Referenz muss zu einer direkten Navigation werden; oft schΓΌtzen ein Identifier, eine Shadow Entity, ein Integration Event oder eine explizite Abfrage Modulgrenzen besser.

Legen Sie Invarianten in die DomΓ€ne, nicht nur in die UI, Datenbank oder Validators. FΓΌhren Sie nach Γ„nderungen an EntitΓ€ten einen Build aus, prΓΌfen Sie den Diff und generieren oder aktualisieren Sie Migrations.

Value Objects

Ein Value Object reprΓ€sentiert ein DomΓ€nenkonzept, das ausschließlich durch seine Attribute definiert wird – es besitzt keine eigene IdentitΓ€t. Zwei Value Objects gelten als gleich, wenn alle ihre Werte ΓΌbereinstimmen.

Hauptmerkmale:

  • UnverΓ€nderlich nach der Erstellung.
  • Besitzen keine Id.

Erstellen eines Value Object mit Lino

FΓΌhren Sie aus:

lino value-object new

Das CLI wird fragen nach:

  • Service – Service, in dem das Objekt erstellt wird.
  • Modul – Modul, in dem das Objekt erstellt wird (nur in modularen Services).
  • Ort – DomΓ€nenwurzel oder spezifisches Aggregat.
  • Name des Value Object.

Definieren Sie anschließend die Felder, die das Objekt ausmachen.

VerfΓΌgbare Feldtypen

TypBeschreibungAnmerkungen
short16-Bit-Ganzzahl-32.768 β†’ 32.767
int32-Bit-Ganzzahl-2.147.483.648 β†’ 2.147.483.647
long64-Bit-Ganzzahl-9.223.372.036.854.775.808 β†’ 9.223.372.036.854.775.807
stringTextBis zu ~2 Milliarden Zeichen
boolBooleantrue/false
decimalPrΓ€zise DezimalzahlMonetΓ€re Werte
floatGleitkommazahl (32 Bit)β‰ˆ 6–9 Stellen
doubleGleitkommazahl (64 Bit)β‰ˆ 15–17 Stellen
DateTimeDatum/UhrzeitEnthΓ€lt Zeitzone
DateOnlyNur DatumC# 10+
TimeOnlyNur UhrzeitC# 10+

Beispiel

Value Object Address:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Property name β”‚ Type   β”‚ Length β”‚ Required  β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Street        β”‚ string β”‚  100   β”‚     x     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Number        β”‚ string β”‚   10   β”‚     x     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Neighborhood  β”‚ string β”‚   50   β”‚           β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ City          β”‚ string β”‚  100   β”‚     x     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ State         β”‚ string β”‚   2    β”‚     x     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ PostalCode    β”‚ string β”‚   20   β”‚     x     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ Country       β”‚ string β”‚  100   β”‚     x     β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Generierte Dateistruktur (Aggregat Person):

<ProjectName>/
└── src/
    └── Services/
        └── <ServiceName>/
            └── Domain/
                β”œβ”€β”€ <ProjectName>.<ServiceName>.Domain.csproj
                └── Aggregates/
                    └── People/
                        β”œβ”€β”€ Person.cs
                        β”œβ”€β”€ ValueObjects/
                        β”‚   └── Address.cs
                        β”œβ”€β”€ Errors/
                        β”‚   β”œβ”€β”€ AddressErrors.cs
                        β”‚   └── PersonErrors.cs
                        β”œβ”€β”€ Repositories/
                        β”‚   └── IPersonRepository.cs
                        └── Resources/
                            β”œβ”€β”€ Address/
                            β”‚   β”œβ”€β”€ AddressResources.resx
                            β”‚   β”œβ”€β”€ AddressResources.en.resx
                            β”‚   └── AddressResources.pt-BR.resx
                            └── Person/
                                β”œβ”€β”€ PersonResources.resx
                                β”œβ”€β”€ PersonResources.en.resx
                                └── PersonResources.pt-BR.resx

Wie bei EntitΓ€ten kΓΆnnen Migrations von Lino verwaltet werden, um das Datenmodell synchron zu halten.

Aktueller Evolutionsablauf

Neben dem interaktiven Befehl kΓΆnnen Sie Parameter verwenden, wenn bereits klar ist, wo das Konzept entstehen soll:

lino value-object new --name <ValueObjectName> --service <ServiceName> --module <ModuleName>
lino value-object edit --service <ServiceName> --module <ModuleName> --value-object <ValueObjectName>
lino value-object list --service <ServiceName> --module <ModuleName>

Wenn eine EntitΓ€tseigenschaft als ValueObject deklariert wird, kann Lino ein vorhandenes Value Object wiederverwenden oder wΓ€hrend der EntitΓ€tsmodellierung ein neues erstellen. Bevorzugen Sie diese Option, wenn die Felder ein gemeinsames Konzept darstellen, etwa Geld, Adresse, Abmessungen, Zeitraum oder Dokument.

Persistenz, UI und Lokalisierung

Lino mappt Eigenschaften von Value Objects ΓΌber die Persistenzschicht des Moduls und ΓΌbertrΓ€gt Anzeigemetadaten in Resources. Dadurch werden lokalisierte Labels und Meldungen fΓΌr verschachtelte Werte mΓΆglich. Das Value Object gehΓΆrt zu dem Modul, in dem es generiert wurde; ohne explizite Integration wird es nicht zu einem gemeinsam genutzten Vertrag zwischen Modulen.

SchΓΌtzen Sie Invarianten im Typ selbst: Negative Werte, ungΓΌltige WΓ€hrungen, invertierte ZeitrΓ€ume oder fehlerhafte Dokumente sollten nicht gΓΌltig erzeugt werden kΓΆnnen.

Enumeration

Enumeration in DDD kΓΆnnen ΓΌber die traditionellen enum-Typen von C# hinausgehen. Sie kΓΆnnen reichhaltige Objekte sein, die feste ZustΓ€nde reprΓ€sentieren, Validierungen, Hilfsmethoden und sogar Verhalten enthalten.

Motivation:

  • Die C#-enum-Typen sind auf einen ganzzahligen oder string-Wert beschrΓ€nkt.
  • Das Modellieren einer Enumeration als Klasse bietet mehr FlexibilitΓ€t und Ausdruckskraft.

Hauptmerkmale:

  • Es sind Klassen, die von einer gemeinsamen Basis erben und Id und Name kapseln.
  • Sie erlauben das HinzufΓΌgen von Validierungen, Hilfsmethoden und Verhalten.

Erstellen einer Enumeration mit Lino

FΓΌhren Sie aus:

lino enumeration new

Der Assistent wird nach Folgendem fragen:

  • Service.
  • Modul (falls zutreffend).
  • Ort – Root des DomΓ€nens oder Aggregats.
  • Name der Enumeration.
  • Typ – traditionelles enum oder Smart Enum (class).
  • Speicherung – int oder string in der Datenbank.

Beispiel

Enumeration PersonStatus:

β”Œβ”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Value β”‚ Name      β”‚ Display Name β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 1     β”‚ Active    β”‚ Active       β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 2     β”‚ Inactive  β”‚ Inactive     β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 3     β”‚ Suspended β”‚ Suspended    β”‚
β”œβ”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”Όβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€
β”‚ 4     β”‚ Deleted   β”‚ Deleted      β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Generierte Struktur:

<ProjectName>/
└── src/
    └── Services/
        └── <ServiceName>/
            └── Domain/
                β”œβ”€β”€ <ProjectName>.<ServiceName>.Domain.csproj
                └── Aggregates/
                    └── People/
                        β”œβ”€β”€ Person.cs
                        β”œβ”€β”€ Enums/
                        β”‚   └── PersonStatus.cs
                        β”œβ”€β”€ ValueObjects/
                        β”‚   └── Address.cs
                        β”œβ”€β”€ Errors/
                        β”‚   β”œβ”€β”€ AddressErrors.cs
                        β”‚   └── PersonErrors.cs
                        β”œβ”€β”€ Repositories/
                        β”‚   └── IPersonRepository.cs
                        └── Resources/
                            β”œβ”€β”€ Address/
                            β”‚   β”œβ”€β”€ AddressResources.resx
                            β”‚   β”œβ”€β”€ AddressResources.en.resx
                            β”‚   └── AddressResources.pt-BR.resx
                            β”œβ”€β”€ Person/
                            β”‚   β”œβ”€β”€ PersonResources.resx
                            β”‚   β”œβ”€β”€ PersonResources.en.resx
                            β”‚   └── PersonResources.pt-BR.resx
                            └── PersonStatus/
                                β”œβ”€β”€ PersonStatusResources.resx
                                β”œβ”€β”€ PersonStatusResources.en.resx
                                └── PersonStatusResources.pt-BR.resx

Das Speichern des Werts einer Enumeration als string ist gΓΌltig und kann die Lesbarkeit verbessern, ist jedoch in Bezug auf Leistung und Speicher ineffizienter. Daher empfehlen wir, den Wert als int zu speichern und zur Wahrung der referenziellen IntegritΓ€t und zur Vereinfachung der Wartung eine HilfsentitΓ€t (Tabelle) zu erstellen, bei der der PrimΓ€rschlΓΌssel dem Wert der Enumeration entspricht.

Nachdem Sie die Enumerationen definiert haben, verwenden Sie Lino, um die Migrations zu generieren und anzuwenden, damit die Datenbank das DomΓ€nenmodell widerspiegelt. Details finden Sie im Abschnitt Persistenzschicht.

Aktueller Evolutionsablauf

Neben dem interaktiven Befehl kΓΆnnen Sie Parameter verwenden, wenn Speicherort und Name bereits feststehen:

lino enumeration new --name <EnumerationName> --service <ServiceName> --module <ModuleName>
lino enumeration edit --service <ServiceName> --module <ModuleName> --enumeration <EnumerationName>
lino enumeration list --service <ServiceName> --module <ModuleName>

Enumeration, EntitΓ€t oder Konfiguration

Enumeration verwendenEntitΓ€t oder Konfiguration verwenden
Bestellstatus, VerΓΆffentlichungsstatus, technischer IntegrationstypVom Benutzer verwaltete Kategorie, pro Tenant konfigurierbarer Grund, im Backoffice verwalteter Lieferant
Mit dem Code versionierte WerteZur Laufzeit geΓ€nderte oder durch Berechtigungen gesteuerte Werte
Einfache Regel oder kleines Verhalten pro WertLebenszyklus, Audit, dynamische Übersetzung oder eigene Beziehung

Wenn die Enumeration einen Anzeigenamen bereitstellt, kann Lino Resources fΓΌr lokalisierte Labels generieren. Wenn sie persistiert oder als Seed Data verwendet wird, prΓΌfen Sie Migrations nach dem HinzufΓΌgen, Entfernen oder Umbenennen von Werten. Konsistenz zwischen Code, Datenbank und UI ist Teil der DomΓ€nenΓ€nderung.

Ein unbehandelter Fehler ist aufgetreten. Aktualisieren πŸ—™