Part 8: Services Without the Ceremony
Dependency injection sounds heavier than it needs to.
Most of the time, the idea is small:
This code needs a thing. Give it the thing at the edge.
Effect calls the thing a service.
Name the service
class UserRepo extends Context.Tag("UserRepo")< UserRepo, { readonly findById: (id: number) => Effect.Effect<User, UserNotFound>; }>() {}This is the part where the syntax looks bigger than the idea.
The idea is just:
There is a service called UserRepo, and this is what it can do.
Use the service
const fetchUser = (id: number) => Effect.gen(function* () { const repo = yield* UserRepo; return yield* repo.findById(id); });Now the requirement slot includes UserRepo.
The function does not import a concrete database module.
It asks for the service it needs.
Provide it with a Layer
const TestUserRepo = Layer.succeed(UserRepo, { findById: (id) => Effect.succeed({ id, name: "Ada" }),});A layer says:
Here is how to satisfy that service.
Keep the point small
Services are not a reason to build a giant framework in your app.
They are useful when a dependency is real: database, config, clock, logger, API client.
For a pure helper, skip it.
My rule: if I would not fake it in a test, I probably do not need a service for it yet.