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
- Spec Alignment – Every document’s properties must reflect the corresponding
.spec.mddefinitions. - Explicit Identity – Prefer string IDs that match request contracts; consider
Guidwhen generated locally. - Minimal Surface – Keep document classes focused on persisted state; non-persisted logic belongs elsewhere.
- 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) tostring.Emptyto 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
IDocumentStorefrom DI configured in host plugins or tests. For test setup, see CompanyProfileAccessTests Marten configuration. - Avoid enabling
AutoCreate.Alloutside 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.CreateOrUpdateor 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
PostgresContainerfrom 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 Document –
CompanyPayrollProfiledemonstrates 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
- Update the corresponding
.spec.mdData Schema table. - Add the property to the document model with appropriate default value and nullability.
- Update accessor handlers to populate the property when storing/fetching.
- Extend integration tests to assert the new field persists and round-trips correctly.
- If the field needs indexing, update Marten configuration (e.g.,
.Schema.For<T>().Index(x => x.Property)). - Publish migration instructions if production data must be backfilled.
Introduce a New Document Type
- Create the model class under the relevant
Modelsfolder. - Update spec and interface projects with request/response types referencing the new document.
- Configure Marten (test + production) to include the new document.
- Implement handlers that store/fetch the document.
- 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.Allin production, which can mask migration issues. - Using
DateTimeinstead ofDateOnlyfor 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.