Repository F# setup
open Reified
open Reified.Refinements
open Reified.ResultBuild a private aggregate
Use a private aggregate when every value passed through business code must satisfy a relationship between fields.
An aggregate is a group of related values that the application creates and updates as one thing. An invariant is a rule that must remain true for every valid instance of that thing.
A booking is a typical example. Start <= End is not a rule for one field. A public record lets any caller bypass the
rule with a literal or record-copy update.
Define an editable draft
The draft is intentionally untrusted. Forms, tests, and mapping code can assemble and edit it freely. This page uses
DateTimeOffset rather than the DateOnly Model Legal Transitions and
Construction Guarantees use for the same shape, since this page goes on to
build a schema over the fields, and DateOnly's canonical schema is unavailable on every target Reified.Schema
ships for; the pattern itself is unaffected by that choice.
open System
type BookingDraft =
{ Start: DateTimeOffset
End: DateTimeOffset }
[<RequireQualifiedAccess>]
type BookingError =
| EndBeforeStart
SystemFsLiveDocsGeneratedPage33_10EA2B4D0E31.BookingDraftStart: DateTimeOffsetSystem.DateTimeOffsetRepresents a point in time, typically expressed as a date and time of day, relative to Coordinated Universal Time (UTC).
End: DateTimeOffsetMicrosoft.FSharp.Core.RequireQualifiedAccessAttributeThis attribute is used to indicate that references to the elements of a module, record or union type require explicit qualified access. Attributes
FsLiveDocsGeneratedPage33_10EA2B4D0E31.BookingErrorEndBeforeStartRequires & types
Requires: open SystemHide the aggregate representation
Keep the record private and expose functions that return only valid bookings.
open System
type BookingDraft =
{ Start: DateTimeOffset
End: DateTimeOffset }
[<RequireQualifiedAccess>]
type BookingError =
| EndBeforeStart
type Booking =
private
{ Start: DateTimeOffset
End: DateTimeOffset }
[<RequireQualifiedAccess>]
module Booking =
let create (draft: BookingDraft) =
if draft.Start <= draft.End then
Ok
{ Start = draft.Start
End = draft.End }
else
Error BookingError.EndBeforeStart
let start booking = booking.Start
let finish booking = booking.End
let toDraft booking =
{ Start = booking.Start
End = booking.End }
SystemFsLiveDocsGeneratedPage34_10EA2B4D0E31.BookingDraftStart: DateTimeOffsetSystem.DateTimeOffsetRepresents a point in time, typically expressed as a date and time of day, relative to Coordinated Universal Time (UTC).
End: DateTimeOffsetMicrosoft.FSharp.Core.RequireQualifiedAccessAttributeThis attribute is used to indicate that references to the elements of a module, record or union type require explicit qualified access. Attributes
FsLiveDocsGeneratedPage34_10EA2B4D0E31.BookingErrorEndBeforeStartFsLiveDocsGeneratedPage34_10EA2B4D0E31.BookingFsLiveDocsGeneratedPage34_10EA2B4D0E31.BookingModulecreate: BookingDraft -> Result<Booking,BookingError>draft: BookingDraft(<=): 'T -> 'T -> boolStructural less-than-or-equal comparison The first parameter. The second parameter. The result of the comparison. 5 <= 1 // Evaluates to false 5 <= 5 // Evaluates to true [1; 5] <= [1; 6] // Evaluates to true
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
start: Booking -> DateTimeOffsetbooking: Bookingfinish: Booking -> DateTimeOffsettoDraft: Booking -> BookingOutside this module, callers cannot construct Booking or use { booking with End = value }.
Build the schema through the same constructor
A schema is Reified's typed description of input shape, field constraints, and construction. It parses boundary fields,
then calls the authoritative constructor. create, the accessors, and toDraft are repeated here from the module
above so this fragment compiles on its own; in a real Booking.fs the schema binding joins the same module as
those functions, not a second copy of it:
open System
open Reified
open Reified.SchemaDSL
type BookingDraft =
{ Start: DateTimeOffset
End: DateTimeOffset }
[<RequireQualifiedAccess>]
type BookingError =
| EndBeforeStart
type Booking =
private
{ Start: DateTimeOffset
End: DateTimeOffset }
[<RequireQualifiedAccess>]
module Booking =
let create (draft: BookingDraft) =
if draft.Start <= draft.End then
Ok
{ Start = draft.Start
End = draft.End }
else
Error BookingError.EndBeforeStart
let start booking = booking.Start
let finish booking = booking.End
let toDraft booking =
{ Start = booking.Start
End = booking.End }
let schema : Schema<Booking> =
schema<Booking> {
fieldAs "start" start
fieldAs "end" finish
constructResult (fun start finish ->
create { Start = start; End = finish }
|> Result.mapError (function
| BookingError.EndBeforeStart -> "End must not be before start."))
}
SystemReifiedReified.SchemaDSLThe concise schema-definition vocabulary: the record computation expression, its field and constructor forms, and the collection-schema operations. Optional and opt-in, in the same shape as Reified.DataDSL and Reified.ConstraintDSL: open Reified.Schema for Schema, then open Reified.SchemaDSL for this vocabulary. There is no constraint catalogue here. One Constraint vocabulary serves direct checking, refinement, and Schema, so a field block reaches for Constraint.email or an opened Reified.ConstraintDSL exactly as standalone code does. Boundary supply is Schema-owned and stays here as mustSupply and mayOmit.
FsLiveDocsGeneratedPage35_10EA2B4D0E31.BookingDraftStart: DateTimeOffsetSystem.DateTimeOffsetRepresents a point in time, typically expressed as a date and time of day, relative to Coordinated Universal Time (UTC).
End: DateTimeOffsetMicrosoft.FSharp.Core.RequireQualifiedAccessAttributeThis attribute is used to indicate that references to the elements of a module, record or union type require explicit qualified access. Attributes
FsLiveDocsGeneratedPage35_10EA2B4D0E31.BookingErrorEndBeforeStartFsLiveDocsGeneratedPage35_10EA2B4D0E31.BookingFsLiveDocsGeneratedPage35_10EA2B4D0E31.BookingModulecreate: BookingDraft -> Result<Booking,BookingError>draft: BookingDraft(<=): 'T -> 'T -> boolStructural less-than-or-equal comparison The first parameter. The second parameter. The result of the comparison. 5 <= 1 // Evaluates to false 5 <= 5 // Evaluates to true [1; 5] <= [1; 6] // Evaluates to true
OkRepresents an OK or a Successful result. The code succeeded with a value of 'T.
ErrorRepresents an Error or a Failure. The code failed with a value of 'TError representing what went wrong.
start: Booking -> DateTimeOffsetbooking: Bookingfinish: Booking -> DateTimeOffsettoDraft: Booking -> Bookingschema: Schema<Booking>Reified.Schema`1Describes a typed value's portable structure and construction for schema interpreters. A schema records shape and construction metadata without tying that metadata to input parsing, diagnostics, validation, codecs, UI generation, or workflow execution. Primitive, collection, optional, union, refined, and record declarations all produce Schema<'value>. Record declarations use the schema<'value> { } computation expression. Each field may contain withSchema, constrain, constraints, refine, and validate operations before the declaration finishes with construct or constructResult.
schema: SchemaCeBuilder.SchemaBuilder<'model>Record-schema computation expression.
fieldAs: string -> ('model -> 'value) -> field<'model,'value>Declares a field under an explicit wire name, for when the name differs from the property and for portable code. field _.Email derives the wire name from the property; reach for fieldAs when the wire name is not the camelCased property name, or when the code must also compile under Fable, which cannot run the quotation the derived form reads. Explicit names are never transformed. type Signup = { Email: string } fieldAs "email_address" (fun (s: Signup) -> s.Email)
constructResult: 'constructor -> SchemaCeBuilder.CheckedConstructorStep<'model,'constructor>Closes a record schema with a checked constructor.
start: DateTimeOffsetfinish: DateTimeOffset(|>): 'T1 -> ('T1 -> 'U) -> 'UApply a function to a value, the value being on the left, the function on the right The argument. The function. The function result. let doubleIt x = x * 2 3 |> doubleIt // Evaluates to 6
Reified.Result.ResultModuleFail-fast helpers over the standard F# Result type.
mapError: ('a -> 'b) -> Result<'c,'a> -> Result<'c,'b>Maps the error value of a result.
Both direct construction and boundary parsing now use Booking.create.
match (Schema.parse Booking.schema raw) with
| Ok booking -> save booking
| Error diagnostics -> display diagnosticsUse an F# signature file for a hard interface
For an important domain module, an .fsi file makes the representation opaque even if the implementation uses a
normal record. It also gives reviewers a short list of allowed operations.
// Booking.fsi
namespace MyApp.Domain
open System
open Reified
type BookingDraft =
{ Start: DateTimeOffset
End: DateTimeOffset }
[<RequireQualifiedAccess>]
type BookingError =
| EndBeforeStart
type Booking
[<RequireQualifiedAccess>]
module Booking =
val create: BookingDraft -> Result<Booking, BookingError>
val start: Booking -> DateTimeOffset
val finish: Booking -> DateTimeOffset
val toDraft: Booking -> BookingDraft
val schema: Schema<Booking>Place Booking.fsi immediately before Booking.fs in the project file:
<ItemGroup>
<Compile Include="Booking.fsi" />
<Compile Include="Booking.fs" />
</ItemGroup>
The compiler rejects implementation members or representations that leak through the signature.
When not to use this pattern
Keep a public record when callers are expected to create arbitrary drafts and trust exists only after one operation. Use refined field types when every invariant is local to a field; they preserve normal record-copy syntax.
Private aggregates pay off when many modules rely on the same cross-field fact or when an invalid update would be hard to trace back to its source.