Моделирование домена

В сердце любого приложения, ориентированного на предметную область, находится модель, которая представляет собой основное знание и бизнес-правила системы. Хорошее моделирование предметной области означает преобразование концепций реального мира в выразительные, связные и последовательные структуры программного обеспечения.

Сущности

Сущность — это объект, который определяется главным образом своей идентичностью, а не только своими атрибутами. Даже если атрибуты меняются со временем, идентичность сущности остается неизменной.

Основные характеристики:

  • Имеет уникальную идентичность (обычно Id).
  • Важен именно кто сущность, а не только что она содержит.
  • Её атрибуты могут изменяться со временем.

Создание сущности с помощью Lino

Для создания новой сущности с использованием Lino выполните:

lino entity new

CLI помощник запросит:

  • Сервис – сервис, в котором будет создана сущность.
  • Модуль – модуль, в котором будет создана сущность (только для модульных сервисов).
  • Имя сущности – имя, используемое в домене и таблице базы данных.

Затем вы определите поля, которые составляют сущность, настроив каждое из них.

Доступные типы полей

Тип Описание Диапазон / Примечания
shortЦелое число 16 бит-32 768 → 32 767
intЦелое число 32 бита-2 147 483 648 → 2 147 483 647
longЦелое число 64 бита-9 223 372 036 854 775 808 → 9 223 372 036 854 775 807
stringТекстДо ~2 миллиардов символов
boolБулево значениеtrue или false
GuidГлобальный уникальный идентификаторРаспределённая уникальность
decimalДесятичное число с высокой точностьюИдеально для денежных значений
floatЧисло с плавающей точкой (32 бита)≈ 6–9 знаков точности
doubleЧисло с плавающей точкой (64 бита)≈ 15–17 знаков точности
DateTimeДата и времяВключая часовой пояс
DateOnlyТолько дата (C# 10+)
TimeOnlyТолько время (C# 10+)
EntityСсылка на другую сущность1 : 1 или 1 : N
Value ObjectНеизменяемый объект значенияНапример: адрес, CPF
EnumПеречислениеФиксированный набор значений
List<Entity>Список сущностей1 : N
ManyToManyМногие-ко-многимТребуется таблица связей

Пример

Создание сущности Person:

┌────┬────┬───────────────┬────────┬────────┬───────────┬────────────────┐
│ PK │ FK │ Property name │ Type   │ Length │ Required  │ Auto-increment │
├────┼────┼───────────────┼────────┼────────┼───────────┼────────────────┤
│ x  │    │ Id            │ int    │        │     x     │       x        │
├────┼────┼───────────────┼────────┼────────┼───────────┼────────────────┤
│    │    │ Name          │ string │  100   │     x     │                │
└────┴────┴───────────────┴────────┴────────┴───────────┴────────────────┘

Структура, сгенерированная Lino:

MyApp/
└── src/
    └── Services/
        └── MyService/
            └── Domain/
                ├── MyApp.MyService.Domain.csproj
                └── Aggregates/
                    └── People/
                        ├── Person.cs
                        ├── Errors/
                        │   └── PersonErrors.cs
                        ├── Repositories/
                        │   └── IPersonRepository.cs
                        └── Resources/
                            └── Person/
                                ├── PersonResources.resx
                                ├── PersonResources.en.resx
                                └── PersonResources.pt-BR.resx

После определения сущностей используйте сам Lino для управления Migrations и синхронизации базы данных. Мы подробно рассмотрим этот процесс в разделе «Слой Персистенции».

Объекты значения

Объект значения представляет собой концепцию домена, определяемую только своими атрибутами – у него нет собственной идентичности. Два объекта значения считаются равными, если все их значения совпадают.

Основные характеристики:

  • Неизменяемы после создания.
  • Не имеют Id.

Создание объекта значения с помощью Lino

Выполните:

lino value-object new

CLI запросит:

  • Сервис – Сервис, в котором будет создан объект.
  • Модуль – Модуль, в котором будет создан объект (только для модульных сервисов).
  • Расположение – Корень домена или конкретный агрегат.
  • Имя объекта значения.

Затем определите поля, которые составляют объект.

Доступные типы полей

ТипОписаниеПримечания
short16-битное целое-32 768 → 32 767
int32-битное целое-2 147 483 648 → 2 147 483 647
long64-битное целое-9 223 372 036 854 775 808 → 9 223 372 036 854 775 807
stringТекстДо ~2 миллиардов символов
boolБулевоtrue/false
decimalТочный десятичныйДенежные значения
floatЧисло с плавающей точкой (32 бита)≈ 6–9 цифр
doubleЧисло с плавающей точкой (64 бита)≈ 15–17 цифр
DateTimeДата/времяВключая часовой пояс
DateOnlyТолько датаC# 10+
TimeOnlyТолько времяC# 10+

Пример

Объект значения 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     │
└───────────────┴────────┴────────┴───────────┘

Структура сгенерированных файлов (агрегат Person):

MyApp/
└── src/
    └── Services/
        └── MyService/
            └── Domain/
                ├── MyApp.MyService.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

Как и в сущностях, Migrations могут управляться Lino для поддержания синхронизации модели данных.

Перечисления

Перечисления в DDD могут выходить за рамки традиционных enum в C#. Они могут быть богатыми объектами, представляющими фиксированные состояния, содержащими валидации, вспомогательные методы и даже поведение.

Мотивация:

  • C# enum ограничены целочисленным или строковым значением.
  • Моделирование перечисления как класса предоставляет большую гибкость и выразительность.

Основные характеристики:

  • Это классы, наследующиеся от общего базового класса и инкапсулирующие Id и Имя.
  • Позволяют добавлять валидации, вспомогательные методы и поведение.

Создание перечисления с помощью Lino

Выполните:

lino enum new

Мастер запросит:

  • Сервис.
  • Модуль (если применимо).
  • Расположение – корень домена или агрегат.
  • Имя перечисления.
  • Тип – традиционный enum или Smart Enum (class).
  • Хранениеint или string в базе данных.

Пример

Перечисление PersonStatus:

┌───────┬───────────┬──────────────┐
│ Value │ Name      │ Display Name │
├───────┼───────────┼──────────────┤
│ 1     │ Active    │ Active       │
├───────┼───────────┼──────────────┤
│ 2     │ Inactive  │ Inactive     │
├───────┼───────────┼──────────────┤
│ 3     │ Suspended │ Suspended    │
├───────┼───────────┼──────────────┤
│ 4     │ Deleted   │ Deleted      │
└───────┴───────────┴──────────────┘

Сгенерированная структура:

MyApp/
└── src/
    └── Services/
        └── MyService/
            └── Domain/
                ├── MyApp.MyService.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

Хранить значение перечисления как string допустимо и может улучшить читаемость, но обычно менее эффективно по производительности и хранению. Поэтому рекомендуется хранить значение как int, а для сохранения ссылочной целостности и облегчения сопровождения создать вспомогательную сущность (таблицу), где первичный ключ соответствует значению перечисления.

После определения перечислений используйте Lino для генерации и применения Migrations, гарантируя, что база данных отражает модель домена. Подробнее см. в разделе Слой Персистентности.

Произошла необработанная ошибка. Обновить 🗙