Environment Slicing
Deep dive into the Record Pattern for dependency management.
In FsFlow, a workflow is not just a function; it is a description of work that needs an environment to run.
As your application grows, you might be tempted to create a single “God Object” (like AppEnv) that contains every service in your system: Database, Logger, API Gateways, and more. However, if every small workflow depends on this massive record, your code becomes hard to test and tightly coupled. You’d have to mock the entire universe just to test a simple calculation.
FsFlow provides two primary architectural styles to help you “slice” your dependencies so that each workflow only asks for what it actually needs.
This is the simplest way to start. You define your environment as a standard F# record. Workflows “read” or “project” the fields they need from this record. It is perfect for local helpers, internal logic, and smaller applications where record types are stable.
Read the Deep Dive: Environment Slicing
This style decouples your workflows from specific record types using interface-based contracts. A workflow says “I need a clock,” and it doesn’t care if that clock comes from a massive app runtime or a tiny test object. It is the best choice for public APIs, shared libraries, and large-scale systems.
Read the Deep Dive: Capabilities (CAPS)
These two patterns support different Architectural Styles:
For more details on these structures, see Architectural Styles.
| Feature | Record Pattern | CAPS Pattern |
|---|---|---|
| Typical Use | Local helpers, small apps | Public APIs, shared libraries |
| Coupling | Bound to a specific record type | Bound to an interface/contract |
| Simplicity | High (Standard F#) | Medium (Interface-based) |
| Flexibility | Moderate | Very High |
Start with the Record Pattern. It is the most idiomatic way to write F# and requires the least amount of boilerplate.
Move to the CAPS Pattern when:
Regardless of which style you choose, FsFlow provides a Capability module that works across both. These helpers let you read from the main Flow surface and from RuntimeContext without needing specific module prefixes.
let log message =
flow {
let! logger = Capability.service _.Logger
logger.Log message
}
Learn more about these polymorphic helpers in the Environment Slicing guide.
Deep dive into the Record Pattern for dependency management.
Deep dive into the interface-based CAPS pattern for decoupled dependencies.