Modeling the domain
At the heart of any domain-driven application is the model that represents the systemβs core knowledge and business rules. Modeling the domain well means translating real-world concepts into expressive, cohesive, and consistent software structures.
Entities
An entity is an object defined primarily by its identity and not just by its attributes. Even if the attributes change over time, the identity of an entity remains the same.
Main characteristics:
- Has a unique identity (usually an
Id). - What matters is who the entity is, not just what it contains.
- Its attributes may change over time.
Creating an entity with Lino
To create a new entity using Lino, run:
lino entity new
The CLI wizard will ask for:
- Service β The service where the entity will be created.
- Module β The module where the entity will be created (only in modular services).
- Entity name β The name used in the domain and in the database table.
Then, you will define the fields that make up the entity, configuring each one.
Available field types
| Type | Description | Range / Notes |
|---|---|---|
short | 16-bit integer | -32,768 β 32,767 |
int | 32-bit integer | -2,147,483,648 β 2,147,483,647 |
long | 64-bit integer | -9,223,372,036,854,775,808 β 9,223,372,036,854,775,807 |
string | Text | Up to ~2 billion characters |
bool | Boolean value | true or false |
Guid | Globally unique identifier | Distributed uniqueness |
decimal | High-precision decimal number | Ideal for monetary values |
float | Floating point (32-bit) | β 6β9 digits of precision |
double | Floating point (64-bit) | β 15β17 digits of precision |
DateTime | Date and time | Includes time zone |
DateOnly | Date only (C# 10+) | β |
TimeOnly | Time only (C# 10+) | β |
Entity | Reference to another entity | 1:1 or 1:N |
Value Object | Immutable Value Object | e.g., Address, CPF |
Enum | Enumeration | Fixed set of values |
List<Entity> | List of entities | 1:N |
ManyToMany | Many-to-many | Requires a join table |
Example
Creating the entity Person:
ββββββ¬βββββ¬ββββββββββββββββ¬βββββββββ¬βββββββββ¬ββββββββββββ¬βββββββββββββββββ β PK β FK β Property name β Type β Length β Required β Auto-increment β ββββββΌβββββΌββββββββββββββββΌβββββββββΌβββββββββΌββββββββββββΌβββββββββββββββββ€ β x β β Id β int β β x β x β ββββββΌβββββΌββββββββββββββββΌβββββββββΌβββββββββΌββββββββββββΌβββββββββββββββββ€ β β β Name β string β 100 β x β β ββββββ΄βββββ΄ββββββββββββββββ΄βββββββββ΄βββββββββ΄ββββββββββββ΄βββββββββββββββββ
Structure generated by Lino:
<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
After defining your entities, use Lino itself to manage the Migrations and keep the database synchronized. We will cover this process in detail in the Persistence Layer section.
Current evolution workflow
Besides the interactive lino entity new flow, the CLI also lets you make intent explicit when service, module, and name are already known:
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>
Use entity list before creating new concepts in large modules. During edits, review identifier, properties, requiredness, length, relationships, indexes, ownership, tenant, and migration impact.
Strongly Typed IDs, ownership, and invariants
When Strongly Typed IDs are enabled, the identifier becomes a dedicated type, such as ProductId, reducing accidental swaps between IDs from different entities. Relationships should reflect real domain ownership: not every reference needs direct navigation; often an identifier, shadow entity, integration event, or explicit query protects module boundaries better.
Put invariants in the domain, not only in the UI, database, or validators. After changing entities, run a build, review the diff, and generate or update migrations.
Value Objects
A Value Object represents a domain concept defined only by its attributes β it does not have its own identity. Two Value Objects are considered equal if all their values are equal.
Main characteristics:
- Immutable after creation.
- Do not have an
Id.
Creating a Value Object with Lino
Run:
lino value-object new
The CLI will ask for:
- Service β Service in which the object will be created.
- Module β Module in which the object will be created (only in modular services).
- Location β Domain root or specific aggregate.
- Value object name.
Then, define the fields that make up the object.
Available field types
| Type | Description | Notes |
|---|---|---|
short | 16-bit integer | -32,768 β 32,767 |
int | 32-bit integer | -2,147,483,648 β 2,147,483,647 |
long | 64-bit integer | -9,223,372,036,854,775,808 β 9,223,372,036,854,775,807 |
string | Text | Up to ~2 billion characters |
bool | Boolean | true/false |
decimal | Precise decimal | Monetary values |
float | Floating point (32-bit) | β 6β9 digits |
double | Floating point (64-bit) | β 15β17 digits |
DateTime | Date/time | Includes timezone |
DateOnly | Date only | C# 10+ |
TimeOnly | Time only | C# 10+ |
Example
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 β βββββββββββββββββ΄βββββββββ΄βββββββββ΄ββββββββββββ
Generated file structure (aggregate 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
As with entities, Migrations can be managed by Lino to keep the data model in sync.
Current evolution workflow
Besides the interactive command, use parameters when you already know where the concept should be created:
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>
When an entity property is declared as ValueObject, Lino can reuse an existing Value Object or create a new one during entity modeling. Prefer this option when the fields represent one concept, such as money, address, dimensions, period, or document.
Persistence, UI, and localization
Lino maps Value Object properties through the module persistence layer and carries display metadata into resources. This enables localized labels and messages for nested values. The Value Object belongs to the module where it was generated; it does not become a shared contract between modules without an explicit integration.
Protect invariants in the type itself: negative values, invalid currency, inverted periods, or malformed documents should not be valid.
Enumerations
In DDD, enumerations can go beyond the traditional C# enum. They can be rich objects
that represent fixed states, containing validations, helper methods, and even behavior.
Motivation:
- C#
enums are limited to an integer or string value. - Modeling an Enumeration as a class offers greater flexibility and expressiveness.
Main characteristics:
- They are classes inheriting from a common base, encapsulating
IdandName. - Allow adding validations, helper methods, and behavior.
Creating an enumeration with Lino
Run:
lino enumeration new
The assistant will ask for:
- Service.
- Module (if applicable).
- Location β Root of the domain or aggregate.
- Enumeration name.
- Type β Traditional
enumor Smart Enum (class). - Storage β
intorstringin the database.
Example
Enumeration PersonStatus:
βββββββββ¬ββββββββββββ¬βββββββββββββββ β Value β Name β Display Name β βββββββββΌββββββββββββΌβββββββββββββββ€ β 1 β Active β Active β βββββββββΌββββββββββββΌβββββββββββββββ€ β 2 β Inactive β Inactive β βββββββββΌββββββββββββΌβββββββββββββββ€ β 3 β Suspended β Suspended β βββββββββΌββββββββββββΌβββββββββββββββ€ β 4 β Deleted β Deleted β βββββββββ΄ββββββββββββ΄βββββββββββββββ
Generated structure:
<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
Storing an enumeration value as a string is valid and can improve readability, but tends to be less efficient in terms of performance and storage.
Therefore, we recommend storing the value as an int, and to maintain referential integrity and ease maintenance,
create an auxiliary entity (table) where the primary key corresponds to the enumeration value.
After defining your enumerations, use Lino to generate and apply Migrations, ensuring the database reflects the domain model. See details in the Persistence Layer section.
Current evolution workflow
Besides the interactive command, use parameters when location and name are already defined:
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, entity, or configuration
| Use enumeration | Use entity or configuration |
|---|---|
| Order status, publication state, technical integration type | User-managed category, tenant-configurable reason, supplier managed in backoffice |
| Values versioned with code | Values changed at runtime or controlled by permissions |
| Simple rule or small behavior per value | Lifecycle, audit, dynamic translation, or own relationship |
If the enumeration exposes a display name, Lino can generate resources for localized labels. If it is persisted or used as seed data, review migrations after adding, removing, or renaming values. Consistency between code, database, and UI is part of the domain change.
