Skip to content
bitzorcas
中EN

Recipe

Practical Guide: Declarative Seed Data & Idempotent Initialization

Master seed data provisioning in BitzOrcas.Modern: implementing the ISeedStep contract, multi-tier environment gates (Global / Tenant / Demo / ProductionSafe), DAG topological dependency ordering, and automated idempotent seeding via --init-schema.

Last updated

In enterprise multi-tenant software, delivering new environments, automated CI test suites, and day-to-day local development rely heavily on database baseline state: platform-standard ISO currencies, system roles and permissions, demo law firms, and sample litigation matters must be populated reliably and idempotently. Traditional ad-hoc SQL scripts carry critical liabilities: repeated executions throw primary key constraint violations, fail to express inter-module dependency topologies, and cannot prevent demo artifacts from polluting production databases.

BitzOrcas.Modern establishes a declarative, tiered seeding architecture powered by ISeedStep and ISeedRunner:

  1. Unified Step Contract (ISeedStep): Each module declares its own seed steps, providing a globally unique SeedId, execution priority Order, monotonic Version, and explicit DependsOn prerequisites;
  2. Four-Tier Environment Scopes (SeedScope): Explicitly segregates Global (universal platform baselines), Tenant (default tenant settings), Demo (sample trial data), and ProductionSafe (additive-only production changes). Demo seeds are automatically blocked in Staging and Production;
  3. Topological DAG Resolution: At boot time, ISeedRunner evaluates the dependency graph across all registered steps to guarantee parent foreign keys are populated before dependent records;
  4. Unified Initialization CLI: Running dotnet run --project src/Hosts/BitzOrcas.Api -- --init-schema executes schema migrations followed by ordered seed steps.

This guide demonstrates implementing and registering “Sample Civil Litigation Matters (LegalDemoMattersSeedStep)” within the Legal domain module (BitzOrcas.Modules.Legal).

Seed Data Tiering & Pipeline Execution Flow

All EnvironmentsAll EnvironmentsDev / Demo OnlyProduction Environment

BitzOrcas Seed Runner (ISeedRunner)

Evaluate SeedScope Environment Gate

1. Global Platform Baselines
(Order: 100~199, Countries/Languages/Dictionaries)

2. Tenant & ProductionSafe
(Order: 200~299, System Roles/Permissions/Root Tenant)

3. Demo Sample Data
(Order: 300+, Sample Matters/Trial Clients)

Automatically Skip Demo Seeds
(Strict Production Anti-Pollution Rule)


Step 1: Implement the Module-Level ISeedStep Contract

Implement ISeedStep inside the module’s infrastructure layer. Inject the command repository for idempotent persistence and assign SeedScope.Demo to ensure non-production isolation:

src/Modules/Legal/BitzOrcas.Modules.Legal/Seeders/LegalDemoMattersSeedStep.cs
using System;
using System.Threading;
using System.Threading.Tasks;
using BitzOrcas.Domain.Abstractions;
using BitzOrcas.Domain.Results;
using BitzOrcas.Infrastructure.Seeders;
using BitzOrcas.Modules.Legal.Domain;
using Microsoft.Extensions.Logging;
namespace BitzOrcas.Modules.Legal.Seeders;
/// <summary>
/// Sample litigation matters seed step
/// </summary>
/// <remarks>
/// <para>Idempotently populates sample litigation matter dossiers in development and demo environments.</para>
/// <para>Depends on <c>identity_roles</c> to ensure designated lead counsel users exist.</para>
/// </remarks>
public sealed class LegalDemoMattersSeedStep : ISeedStep
{
private readonly ICommandRepository<MatterIntake, string> _repository;
private readonly ILogger<LegalDemoMattersSeedStep> _logger;
/// <summary>
/// Initializes sample matters seed step
/// </summary>
/// <param name="repository">Write-side command repository.</param>
/// <param name="logger">Structured logger.</param>
public LegalDemoMattersSeedStep(
ICommandRepository<MatterIntake, string> repository,
ILogger<LegalDemoMattersSeedStep> logger)
{
_repository = repository;
_logger = logger;
}
/// <summary>
/// Execution order: 300+ represents business demo tiers, executed after infrastructure and identity roles
/// </summary>
public int Order => 320;
/// <summary>
/// Globally unique seed identifier (snake_case recommended)
/// </summary>
public string SeedId => "legal_demo_matters";
/// <summary>
/// Scope classification: Demo seeds are automatically skipped in Staging and Production
/// </summary>
public SeedScope SeedScope => SeedScope.Demo;
/// <summary>
/// Monotonic version number for tracking schema drift
/// </summary>
public int Version => 1;
/// <summary>
/// List of prerequisite SeedIds that must execute prior to this step
/// </summary>
public string[] DependsOn => new[]
{
"identity_platform_tenants",
"identity_roles"
};
/// <summary>
/// Executes idempotent data seeding asynchronously
/// </summary>
public async Task ExecuteAsync(string environment, CancellationToken cancellationToken)
{
const string targetMatterId = "MAT-DEMO-2026-0001";
// 1. Strict idempotency pre-check: if target entity already exists, skip execution
var existing = await _repository.FindAsync(targetMatterId, cancellationToken);
if (existing.IsSuccess)
{
_logger.LogInformation("[LegalDemoMattersSeedStep] Sample matter {MatterId} already exists; skipping.", targetMatterId);
return;
}
// 2. Instantiate demo aggregate root via domain factory method
var demoMatter = MatterIntake.Create(
id: targetMatterId,
tenantId: "tenant-alpha-lawfirm",
matterCode: "CIV-2026-0081",
title: "Sample Litigation: Semiconductor Intellectual Property Dispute",
clientId: "CLI-DEMO-001",
leadLawyerId: "USR-LAWYER-01",
claimAmount: 12000000.00m);
if (demoMatter.IsFailure)
{
_logger.LogError("[LegalDemoMattersSeedStep] Failed to construct sample matter: {Error}", demoMatter.Error.Message);
return;
}
// 3. Persist aggregate root
var saveResult = await _repository.SaveAsync(demoMatter.Value, cancellationToken);
if (saveResult.IsSuccess)
{
_logger.LogInformation("[LegalDemoMattersSeedStep] Successfully seeded sample matter: {MatterId}", targetMatterId);
}
else
{
_logger.LogError("[LegalDemoMattersSeedStep] Failed to persist sample matter: {Error}", saveResult.Error.Message);
}
}
}

Step 2: Register SeedStep in Dependency Injection

Register the step as ISeedStep within module DI registration extensions:

src/Modules/Legal/BitzOrcas.Modules.Legal/LegalModuleExtensions.cs (Excerpt)
using BitzOrcas.Infrastructure.Seeders;
using BitzOrcas.Modules.Legal.Seeders;
using Microsoft.Extensions.DependencyInjection;
namespace BitzOrcas.Modules.Legal;
public static class LegalModuleExtensions
{
public static IServiceCollection AddLegalModuleSeeders(this IServiceCollection services)
{
// Register seed step for automatic discovery and DAG sorting by ISeedRunner
services.AddSingleton<ISeedStep, LegalDemoMattersSeedStep>();
return services;
}
}

Step 3: Execute Schema Initialization and Seeding

Run database schema migration and ordered seeding via the API Host CLI:

Execute database schema initialization and seed data loading
# 1. Production safe mode: migrates tables and populates Global, Tenant, and ProductionSafe seeds
dotnet run --project src/Hosts/BitzOrcas.Api -- --init-schema
# 2. Schema migration only (skips all seeding steps)
dotnet run --project src/Hosts/BitzOrcas.Api -- --init-schema --no-seed
# 3. Development environment reset: updates schema and re-seeds demo passwords
USER__ADMIN__PASSWORD="YourStrongPassword123!" dotnet run --project src/Hosts/BitzOrcas.Api -- --init-schema --reset-demo-passwords

Summary

The declarative seed architecture powered by ISeedStep provides robust enterprise guarantees:

  • Strict Idempotency: Primary key pre-checks and differential merges guarantee repeated runs never throw constraint errors;
  • Zero Production Pollution: SeedScope.Demo is gated strictly by the runner, preventing test data from ever entering production databases;
  • Topological DAG Safety: The DependsOn dependency graph prevents foreign key violations caused by non-deterministic module loading order.

100%

Scroll or use controls to zoom · drag when enlarged · double-click for 100% / 200%