Hermes: the internal API gateway behind our microservices
The hard part of microservices is the space between them
Split a system into enough services and the services stop being the problem. The calls between them become the problem.
Every team wires its own clients. Addresses sit in config files. Timeouts and retries depend on who wrote the code. When a request fails three hops deep, finding where takes longer than fixing it. And the question “which service may call which?” has no single answer.
I built Hermes to close that gap. Hermes is an internal API gateway and service registry: one asynchronous gRPC service that every internal call passes through. It is named after the messenger of the Greek gods, because carrying messages reliably is the whole job.
One address instead of many
Hermes has two parts.
- The gateway receives every service-to-service call and routes it to the right target over gRPC, asynchronously.
- The service registry is the catalogue of every internal API: what exists and where it lives.
A service no longer needs to know where any other service lives. It knows Hermes, and Hermes knows the rest.
With five services, direct calls mean up to ten connections to secure, monitor and keep in sync. Through Hermes there are five. The gap widens with every service you add.
How a call travels
Every call follows the same path, whichever service makes it.
- Authorise. Hermes checks the call against attribute-based access control (ABAC) policies. A caller without the right attributes is rejected before it reaches the target.
- Resolve. Hermes looks up the target in the service registry.
- Route. Hermes forwards the call over asynchronous gRPC. Nothing waits on a blocked thread, so one slow target does not stall the calls behind it.
Every call leaves traces and metrics behind, and those feed the Grafana dashboards.
What we get from it
The ten benefits group into four pillars, with security underneath all of them.
Control. Routing lives in one place. The registry keeps the estate organised: every internal API is listed and findable.
Reliability. Every call passes through one layer, so failure handling is built once and applied everywhere. That makes the platform fault tolerant and resilient without each team reinventing it.
Speed. gRPC keeps service-to-service latency low. Asynchronous communication keeps calls non-blocking, so the gateway handles many calls at once without tying up threads.
Visibility. Every call is traceable end to end. Monitoring is easy because the data comes from one source, and performance dashboards on the Grafana stack show how the whole estate behaves.
Security. ABAC is enforced at the service level. Each call is allowed or denied on the attributes of the caller and the target.
What it costs
A central gateway is not free, and pretending otherwise would be dishonest.
- One more hop. Every internal call now crosses the gateway. gRPC and non-blocking I/O keep that overhead small, but it is never zero.
- A critical dependency. If the gateway is down, internal traffic stops. It has to run as critical infrastructure: several instances and no single point of failure.
- An alternative exists. A service mesh covers some of the same ground with a sidecar beside each service. A gateway keeps the registry and the policies explicit and in one place.
For a growing estate, I think the trade is worth it: a small, known cost per call in exchange for control, visibility and security that no team has to build twice.
The takeaway
Microservices do not fail because individual services are badly built. They fail in the gaps between services. Hermes gives those gaps an owner: one path, one registry, one set of policies and one view of what is happening.
How do you handle internal service-to-service traffic: a gateway, a service mesh, or direct calls? I would like to hear what has worked for you.