Siamand

The services you write once

Chat, identity, files; most of what a project needs is not the project. On building a few generic services well and carrying them from one product to the next

Start a new project and write down everything it needs. Accounts and login. Uploading a file and getting it back. Messages between people. Notifications. Search. Some permission model.

Now cross out the parts that are actually about your product.

For most things I have built, the list that survives is embarrassingly short. The product is a thin layer of genuinely specific logic sitting on top of a pile of problems that every project has, and that I have solved before, slightly differently each time, each time worse than I thought.

So at some point the question stops being how to build these well and becomes how to build them once.

A service is generic when you could delete the product around it and it would still make sense.

What generic actually means

The test above is the whole discipline, and it is harsher than it sounds.

A file service stores bytes and gives them back. It does not know about album art, profile pictures or invoice PDFs. The moment it grows an endpoint called uploadAvatar, it has stopped being a file service and become part of one product, and you will not be able to lift it into the next one.

A chat service moves messages between participants in a conversation. It does not know whether a conversation is a support ticket, a game lobby or two people arguing about a recipe. Those are names the product gives. The service sees participants, conversations, messages, read state, maybe presence.

A security service issues and verifies credentials. It does not know what roles mean in your domain.

Generic services own a capability, not a feature. The product composes capabilities into features. That line is easy to write and gets attacked every single sprint, usually by me, usually with a very reasonable argument about how adding one field would save an afternoon.

Identity, and why the browser should not hold a token

This is the one where the reuse argument is strongest, because getting it right takes real effort and nobody wants to spend that effort twice.

A security service issues tokens, verifies them, rotates its signing keys, and revokes. Service-to-service calls carry a JWT, because a stateless, signed, self-describing token is genuinely good at that job: the receiving service verifies a signature and knows who is calling without asking anyone.

What a JWT is not good at is living in a browser. Once it is in the browser it is in storage somewhere, it is readable by any script that gets a foothold, and it stays valid until it expires because that is the entire point of a stateless token. Revocation turns into a blocklist, which is a session store with worse ergonomics and a straight face.

So put a backend-for-frontend in between. Each front end gets its own BFF. The browser authenticates against its BFF and receives an opaque session cookie: a random identifier, nothing inside it, HttpOnly and Secure. The BFF holds the real token server side and attaches it when it calls the services behind it.

If the token never reaches the browser, logging someone out is a delete, not a wish.

What that buys, beyond the obvious:

The cost is honest: another hop, another thing to deploy, and session state to store. Pay it anyway for anything facing the public internet.

Where the reuse actually happens

Reuse here is not a shared library and it is definitely not a shared database. It is a stable contract and the same image deployed again with different configuration, its own data, its own keys.

Which means the hard part is not writing the service. It is keeping the contract boring enough that a second project can adopt it without negotiating, and disciplined enough that the first project’s needs do not leak into it permanently.

Two rules I try to hold:

eXstream, deliberately small

I keep a tiny example around for exactly this conversation. eXstream is a music streaming app written in Xi, and it is useful here precisely because there is so little to it.

Four processes. A gateway that terminates requests and verifies the bearer token at the edge, so no service behind it re-implements that check. An auth service. A file service holding the audio in a bucket. A playlist service. A web player on top.

Look at which of those know what the product is. The playlist service does; playlists are the domain. The auth service does not. The file service really does not; it stores objects and streams bytes back, and it would be just as happy holding attachments for something that has nothing to do with music.

That is the whole point of the example. Two of the four services could move to a completely different project tomorrow with a configuration change, and the thing that makes the product a music app is a single service and a front end.

It also shows the smallest honest version of the identity story: verify once at the edge, carry the token inward, keep the services ignorant of how authentication was obtained.

The part where I argue against myself

This is a good pattern and it fails in specific ways, so here they are.

Shared services become shared risk. A bug in the file service is now a bug in four products, and an outage is four outages. That changes how carefully you deploy and how seriously you treat a version bump.

They also become a coordination point. If every change needs the one person who understands the security service, you have replaced duplicated code with a queue, and queues of people are more expensive than duplicated code.

And generic things decay. Each product pushes one small exception into them, each exception is reasonable on its own, and after two years you have a service that is generic in name and knows about invoices, avatars and album art.

None of this argues for rewriting identity in every project. It argues for a small number of these services, owned properly, with versioned contracts and someone willing to say no.

What it is really for

The reason I care about this is not architectural elegance. It is that the interesting part of any project is the small bit that is actually new, and that part is the first thing to get starved when you spend the first three weeks rebuilding login, uploads and messaging for the fourth time.

Write them once. Keep them ignorant of your domain. Then spend your attention on the thing nobody has built before.

#microservices#architecture#jwt#bff#backend#reuse