Управление Persistence данных

Persistence данных в Lino генерируется вокруг границ Clean Architecture: доменная модель остается независимой от Entity Framework Core, а вопросы базы данных находятся в Infrastructure.Persistence. Это сохраняет фокус сущностей на бизнес-правилах и размещает mappings, provider configuration, migrations, repositories, transactions и initialization в infrastructure layer.


При создании сервиса с lino service new Lino фиксирует важные решения по persistence, такие как архитектура сервиса и data provider. Текущие варианты provider: SqlServer и PostgreSql. В традиционных сервисах persistence генерируется непосредственно под сервисом. В модульных сервисах каждый модуль получает собственный проект Infrastructure.Persistence и собственный ApplicationDbContext, а база данных остается общей для сервиса.


В традиционных или модульных сервисах база данных одна на сервис. В модульных сервисах каждый модуль сопоставляется с отдельной schema внутри той же базы данных. Это позволяет каждому bounded context сохранять отдельное логическое namespace, с функциональной изоляцией, более понятным versioning и более организованными migrations.


Эта страница объясняет, как Lino организует конфигурации Entity Framework Core, регистрацию DbContext, repositories, IUnitOfWork, transactions, поток Transactional Outbox и цикл migrations, предоставляемый CLI.

Важно: сгенерированный persistence code является production-oriented scaffolding. Всегда проверяйте mappings, constraints, indexes, delete behavior, transactions и migration scripts перед применением изменений к shared или production базам данных.

Конфигурации типов сущностей

Lino следует принципу Persistence-Ignorant: доменные сущности не знают деталей инфраструктуры. Весь ORM mapping находится в классах, которые реализуют IEntityTypeConfiguration<TEntity> и расположены в Infrastructure.Persistence/Configurations. Поэтому сущности выражают бизнес-поведение, а инфраструктура определяет, как они будут храниться.

Файлы конфигурации генерируются на основе решений, принятых в CLI: primary keys, strongly typed IDs, обязательные поля, размеры строк, Value Objects, связи, перечисления, поля аудита, tenant fields и дочерние сущности. Сгенерированный результат следует рассматривать как надежную отправную точку, а не как код, который никогда не требует проверки.

Что обычно определяют конфигурации

  • Таблица и schema: имя таблицы и schema текущего сервиса или модуля.
  • Primary keys: идентификаторы сущностей, преобразования strongly typed IDs и поведение генерации ключей.
  • Свойства: обязательные поля, максимальная длина, типы колонок, сохранение enum, owned structures для Value Objects и колонки metadata файлов.
  • Связи: one-to-one, one-to-many, many-to-many, child collections, foreign keys и поведение удаления.
  • Индексы и constraints: уникальность, производительность lookup, tenant-aware уникальность и согласованность на уровне базы данных.

Глобальные соглашения

Повторяющиеся соглашения можно централизовать в ModelConfiguration или эквивалентных helpers: decimal precision, collation, преобразование DateTime, naming convention, глобальные фильтры, аудируемые свойства и общие правила для multi-tenant полей.

Как применяются конфигурации

Сгенерированный ApplicationDbContext применяет конфигурации в OnModelCreating. По умолчанию Lino использует Source Generators, чтобы регистрировать конфигурации производительнее и без Reflection. Когда используется scanning по assembly, конфигурация может выглядеть так:

modelBuilder.ApplyConfigurationsFromAssembly(typeof(ApplicationDbContext).Assembly);

Когда включен messaging, Lino также применяет конфигурацию OutboxMessage, чтобы integration events сохранялись в той же транзакции базы данных до того, как worker опубликует их асинхронно.

Checklist проверки

  1. Убедитесь, что имена таблиц, schemas и соглашения соответствуют границе модуля.
  2. Проверьте размеры строк, decimal precision, nullable колонки и сохранение enums перед генерацией migrations.
  3. Внимательно проверьте связи и delete behavior, особенно для aggregate roots и дочерних сущностей.
  4. Добавьте или скорректируйте индексы для queries, которые используются grids, filters, integrations и background jobs.
  5. Запустите build перед созданием migration, чтобы убедиться, что EF Core видит согласованную модель.

DbContexts

Сгенерированный ApplicationDbContext является контекстом EF Core для границы persistence сервиса или модуля. Он предоставляет свойства DbSet<TEntity> для сопоставленных сущностей и реализует IApplicationDbContext, позволяя query handlers и application services зависеть от application abstraction, а не от конкретного infrastructure класса.

Традиционные и модульные сервисы

  • Традиционный сервис: persistence генерируется в src/Services/<ServiceName>/Infrastructure.Persistence, а API сервиса используется как startup project для migrations.
  • Модульный сервис: каждый модуль имеет собственный проект src/Services/<ServiceName>/Modules/<ModuleName>/Infrastructure.Persistence и собственный ApplicationDbContext. Host сервиса используется как startup project для migrations.

В модульных сервисах такое разделение делает каждый bounded context явным в коде. Модули могут развивать свою модель persistence независимо, даже если они работают в одной базе данных сервиса, когда выбрана такая архитектура.

Регистрация и настройка provider

Lino генерирует регистрацию persistence через extension для IHostApplicationBuilder. Сгенерированный код регистрирует Unit of Work, repositories, domain services, pooled factory для ApplicationDbContext, scoped ApplicationDbContext и abstraction IApplicationDbContext.

Provider-specific конфигурация генерируется в соответствии с базой данных, выбранной для сервиса:

  • UseSqlServer(...) для сервисов SQL Server.
  • UseNpgsql(...).UseSnakeCaseNamingConvention() для сервисов PostgreSQL.
  • MigrationsHistoryTable(Constants.Database.MigrationsHistoryTable, Constants.Database.Schema) для хранения истории migrations в ожидаемых таблице и schema.
  • Provider-specific constraint validators регистрируются, чтобы преобразовывать нарушения базы данных в согласованные application errors.

Tenant-aware contexts

Когда модуль содержит tenant-aware сущности, сгенерированный context может включать состояние tenant и глобальные query filters. В этом сценарии scoped factory создает context, настроенный для текущего scope, чтобы handlers не повторяли tenant filters вручную во всех местах чтения и записи.

Repositories

Lino генерирует repository interfaces в domain layer и concrete implementations в Infrastructure.Persistence/Repositories. Это сохраняет направление зависимостей: domain определяет нужный ему persistence contract, а infrastructure предоставляет реализацию с Entity Framework Core.

Concrete repositories также могут находиться в <ModuleName>/Infrastructure.Persistence.Repositories и реализовывать interfaces из <ModuleName>/Domain.Repositories, в зависимости от структуры сервиса. Важный момент в том, что application и domain не зависят напрямую от concrete деталей EF Core.

Repositories в основном используются command handlers и domain-oriented flows, которым нужно сохранять aggregates. Query Handlers могут использовать IApplicationDbContext напрямую, когда им нужны оптимизированные read projections, filters, pagination и результаты в DTO format.

Обязанности repository

  • Загружать aggregate roots и дочерние данные, необходимые command handlers.
  • Добавлять, обновлять и удалять aggregates в соответствии с persistence model.
  • Инкапсулировать persistence queries, которые являются частью поведения aggregate или write orchestration.
  • Держать EF Core-specific операции вне domain project.
  • Инкапсулировать сложные queries, когда они относятся к write flow, включая LINQ, FromSql и вспомогательные projections.

Практические рекомендации

  • Используйте repositories для write use cases, которым нужно сохранять consistency aggregate.
  • Для чтения используйте query projections вместо загрузки полных aggregates только ради сборки DTO.
  • Держите методы repository явными; избегайте generic methods, которые открывают application произвольные persistence операции.
  • Проверяйте includes и tracking, когда handler изменяет aggregate, child collection или many-to-many relationship.
  • Открывайте только методы, необходимые domain и application, сохраняя repository aggregate-root-centric.

Unit of Work

IUnitOfWork является транзакционной границей, сгенерированной Lino. Он координирует persistence с EF Core, transaction control, публикацию domain events и сохранение в Outbox для integration events. Handlers используют эту abstraction, чтобы явно подтверждать изменения, вместо прямого вызова DbContext.SaveChangesAsync во всех местах application.

В простых сценариях сам DbContext часто достаточен как unit of work. Тем не менее Lino генерирует выделенную реализацию UnitOfWork, чтобы обеспечить согласованный контроль над transactions, events и интеграцией с Outbox.

Сгенерированные операции

  • SaveChangesAsync(cancellationToken): сохраняет изменения и публикует domain events, когда это настроено.
  • SaveChangesAsync(publishDomainEvents, cancellationToken): позволяет сохранять с публикацией domain events или без нее.
  • SaveChangesInTransactionAsync(cancellationToken): открывает transaction, сохраняет изменения и выполняет commit.
  • BeginTransactionAsync, CommitAsync и RollbackAsync: позволяют явно управлять transaction.
  • CommitOrRollbackAsync: выполняет commit или rollback transaction на основе Result.

Domain events и Outbox

Когда существуют domain events, Lino требует открытую transaction перед их публикацией. Это сделано намеренно. Если domain event создает integration events, эти events регистрируются в Outbox и сохраняются в той же transaction. Затем worker может обработать записи Outbox и опубликовать messages через настроенную messaging infrastructure.

Этот flow защищает consistency: когда command создает entities, которые поднимают events, и проект использует Outbox, handler должен сохранять через transaction, например с SaveChangesInTransactionAsync, или явно открыть и подтвердить transaction. Без этой transaction infrastructure должна завершиться ошибкой, а не разрешить ненадежный event flow.

Когда использовать каждый стиль save

  • Используйте SaveChangesAsync для прямой persistence без требования consistency между events и Outbox.
  • Используйте SaveChangesInTransactionAsync, когда domain events и persistence в Outbox должны быть частью одного commit.
  • Используйте BeginTransactionAsync, CommitAsync и RollbackAsync, когда handler имеет несколько шагов и должен принять итоговое решение на основе Result.

Практическое правило: если write operation публикует domain events, которые могут произвести integration events, держите database changes и Outbox records в одной transaction.

Managing Migrations

Lino оборачивает цикл migrations Entity Framework Core CLI-командами, которые знают service, module, architecture, provider, DbContext, startup project, version file и output location для scripts. Это снижает количество ошибок ручных команд и сохраняет migration metadata отслеживаемой моделью проекта Lino.

Migrations фиксируют эволюцию базы данных на основе изменений в entities, Value Objects, relationships, indexes и конфигурациях Entity Framework. Перед созданием migration скомпилируйте solution или хотя бы затронутый service, чтобы EF Core загрузил текущую модель.

Создать migration

lino database migrations add --service <ServiceName> --module <ModuleName>

Команда запрашивает service, module, текущую версию service в src/Services/<ServiceName>/version.txt и описание migration. Lino создает имя с использованием version и sequence, затем выполняет dotnet ef migrations add для корректных persistence project и startup project.

Lino также генерирует SQL script для migration с помощью dotnet ef migrations script. Script записывается под затронутым persistence project в versioned folder по шаблону:

Infrastructure.Persistence/Scripts/<Version>/<Sequence>_<Description>.sql
Infrastructure.Persistence/Scripts/v1.2.3/001_AddCustomerIsActive.sql

Примечание: помимо файлов .cs, сгенерированных Entity Framework, Lino генерирует соответствующий .sql script с DDL-инструкциями. Это упрощает review для infrastructure teams, DBAs и controlled deployment процессов.

Применить, вывести список, откатить и удалить

lino database migrations add --service <ServiceName> --module <ModuleName>
lino database migrations list --service <ServiceName> --module <ModuleName>
lino database migrations apply --service <ServiceName> --module <ModuleName>
lino database migrations revert --service <ServiceName> --module <ModuleName>
lino database migrations remove --service <ServiceName> --module <ModuleName>
  • add: создает migration для ожидающих изменений в модели.
  • list: выводит известные migrations для выбранного context.
  • apply: выполняет dotnet ef database update, чтобы применить migrations к настроенной базе данных.
  • revert: возвращает последний успешный migration path, когда workflow это допускает.
  • remove: удаляет последнюю запись созданной migration и сгенерированные EF files, когда применимо.

Каноническая группа команд: database migrations. Aliases, такие как db и migration, могут существовать для productivity, но документация должна предпочитать полную форму.

Что Lino выбирает за вас

  • В традиционных сервисах EF project: src/Services/<ServiceName>/Infrastructure.Persistence, а startup project: src/Services/<ServiceName>/Api.
  • В модульных сервисах EF project: src/Services/<ServiceName>/Modules/<ModuleName>/Infrastructure.Persistence, а startup project: src/Services/<ServiceName>/Host.
  • Context является сгенерированным ApplicationDbContext для выбранного service или module.
  • Таблица history migrations EF настраивается с Constants.Database.MigrationsHistoryTable и Constants.Database.Schema.

Рекомендуемый workflow

  1. Смоделируйте или измените entities, Value Objects, relationships, indexes или persistence configurations.
  2. Запустите build и исправьте ошибки компиляции перед генерацией migration.
  3. Выполните lino database migrations add для корректных service и module.
  4. Проверьте сгенерированные C# migration и SQL script перед применением.
  5. Примените migration в local или development environment и проверьте database schema.
  6. Закоммитьте migration files и SQL scripts вместе с изменением domain или application, которое потребовало изменение.

Checklist: проверьте migration diff, примените к local database, запустите build и протестируйте затронутый flow перед публикацией.

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