Services & Layers
Most real programs depend on things they don’t construct themselves: a database
connection, an HTTP client, a clock, a configuration value. Effect models these
dependencies as services, tracked in the third type parameter of
Effect<Success, Error, Requirements>. When an effect needs a service, that
requirement shows up in its type and stays there until you provide an
implementation.
import { Context, Effect } from "effect"
// A service is just a typed key into the "context" — the bag of dependencies// an effect can reach for. The class itself is the key.class Database extends Context.Service<Database, { query(sql: string): Effect.Effect<ReadonlyArray<unknown>>}>()("myapp/Database") {}
// Yielding the key gives you the implementation. Notice the requirement// `Database` appears in the effect's type — this program cannot run until a// `Database` is provided.const program: Effect.Effect<ReadonlyArray<unknown>, never, Database> = Effect.gen(function*() { const db = yield* Database return yield* db.query("SELECT * FROM users") })The compiler now enforces that program is given a Database before it runs.
You never thread services through function arguments by hand — Effect tracks
them for you and fails to compile if one is missing.
Services vs. Layers
Section titled “Services vs. Layers”There are two halves to dependency injection in Effect:
- A service is the interface and its context key. You declare it once with
Context.Service(orContext.Referencewhen it should have a default). - A layer is the recipe that builds the service’s implementation, possibly
acquiring resources and depending on other services. You construct layers with
Layer.effect/Layer.succeedand wire them together withLayer.provide.
Keeping these separate is what makes Effect code testable: the same program
runs against a real Database.layer in production and an in-memory stub in
tests, with no change to the program itself.
In this section
Section titled “In this section”- Services — define a service with
Context.Serviceand attach itslayer. - References —
Context.Referencefor defaults, config values, and feature flags that don’t need to be provided. - Managing layers — build layers
with
effect/succeed/ scoped acquisition and provide them. - Layer composition — combine
layers with
Layer.provideandLayer.provideMerge. - Layers from config — choose an
implementation at build time with
Layer.unwrap. - Keyed resources with LayerMap — manage a dynamic, cached set of per-key services.
- Managed runtime — bridge your
layers into non-Effect frameworks with
ManagedRuntime.
Configuration is covered in depth in Configuration, and resource lifecycles in Resource Management.