Création d'un service avec Effect v4

Apprenez à créer des services avec Context.Service, les clés IdentityComposer et la composition de Layer dans Effect v4.

Spar Skills Guide Bot
DeveloppementIntermédiaire
1022/07/2026
Claude CodeCursorWindsurfCopilot
#effect#context-service#layer-composition#dependency-injection#typescript

Recommandé pour


name: effect-services description: > Creating services with Context.Service, IdentityComposer keys, and Layer composition. Trigger on: new service, service definition, Layer wiring, dependency injection, Context. version: 0.1.0 status: active

Creating a Service (Effect v4)

Step 1: Create the Identity

Every service needs a unique key from @beep/identity/packages. Never use plain strings.

// WHY: IdentityComposer produces branded strings with compile-time path validation.
// The tagged template literal validates segment characters at runtime.
import { $PackageNameId } from "@beep/identity/packages"

const $I = $PackageNameId.create("relative/path/to/file/from/package/src")
//                        ↑ package composer      ↑ file-local composer

Step 2: Define the Service Class

Use Context.Service<Self, Shape>()(identityKey). Note: type params FIRST, then key in second call.

import { Effect, Context, Layer } from "effect"

// WHY: Class syntax gives you a nominal type (Self) + the service shape in one declaration.
// The $I template tag produces a branded IdentityString under the file-local path.
class Notifications extends Context.Service<Notifications, {
  readonly notify: (msg: string) => Effect.Effect<void>
}>()($I`Notifications`) {}

Step 3: Add the Constructor

Use an explicit constructor effect and Layer.effect(...). Parameterized or reusable constructor functions should be named Effect.fn("Service.make"). Zero-arg constructor values may stay Effect.gen(...).pipe(Effect.withSpan("Service.make")) to avoid immediate Effect.fn() IIFEs.

const makeNotifications = Effect.gen(function*() {
  const config = yield* AppConfig
  return {
    notify: Effect.fn("Notifications.notify")(function*(msg: string) {
      yield* Effect.annotateCurrentSpan({ message_length: msg.length })
      yield* Effect.logInfo({ message: "notification emitted" }).pipe(
        Effect.annotateLogs({ service: "notifications" })
      )
      return yield* Effect.log(`[${config.prefix}] ${msg}`)
    })
  }
}).pipe(Effect.withSpan("Notifications.make"))

class Notifications extends Context.Service<Notifications, {
  readonly notify: (msg: string) => Effect.Effect<void>
}>()($I`Notifications`) {
  // WHY: Explicit layer construction. Wire deps with Layer.provide, not `dependencies`.
  static layer = Layer.effect(this, makeNotifications).pipe(
    Layer.provide(AppConfig.layer)
  )
}

Step 4: Consume the Service

Prefer yield* over .use() — it makes dependencies visible at the call site.

Exception: in callback-only APIs (for example SchemaTransformation.transform* decode/encode callbacks) where yield* is not available, use Context.Service.use(...) directly.

// WHY: yield* in Effect.fn makes the Notifications dependency appear in the R channel.
const sendWelcome = Effect.fn("sendWelcome")(function*(userId: string) {
  const notifications = yield* Notifications
  yield* notifications.notify(`Welcome ${userId}`)
})

const JsoncTextToUnknown = S.String.pipe(
  S.decodeTo(
    S.Unknown,
    SchemaTransformation.transformOrFail({
      decode: (content) => JsoncCodecService.use((service) => service.parseUnknown(content)),
      encode: (value) => Effect.fail(`unsupported encode: ${value}`)
    })
  )
)

Step 5: Compose Layers

Wire the full dependency graph at the application entry point.

// WHY: MemoMap deduplicates layers by reference. Same const = built once.
// No intermediate bundles needed — just provide each layer directly.
const AppLayer = Layer.mergeAll(
  Notifications.layer,
  UserService.layer
)

const program = myEffect.pipe(Effect.provide(AppLayer))

Verify

  1. yield* ServiceName compiles — the R channel includes the service.
  2. Layer.effect(Service, constructorEffect) compiles — shape matches.
  3. No Context.Tag, Effect.Tag, Effect.Service, or .Default anywhere in your code.
  4. All service keys use $I\Name`` template tags, not string literals.
  5. Reusable service methods use named Effect.fn("Service.method").
  6. Constructor flows are observable with Effect.withSpan(...) plus structured log annotations where the path matters.
Skills similaires