Repository F# setup
open Reified
open Reified.Refinements
open Reified.Result
open Reified.Schema.Json

Split a larger application

Namespaces organize names but do not stop references. Separate projects can prevent domain code from importing wire or infrastructure types at all.

Here, domain means the business types and rules. Application means the code that coordinates business operations. Infrastructure means database, filesystem, HTTP, and other operational implementations. The host starts the process and connects those parts.

Contracts are the wire records and version declarations used to exchange or store data. They are not the same as the domain types used by business code.

A useful starting point is:

MyApp.Contracts
MyApp.Domain
MyApp.Application
MyApp.Infrastructure
MyApp.Host

Keep the dependency direction small

Contracts  -> Reified.Schema
Domain     -> Reified.Result, Reified.Constraint, Reified.Refinements, Reified.Parse, optionally Reified.Schema
Application -> Domain
Infrastructure -> Application
Host -> Contracts, Domain, Application, Infrastructure

The exact project names do not matter. The useful restriction is that Domain cannot reference Contracts, Infrastructure, a database provider, or an HTTP host.

Put each concern in one place

Contracts contains public wire records, generated schemas, codecs, version chains, and explicit mappings at the data boundary.

Domain contains refined values, private aggregates, domain errors, and named transitions. It should not know whether a value arrived through JSON, a database row, or a message.

Application coordinates domain operations and declares required repositories or gateways. A record of those dependencies, passed in, is enough.

Infrastructure implements repositories and gateways. It may use filesystem, database, HTTP, clock, and other operational libraries that Domain cannot reference.

Host owns dependency injection, configuration, routing, process lifetime, and assembly of live environments.

Keep service-provider lookup at the host

Resolve container-managed objects while constructing the application environment. Application workflows then use typed fields or Service.get, not arbitrary lookups.

type AppEnv =
    { Bookings: IBookingRepository
      Clock: IClock }

let env =
    { Bookings = provider.GetRequiredService<IBookingRepository>()
      Clock = provider.GetRequiredService<IClock>() }

Business workflows now take AppEnv as a parameter, and tests supply a record of fakes.

Start smaller when appropriate

A small application can keep these as modules in one project. Preserve the same direction in file order and module interfaces, then split projects when accidental imports or unclear ownership begin to cause review problems.

Project separation is useful when it removes choices from normal development. It is not a goal by itself.