---
title: "Why almost everything I ship is a library"
date: "2025-02-01T06:00:00.000Z"
categories: "Systems"
canonical: "https://xraph.com/writing/library-not-a-service"
---

# Abstract

Job queues, webhook delivery, authorisation and billing as libraries you compile in rather than services you operate. The reasoning, and the three places it fails.

## A pattern I noticed afterwards

Around the eighth module I realised every README had independently converged on the same sentence. A library, not a service. Import it, compose it, own your infrastructure.

I had not planned that. It came from making the same call repeatedly for reasons that felt local each time.

## What a service costs that a library does not

Every self-hosted service is an operational unit. A deployment, a health check, a network path, credentials, a backup story, a version skew policy against its clients, an upgrade window, and somebody who knows how to restart it at the weekend [4]. That cost is roughly constant regardless of how much of it you use.

For a job queue, a webhook sender or an authorisation check, it frequently exceeds the thing itself. A team of four running a workflow engine as a separate deployment spends more on the engine's operations than on the workflows.

The second cost bit harder. An authorisation check as a remote call means every request depends on another service being reachable, so you build a cache, and the cache has an invalidation bug. In process there is no third thing to be down.

## What you give up

1. **Polyglot access.** A Go library is for Go programs. If half your stack is Python this is a hard stop, and the answer is to run it as a service, for which one of these libraries is a reasonable foundation.
2. **Independent scaling.** Workers scale with the process they live in. Running the same binary in a worker-only mode gets most of the way there and is a workaround for a real limitation.
3. **Upgrade coupling.** Upgrading means redeploying. With a service you can upgrade the engine without touching clients, which sounds like an advantage until the version skew that permits becomes the outage [1].

> Every dependency you can compile in is a dependency that cannot be independently unavailable on a Friday afternoon.

## The discipline it imposes

Each library has a framework extension and also a plain constructor, its own context helpers, and an in-memory store so the quick start runs with no infrastructure. If a library only worked inside the framework, the framework's assumptions would leak into its design and nothing would fail to warn me.

```go
// Standalone: no framework, no database, no Redis.
d, _ := dispatch.New(dispatch.WithStore(memory.New()))
eng := engine.Build(d)
engine.Register(eng, SendEmail)
```

## Where the line is

Two of my projects are not libraries. A gateway is a binary, because it is genuinely a separate network hop and pretending otherwise helps nobody. The end-to-end argument is the relevant one here [3]: some functions belong at a boundary because that is the only place they can be enforced completely.

The test I use: if the thing must exist between processes, it is a service. If it must exist inside one, it is a library, and making it a service adds a failure domain for no benefit [2].

# References

1. Sam Newman, "Building Microservices: Designing Fine-Grained Systems", O'Reilly Media, 2nd edition, 2021

2. Michael T. Nygard, "Release It! Design and Deploy Production-Ready Software", Pragmatic Bookshelf, 2nd edition, 2018

3. J. H. Saltzer, D. P. Reed, D. D. Clark, "End-to-End Arguments in System Design", ACM Transactions on Computer Systems, vol. 2, no. 4, pp. 277-288, 1984 <https://doi.org/10.1145/357401.357402>

4. Betsy Beyer, Chris Jones, Jennifer Petoff, Niall Richard Murphy, "Site Reliability Engineering: How Google Runs Production Systems", O'Reilly Media, 2016

---

*Unpublished draft. Not peer reviewed and not submitted to a venue.*
