Recommended patterns

These pages show how to use Schema with ordinary F# modules and projects. They focus on problems that appear as an application grows: public records bypassing checks, wire DTOs entering business code, and updates breaking invariants.

Use only the strength of guarantee the code needs. A plain record is often right for a wire payload or edit form. A private type is useful when many callers must rely on the same invariant without checking it again.

  • Build a private aggregate — keep record syntax inside the owning module while callers see only safe construction and update functions.
  • Model legal transitions — replace unrestricted record updates with named operations and typed refusals.
  • Separate wire and domain models — generate permissive wire schemas during the build, then admit them into hand-written domain types.
  • Split a larger application — use project references to stop boundary and infrastructure types from reaching the domain.
  • Test schema guarantees — study the repository-only FsCheck adapter pattern for testing constructors, transitions, codecs, and migrations.

Start with Construction Guarantees if you are deciding whether a public record, refined field, or private aggregate fits the model.