1# Services: `Context.Tag` → `Context.Service`
2
3In v3, services were defined using `Context.Tag`, `Context.GenericTag`,
4`Effect.Tag`, or `Effect.Service`. In v4, all of these have been replaced by
5`Context.Service`.
6
7The underlying runtime data structure is a typed map from service identifiers to
8their implementations.
9
10## Defining Services
11
12**v3: `Context.GenericTag`**
13
14```ts
15import { Context } from "effect"
16
17interface Database {
18 readonly query: (sql: string) => string
19}
20
21const Database = Context.GenericTag<Database>("Database")
22```
23
24**v4: `Context.Service` (function syntax)**
25
26```ts
27import { Context } from "effect"
28
29interface Database {
30 readonly query: (sql: string) => string
31}
32
33const Database = Context.Service<Database>("Database")
34```
35
36## Class-Based Services
37
38**v3: `Context.Tag` class syntax**
39
40```ts
41import { Context } from "effect"
42
43class Database extends Context.Tag("Database")<Database, {
44 readonly query: (sql: string) => string
45}>() {}
46```
47
48**v4: `Context.Service` class syntax**
49
50```ts
51import { Context } from "effect"
52
53class Database extends Context.Service<Database, {
54 readonly query: (sql: string) => string
55}>()("Database") {}
56```
57
58Note the difference in argument order: in v3, the identifier string is passed to
59`Context.Tag(id)` before the type parameters. In v4, the type parameters come
60first via `Context.Service<Self, Shape>()` and the identifier string is
61passed to the returned constructor `(id)`.
62
63## `Effect.Tag` Accessors → `Context.Service` with `use`
64
65v3's `Effect.Tag` provided proxy access to service methods as static properties
66on the tag class (accessors). This allowed calling service methods directly
67without first yielding the service:
68
69```ts
70// v3 — static accessor proxy
71const program = Notifications.notify("hello")
72```
73
74This pattern had significant limitations. The proxy was implemented via mapped
75types over the service shape, which meant **generic methods lost their type
76parameters**. A service method like `get<T>(key: string): Effect<T>` would
77have its generic erased when accessed through the proxy, collapsing to
78`get(key: string): Effect<unknown>`. For the same reason, overloaded signatures
79were not preserved.
80
81In v4, accessors are removed. The most direct replacement is `Service.use`,
82which receives the service instance and runs a callback:
83
84**v3**
85
86```ts
87import { Effect } from "effect"
88
89class Notifications extends Effect.Tag("Notifications")<Notifications, {
90 readonly notify: (message: string) => Effect.Effect<void>
91}>() {}
92
93// Static proxy access
94const program = Notifications.notify("hello")
95```
96
97**v4 — `use`**
98
99```ts
100import { Context, Effect } from "effect"
101
102class Notifications extends Context.Service<Notifications, {
103 readonly notify: (message: string) => Effect.Effect<void>
104}>()("Notifications") {}
105
106// use: access the service and call a method in one step
107const program = Notifications.use((n) => n.notify("hello"))
108```
109
110`use` takes an effectful callback `(service: Shape) => Effect<A, E, R>` and
111returns an `Effect<A, E, R | Identifier>`. `useSync` takes a pure callback
112`(service: Shape) => A` and returns an `Effect<A, never, Identifier>`. Both
113return Effects — `useSync` just allows the accessor function itself to be
114synchronous:
115
116```ts
117// ┌─── Effect<void, never, Notifications>
118// ▼
119const program = Notifications.use((n) => n.notify("hello"))
120
121// ┌─── Effect<number, never, Config>
122// ▼
123const port = Config.useSync((c) => c.port)
124```
125
126**Prefer `yield*` over `use` in most cases.** While `use` is a convenient
127one-liner, it makes it easy to accidentally leak service dependencies into
128return values. When you call `use`, the service is available inside the
129callback but the dependency is not visible at the call site — making it harder
130to track which services your code depends on. Using `yield*` in a generator
131makes dependencies explicit and keeps service access co-located with the rest
132of your effect logic:
133
134```ts
135const program = Effect.gen(function*() {
136 const notifications = yield* Notifications
137 yield* notifications.notify("hello")
138 yield* notifications.notify("world")
139})
140```
141
142## `Effect.Service` → `Context.Service` with `make`
143
144v3's `Effect.Service` allowed defining a service with an effectful constructor
145and dependencies inline. In v4, use `Context.Service` with a `make` option.
146
147**v3**
148
149In v3, `Effect.Service` automatically generated a `.Default` layer from the
150provided constructor, and wired `dependencies` into it:
151
152```ts
153import { Effect, Layer } from "effect"
154
155class Logger extends Effect.Service<Logger>()("Logger", {
156 effect: Effect.gen(function*() {
157 const config = yield* Config
158 return { log: (msg: string) => Effect.log(`[${config.prefix}] ${msg}`) }
159 }),
160 dependencies: [Config.Default]
161}) {}
162
163// Logger.Default is auto-generated: Layer<Logger, never, never>
164// (dependencies are already wired in)
165const program = Effect.gen(function*() {
166 const logger = yield* Logger
167 yield* logger.log("hello")
168}).pipe(Effect.provide(Logger.Default))
169```
170
171**v4**
172
173In v4, `Context.Service` with `make` stores the constructor effect on the
174class but does **not** auto-generate a layer. Define layers explicitly using
175`Layer.effect`:
176
177```ts
178import { Context, Effect, Layer } from "effect"
179
180class Logger extends Context.Service<Logger>()("Logger", {
181 make: Effect.gen(function*() {
182 const config = yield* Config
183 return { log: (msg: string) => Effect.log(`[${config.prefix}] ${msg}`) }
184 })
185}) {
186 // Build the layer yourself from the make effect
187 static readonly layer = Layer.effect(this, this.make).pipe(
188 Layer.provide(Config.layer)
189 )
190}
191```
192
193The `dependencies` option no longer exists. Wire dependencies via
194`Layer.provide` as shown above.
195
196Note: v4 adopts the convention of naming layers with `layer` (e.g.
197`Logger.layer`) instead of v3's `Default` or `Live`. Use `layer`
198for the primary layer and descriptive suffixes for variants (e.g.
199`layerTest`, `layerConfig`).
200
201## References (Services with Defaults)
202
203**v3: `Context.Reference`**
204
205```ts
206import { Context } from "effect"
207
208class LogLevel extends Context.Reference<LogLevel>()("LogLevel", {
209 defaultValue: () => "info" as const
210}) {}
211```
212
213**v4: `Context.Reference`**
214
215```ts
216import { Context } from "effect"
217
218const LogLevel = Context.Reference<"info" | "warn" | "error">("LogLevel", {
219 defaultValue: () => "info" as const
220})
221```
222
223## Quick Reference
224
225| v3 | v4 |
226| ------------------------------------- | --------------------------------------- |
227| `Context.GenericTag<T>(id)` | `Context.Service<T>(id)` |
228| `Context.Tag(id)<Self, Shape>()` | `Context.Service<Self, Shape>()(id)` |
229| `Effect.Tag(id)<Self, Shape>()` | `Context.Service<Self, Shape>()(id)` |
230| `Effect.Service<Self>()(id, opts)` | `Context.Service<Self>()(id, { make })` |
231| `Context.Reference<Self>()(id, opts)` | `Context.Reference<T>(id, opts)` |
232| `Context.make(tag, impl)` | `Context.make(tag, impl)` |
233| `Context.get(ctx, tag)` | `Context.get(map, tag)` |
234| `Context.add(ctx, tag, impl)` | `Context.add(map, tag, impl)` |
235| `Context.mergeAll(...)` | `Context.mergeAll(...)` |