データ永続化の管理

Lino のデータ永続化は Clean Architecture の境界に沿って生成されます。ドメインモデルは Entity Framework Core から独立したままになり、データベースに関する関心事は Infrastructure.Persistence に置かれます。これにより、エンティティはビジネスルールに集中し、マッピング、provider 設定、migrations、repositories、transactions、初期化はインフラストラクチャ層に配置されます。


lino service new でサービスを作成すると、Lino はサービスのアーキテクチャや data provider など、永続化に関する重要な判断を記録します。現在の provider オプションは SqlServerPostgreSql です。従来型サービスでは、永続化はサービス直下に生成されます。モジュール型サービスでは、各モジュールが独自の Infrastructure.Persistence project と独自の ApplicationDbContext を持ちますが、データベースはサービスで共有されます。


従来型サービスでもモジュール型サービスでも、データベースはサービスごとに 1 つです。モジュール型サービスでは、各モジュールが同じデータベース内の個別の schema にマッピングされます。これにより、各 bounded context は論理的に分離された namespace を維持でき、機能的な分離、より明確な versioning、整理された migrations が可能になります。


このページでは、Lino が Entity Framework Core の設定、DbContext 登録、repositories、IUnitOfWork、transactions、Transactional Outbox flow、CLI で公開される migrations lifecycle をどのように整理するかを説明します。

重要: 生成された永続化コードは production-oriented scaffolding です。共有環境や production のデータベースに変更を適用する前に、mappings、constraints、indexes、delete behavior、transactions、migration scripts を必ず確認してください。

Entity Type Configurations

Lino は Persistence-Ignorant の原則に従います。ドメインエンティティはインフラストラクチャの詳細を知りません。ORM マッピングはすべて、Infrastructure.Persistence/Configurations に配置された IEntityTypeConfiguration<TEntity> を実装するクラスに置かれます。これにより、エンティティはビジネス上の振る舞いを表現し、インフラストラクチャはそれらをどのように保存するかを定義します。

設定ファイルは CLI で行った判断から生成されます。primary keys、strongly typed IDs、必須フィールド、文字列長、Value Objects、リレーションシップ、列挙型、監査フィールド、tenant fields、子エンティティなどです。生成結果は強力な出発点として扱うべきであり、レビューが不要な固定成果物ではありません。

設定が通常定義するもの

  • テーブルと schema: 現在のサービスまたはモジュールのテーブル名と schema。
  • Primary keys: エンティティ識別子、strongly typed IDs の変換、キー生成の振る舞い。
  • プロパティ: 必須フィールド、最大長、列型、enum 永続化、Value Objects の owned structures、ファイル metadata 列。
  • リレーションシップ: one-to-one、one-to-many、many-to-many、child collections、foreign keys、削除時の振る舞い。
  • インデックスと constraints: 一意性、lookup の performance、tenant-aware な一意性、データベースレベルの整合性。

グローバル規約

繰り返し使われる規約は、ModelConfiguration または同等の helpers に集約できます。たとえば decimal precision、collation、DateTime 変換、naming convention、global filters、監査可能プロパティ、multi-tenant フィールドの共通ルールなどです。

設定の適用方法

生成された ApplicationDbContextOnModelCreating で設定を適用します。既定では、Lino は Source Generators を使って、Reflection を使わず、より高いパフォーマンスで設定を登録します。assembly scanning を使用する場合、設定は次のようになります。

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

messaging が有効な場合、Lino は OutboxMessage の設定も適用します。これにより、integration events は worker が非同期に publish する前に、同じデータベース transaction 内で永続化できます。

レビュー checklist

  1. テーブル名、schemas、規約がモジュール境界に従っていることを確認します。
  2. migrations を生成する前に、文字列長、decimal precision、nullable 列、enums の永続化を確認します。
  3. 特に aggregate roots と子エンティティについて、リレーションシップと delete behavior を慎重に確認します。
  4. grids、filters、integrations、background jobs で使われる queries に合わせてインデックスを追加または調整します。
  5. migration を作成する前に build を実行し、EF Core が一貫したモデルを認識できることを確認します。

DbContexts

生成された ApplicationDbContext は、サービスまたはモジュールの永続化境界に対応する EF Core context です。マッピングされたエンティティの DbSet<TEntity> プロパティを公開し、IApplicationDbContext を実装します。これにより、query handlers と application services は、インフラストラクチャの具体クラスではなく、application abstraction に依存できます。

従来型サービスとモジュール型サービス

  • 従来型サービス: 永続化は src/Services/<ServiceName>/Infrastructure.Persistence に生成され、サービスの API が migrations の startup project として使われます。
  • モジュール型サービス: 各モジュールは独自の src/Services/<ServiceName>/Modules/<ModuleName>/Infrastructure.Persistence project と独自の ApplicationDbContext を持ちます。サービス host が migrations の startup project として使われます。

モジュール型サービスでは、この分離によって各 bounded context がコード上で明示されます。モジュールは、選択したアーキテクチャで同じサービスデータベース上で動作する場合でも、永続化モデルを独立して進化させられます。

provider の登録と設定

Lino は IHostApplicationBuilder の extension として永続化登録を生成します。生成コードは Unit of Work、repositories、domain services、ApplicationDbContext の pooled factory、scoped な ApplicationDbContext、そして IApplicationDbContext abstraction を登録します。

provider 固有の設定は、サービスで選択されたデータベースに応じて生成されます。

  • SQL Server サービスでは UseSqlServer(...)
  • PostgreSQL サービスでは UseNpgsql(...).UseSnakeCaseNamingConvention()
  • MigrationsHistoryTable(Constants.Database.MigrationsHistoryTable, Constants.Database.Schema) により、migration history を期待される table と schema に保存します。
  • provider 固有の constraint validators が登録され、データベース違反を一貫した application errors に変換します。

Tenant-aware contexts

モジュールに tenant-aware エンティティが含まれる場合、生成された context は tenant state と global query filters を含めることができます。この場合、scoped factory は現在の scope に合わせた context を作成し、handlers が読み書きのすべての箇所で tenant filters を手動で繰り返す必要をなくします。

Repositories

Lino は domain layer に repository interfaces を生成し、Infrastructure.Persistence/Repositories に具体実装を生成します。これにより依存方向が保たれます。domain は必要な persistence contract を定義し、infrastructure は Entity Framework Core を使って実装を提供します。

具体的な repositories は、サービス構造に応じて <ModuleName>/Infrastructure.Persistence.Repositories に置かれ、<ModuleName>/Domain.Repositories の interfaces を実装する場合もあります。重要なのは、application と domain が EF Core の具体的な詳細に直接依存しないことです。

Repositories は主に、aggregates を永続化する必要がある command handlers と domain-oriented flows で使われます。Query Handlers は、読み取りに最適化された projections、filters、pagination、DTO 形式の results が必要な場合、IApplicationDbContext を直接使えます。

repository の責務

  • command handlers に必要な aggregate roots と子データを読み込みます。
  • persistence model に従って aggregates を追加、更新、削除します。
  • aggregate の振る舞いまたは write orchestration の一部である persistence queries をカプセル化します。
  • EF Core 固有の操作を domain project の外に保ちます。
  • write flow に属する複雑な queries を、LINQ、FromSql、補助 projections を含めてカプセル化します。

実務上の指針

  • aggregate consistency を保つ必要がある write use cases では repositories を使います。
  • DTO を組み立てるためだけに完全な aggregates を読み込むのではなく、読み取りには query projections を使います。
  • repository methods は明示的に保ちます。application に任意の persistence operations を公開する generic methods は避けます。
  • handler が aggregate、child collection、many-to-many relationship を変更する場合は、includes と tracking を確認します。
  • domain と application に必要な methods だけを公開し、repository を aggregate-root-centric に保ちます。

Unit of Work

IUnitOfWork は Lino が生成する transactional boundary です。EF Core による永続化、transaction control、domain events の publish、integration events の Outbox への永続化を調整します。Handlers は application の各所で DbContext.SaveChangesAsync を直接呼ぶのではなく、この abstraction を使って変更を明示的に確定します。

単純なシナリオでは、DbContext 自体が unit of work として十分なことがよくあります。それでも Lino は、transactions、events、Outbox 連携を一貫して制御するために、専用の UnitOfWork implementation を生成します。

生成される operations

  • SaveChangesAsync(cancellationToken): 変更を保存し、設定されている場合は domain events を publish します。
  • SaveChangesAsync(publishDomainEvents, cancellationToken): domain events を publish するかどうかを選んで保存できます。
  • SaveChangesInTransactionAsync(cancellationToken): transaction を開き、変更を保存して commit します。
  • BeginTransactionAsyncCommitAsyncRollbackAsync: 明示的な transaction control を可能にします。
  • CommitOrRollbackAsync: Result に基づいて transaction を commit または rollback します。

domain events と Outbox

domain events が存在する場合、Lino はそれらを publish する前に開かれた transaction を要求します。これは意図的です。domain event が integration events を生成する場合、それらの events は Outbox に記録され、同じ transaction で永続化されます。その後、worker が Outbox records を処理し、設定された messaging infrastructure を通じて messages を publish できます。

この flow は consistency を保護します。command が events を発生させる entities を作成し、project が Outbox を使っている場合、handler は SaveChangesInTransactionAsync などで transaction 内に保存するか、transaction を明示的に開いて commit する必要があります。この transaction がない場合、infrastructure は信頼できない event flow を許すのではなく失敗するべきです。

save style の使い分け

  • events と Outbox の間で consistency が不要な直接的な永続化には SaveChangesAsync を使います。
  • domain events と Outbox への永続化を同じ commit に含める必要がある場合は SaveChangesInTransactionAsync を使います。
  • handler に複数の steps があり、Result に基づいて最終結果を決める必要がある場合は、BeginTransactionAsyncCommitAsyncRollbackAsync を使います。

実務ルール: write operation が integration events を生成しうる domain events を publish する場合、database changes と Outbox records を同じ transaction に保ってください。

Managing Migrations

Lino は、サービス、モジュール、アーキテクチャ、provider、DbContext、startup project、version file、script output location を理解する CLI commands で Entity Framework Core の migrations lifecycle を包みます。これにより手動コマンドのミスを減らし、migration metadata を Lino project model で追跡できます。

Migrations は、entities、Value Objects、relationships、indexes、Entity Framework 設定の変更に基づいてデータベースの進化を記録します。migration を作成する前に、solution または少なくとも対象サービスを compile し、EF Core が現在のモデルを読み込めるようにしてください。

migration を作成する

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

コマンドは service、module、src/Services/<ServiceName>/version.txt にある現在の service version、migration description を要求します。Lino は version と sequence を使って名前を作成し、正しい persistence project と startup project に対して dotnet ef migrations add を実行します。

Lino は dotnet ef migrations script を使って migration の SQL script も生成します。script は、影響を受ける persistence project の下に、次の pattern に従った versioned folder として書き込まれます。

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

注: Entity Framework が生成する .cs files に加えて、Lino は DDL statements を含む対応する .sql script を生成します。これにより、infrastructure teams、DBAs、controlled deploy processes によるレビューが容易になります。

適用、一覧、revert、remove

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: model の未反映の変更に対して migration を作成します。
  • list: 選択した context の既知の migrations を一覧表示します。
  • apply: dotnet ef database update を実行し、設定済み database に migrations を適用します。
  • revert: flow が許す場合、最後に成功した migration path を戻します。
  • remove: 該当する場合、最後に作成された migration record と生成された EF files を削除します。

canonical group は database migrations です。dbmigration などの aliases は productivity のために存在する場合がありますが、documentation では完全な形式を優先してください。

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 は、選択した service または module のために生成された ApplicationDbContext です。
  • EF migration history table は Constants.Database.MigrationsHistoryTableConstants.Database.Schema で設定されます。

推奨 workflow

  1. entities、Value Objects、relationships、indexes、または persistence settings をモデリングまたは変更します。
  2. migration を生成する前に build を実行し、compile errors を修正します。
  3. 正しい service と module に対して lino database migrations add を実行します。
  4. 適用する前に、生成された C# migration と SQL script を確認します。
  5. local または development environment で migration を適用し、database schema を確認します。
  6. 変更を必要とした domain または application の変更と一緒に、migration files と SQL scripts を commit します。

Checklist: publish する前に migration diff を確認し、local database に適用し、build を実行し、影響を受ける flow を test してください。

処理されていないエラーが発生しました。 再読み込み 🗙