Microservices are code navigation problems in disguise
Stop reading architecture diagrams. Start reading the trace IDs and Protobuf definitions.
By git11 Engineering · Mon Feb 16 2026 · 7 min read
Most microservices diagrams are lies. They show neat little boxes with arrows that suggest a logical flow, but the reality is usually a tangled web of retries, race conditions, and hidden dependencies.
If you want to understand how a distributed system actually works, stop looking at the Confluence pages. They were outdated three months ago. You have to look at the code—specifically the glue code that connects the nodes.
At git11, we’ve analyzed thousands of repositories. The patterns aren't in the READMEs; they're in the /internal/transport directories and the .proto files.
The fallacy of service isolation
Everyone tells you microservices provide isolation. In reality, they often just turn compile-time errors into runtime disasters.
We see this constantly in the wild. A team splits a monolith because they want 'independent scaling.' Six months later, they have five services that all need to be deployed simultaneously because they share a database schema or a tightly coupled client library.
If you can't change a field in Service A without breaking Service B, you don't have microservices. You have a distributed monolith. It's the worst of both worlds: all the latency of the network with none of the simplicity of a single binary.
Look at how successful teams handle this. They don't share libraries for business logic. They share interface definitions. If I see a common.jar or shared-utils npm package being imported by every service in your cluster, I know your architecture is failing.
Protocol buffers are the source of truth
If you want to understand a system fast, look for the .proto or OpenAPI definitions. These are the only documents that actually matter.
In high-performing teams, the API definition is the contract. If it isn't in the IDL, it doesn't exist. We've noticed that engineers who prioritize schema-first development spend 40% less time debugging integration issues.
Take a look at the grpc-gateway patterns in modern Go services. They define the service, the messages, and the HTTP mapping in one file. That’s your map. Everything else is just implementation detail.
Avoid services that rely on 'blind' JSON blobs. Using Map<String, Object> in Java or interface{} in Go for cross-service communication is a crime. It hides the contract and makes static analysis impossible.
The observability tax
Microservices are not free. You pay for them with operational overhead. If you don't have a correlation ID passed in every single header, you are flying blind.
We analyzed a series of outages across open-source distributed systems last year. The common thread? A lack of context propagation.
Service A calls Service B, which calls Service C. Service C fails. Service A shows a generic 500 error.
Your code should reflect this. If I don't see ctx being passed as the first argument in every function (in Go) or a scoped logging MDC (in Java), that codebase is a ticking time bomb.
You should be able to grep a trace ID and see the entire lifecycle of a request across five different repositories. If you can't do that, your microservices are just a complicated way to hide bugs.
Database per service is not a suggestion
This is where most teams blink. They build the services but keep the giant Postgres instance in the middle.
This is wrong. The moment two services query the same table, your architecture is dead. One team changes a column type and the other team's service crashes at 3 AM.
True microservices own their data. If Service A needs data from Service B, it asks via an API or listens to an event. It never, ever reaches into Service B’s disk.
We see this pattern in the best codebases: the 'Data Access Object' is internal. It is never exported. The only way to get data out is through a defined interface. This creates a hard boundary that forced the team to think about data ownership early.
Event-driven is harder but honest
Synchronous REST calls are a trap. They're easy to write but they create a chain of fragility. If one service in the chain is slow, the whole request hangs.
Modern systems favor asynchronous events. Look for events.proto or Kafka consumer groups in the codebase. This tells you the system is designed to be eventually consistent.
When we analyze these repos, we look for the 'Saga pattern' or 'Transactional Outbox' pattern. If you're publishing to Kafka, you need to make sure the database update and the message publish happen together.
Most people get this wrong. They update the DB and then try to send a message. If the network hiccups between those two steps, your system is now inconsistent. Look for a processed_events table in the database—that’s the sign of a senior engineer who has been burned before.
Stop over-engineering for scale you don't have
I’ve seen three-person startups building service meshes with Istio and Sidecars for a product that has 100 users. It’s pathetic.
Abstraction costs. Every service boundary is a place where performance dies and understanding goes to hide. Start with a structured monolith. Use clear package boundaries.
Only split when the organizational pain of working in one repo exceeds the technical pain of managing a distributed system. For most of you, that day is much further away than you think.
Context matters. Code is truth. If the repository structure doesn't match the whiteboard drawing, believe the code. It's the only thing actually running in production.
Takeaway: Check your imports today. If your services are sharing database-backed libraries or lack trace IDs, you aren't doing microservices—outsource the complexity, don't just move it around.