esteban@devtrillo:~/blog/writing$
← cd -
$ cat learning-effect-8-services-without-the-ceremony.md

Part 8: Services Without the Ceremony

Jul 17, 2026·1 min read
$ tail -f learning-effect-8-services-without-the-ceremony/readers
connecting…

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.

What does the service describe?

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.

What would you swap in a test?

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.

NOTE

My rule: if I would not fake it in a test, I probably do not need a service for it yet.