implementation reference

Marten Document Modeling

Defines conventions for authoring Marten document models and store configuration in src/services/accessor/*/.Service, aligning schema with accessor specs and ensuring persistence guardrails.

Marten Document Modeling Playbook

This skill captures how Marten documents are designed, configured, and synchronized with accessor specifications.

When to Use

  • Creating or updating document classes under *.Service/Models
  • Evolving schema to support new fields in accessor specs
  • Adjusting Marten configuration during service startup or tests
  • Validating persistence behavior before code generation changes

Core Principles

  1. Spec Alignment – Every document’s properties must reflect the corresponding .spec.md definitions.
  2. Explicit Identity – Prefer string IDs that match request contracts; consider Guid when generated locally.
  3. Minimal Surface – Keep document classes focused on persisted state; non-persisted logic belongs elsewhere.
  4. Migration Awareness – Default to additive changes; coordinate destructive changes with DBA and data migration strategy.

Modeling Workflow

1. Locate or Create Model

  • Document models live under src/services/accessor/<facet>/Lista.Accessor.<Facet>.Service/Models.
  • Reference examples like Company payroll profile model.

2. Shape the Class

  • Use auto-properties with public setters; Marten relies on property setters for serialization.
  • Initialize reference types (string) to string.Empty to avoid null issues (see existing models).
  • Mirror optional fields using nullable types (string?, DateOnly?).
  • Keep type choices consistent with spec guidelines (DateOnly for dates, decimal for monetary values).
  • Add XML comments when the field’s purpose is not obvious for future contributors.

3. Synchronize with Specifications

  • Ensure every property listed in the spec’s Data Schema table is present.
  • If the spec adds validation (e.g., max length), capture it via data annotations or service-level validation (Marten does not enforce it automatically).
  • Update any DTO mappings in the accessor service to populate new fields.
  • When properties are removed or renamed, coordinate spec, service, and model updates in the same change set.

4. Configure Marten Store

  • Services usually obtain an IDocumentStore from DI configured in host plugins or tests. For test setup, see CompanyProfileAccessTests Marten configuration.
  • Avoid enabling AutoCreate.All outside test environments. Production plugins should specify explicit schema updates.
  • When introducing new document types, register them if using projections or advanced features.
  • For production, prefer AutoCreate.CreateOrUpdate or bespoke migrations to control schema drift.

5. Schema Evolution Checklist

  • Spec updated with new properties/constraints
  • Document class updated with new members and default values
  • Accessor service mapping adjusted for new properties
  • Integration tests extended to cover new data
  • Database migration plan documented if schema-breaking
  • Marten store configuration updated if default schema or tenancy changes

6. Testing Guidance

  • Write or update integration tests that store and fetch documents using the new fields (see Profile accessor tests).
  • Use PostgresContainer from Lista.Common.Testing to provision clean databases per test run.
  • Seed sample data inside tests using the same models to validate serialization/deserialization symmetry.
  • Consider adding regression tests for backward compatibility when evolving schemas.

Example Patterns

  • Simple DocumentCompanyPayrollProfile demonstrates required string fields with defaults.
  • Composite Document – For more complex structures, mirror nested spec tables using nested classes or List<T> properties.
  • Immutable Considerations – If future requirements demand immutability, supply constructors and mark properties with private setters while ensuring Marten serialization is configured accordingly.
  • Soft Deletes / Status Flags – Represent logical deletion with boolean flags rather than removing documents unless a purge job exists.

Task Playbooks

Add a New Field

  1. Update the corresponding .spec.md Data Schema table.
  2. Add the property to the document model with appropriate default value and nullability.
  3. Update accessor handlers to populate the property when storing/fetching.
  4. Extend integration tests to assert the new field persists and round-trips correctly.
  5. If the field needs indexing, update Marten configuration (e.g., .Schema.For<T>().Index(x => x.Property)).
  6. Publish migration instructions if production data must be backfilled.

Introduce a New Document Type

  1. Create the model class under the relevant Models folder.
  2. Update spec and interface projects with request/response types referencing the new document.
  3. Configure Marten (test + production) to include the new document.
  4. Implement handlers that store/fetch the document.
  5. Write integration tests covering lifecycle operations.

Acceptance Criteria

  • Document classes reflect the latest accessor specifications without missing or extra persisted fields.
  • Marten configuration avoids accidental schema drift in production environments.
  • Integration tests confirm documents can be created, read, and mutated with new or updated fields.
  • Migration implications are identified and communicated for non-additive changes.

Common Pitfalls

  • Adding properties to models without updating specs, causing drift between documentation and implementation.
  • Forgetting to initialize collections, resulting in null reference exceptions when serialized.
  • Relying on AutoCreate.All in production, which can mask migration issues.
  • Using DateTime instead of DateOnly for date-only fields, leading to unintended time zones.
  • Neglecting to update DTO mappings, leaving new fields unused despite being persisted.

Follow this playbook to keep Marten documents consistent, maintainable, and aligned with the Lista Payroll System’s accessor architecture.