Microservices is Not Just About Splitting Services
A checklist of things I ignored and paid the price for while building a microservice system for the first time.
This article is not a tutorial. It's a checklist of things I ignored—and paid the price for (or left as technical debt)—during the process of building a microservice system for the first time.
The Most Common Misconception
Many developers (myself included) think that doing microservices means splitting code into many small services, having them call each other via HTTP or a message queue, and calling it a day.
Wrong.
Splitting services is only 20% of the work. The remaining 80% is making the system operable—meaning it's observable, debuggable, deployable, and doesn't collapse when one small piece has a problem.
Here are the important things I underestimated.
1. Observability: You Won't Know What the System is Doing Without It
With a monolith, when there's an error, you open the logs of the single application and search. With microservices, a request might go through 5–10 services. When there's an error in the seventh service, do you know where to start looking?
Observability consists of 3 pillars:
Standardized Logging. Each log entry needs: timestamp, service name, trace ID, request ID, log level, and message. If each service logs in its own format, aggregating and searching will be a nightmare. Use structured logging (JSON) from the start—don't leave it for a later refactor.
Metrics. Requests per second, p50/p95/p99 latency, error rate, resource usage. Without metrics, you don't know which service is "hurting" or how the system is handling the load. Prometheus + Grafana is the most common combo.
Distributed Tracing. This is the most important thing that few people do from the start. A trace ID is generated at the entry point and passed through all services—allowing you to see the entire journey of a request. OpenTelemetry is the current standard, which can export to Jaeger, Zipkin, or Datadog.
I learned this lesson the hard way: when the system was unusually slow and I didn't know where to look.
2. CI/CD: Manual Deployment is Not Microservices
One of the biggest benefits of microservices is independent deployment of each service. But that benefit only truly materializes when deployment is fast, safe, and automated.
If every time you deploy you have to SSH into a server, pull code, and restart processes manually—you are doing microservices without its benefits, only its added risks.
A minimum CI/CD pipeline needs:
- Automated testing running on every PR—catch errors before merging.
- Build & push Docker image automatically when merging to main.
- Automated deployment to staging, with a gate to promote to production.
- Easy Rollback—when a deployment goes bad, you must be able to rollback in minutes.
This is not optional. This is a prerequisite for microservices to function correctly.
3. Health Checks & Resilience: The System Must Know How to Self-Heal
In microservices, services will fail. It's not a "maybe"—they will die. The question is: when a service dies, does the system keep functioning?
Health check is a simple endpoint (usually /health or /healthz) that returns the status of the service. Kubernetes, load balancers, and service meshes use this endpoint to know whether to route traffic to the service.
Circuit breaker is a pattern to prevent a faulty service from dragging down the entire chain. When Service B fails repeatedly, the circuit breaker will "trip"—Service A will stop trying to call B and return a fallback immediately. When B recovers, the circuit breaker automatically "closes."
Retry with exponential backoff—don't retry immediately and continuously; that only makes a failing service worse. Retry after 1s, 2s, 4s, 8s... with jitter to avoid a thundering herd.
4. API Versioning: So Services Can Deploy Independently
Microservices mean that teams/services can deploy asynchronously. Service A deploys a new version while Service B is still running the old version. Without versioning, a service changing its API will break all its consumers immediately.
The simplest way: prefix all endpoints with a version—/v1/users, /v2/users. Keep the old version running long enough for all consumers to migrate.
It sounds simple, but this discipline needs to be enforced from the start—not when an incident occurs.
5. Anti-Patterns to Avoid From the Start
Shared Database. If multiple services read/write to the same database, they aren't truly independent. Changing a schema to serve Service A will break Service B. Each service should have its own storage.
Long Synchronous Chains. Service A calls B, B calls C, C calls D. Latency accumulates. If D is slow, the entire chain is slow. If D dies, the entire chain dies. Consider using async messaging (message queue) for flows that don't need an immediate response.
Hardcoded Service URLs. Use service discovery (Consul, Kubernetes DNS) instead of hardcoding IP/port. Services can scale up/down, restart, and change addresses—the client shouldn't need to know those details.
Conclusion
Microservice is a powerful architecture—but powerful in the sense of both "high impact" and "potentially harmful if used incorrectly."
The list above isn't meant to scare you. It's to help you understand: when you choose microservices, you are choosing a new set of responsibilities. Those responsibilities need to be acknowledged and planned for—not left for later.
If you have to prioritize: CI/CD first, observability immediately after. Those two allow you to detect problems early and fix them fast. Everything else can be built gradually.
Series • Part 2 of 20