跪拜 Guibai
← All articles
AI Programming · OpenAI

Microservices Done Right: Splitting, Communicating, and Governing Without Creating a Distributed Monolith

By vipxieliang ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

The article gives Western engineering teams a concrete checklist for avoiding the two most expensive microservices failures: splitting by technology instead of business capability, and ending up with a distributed monolith. It also spells out the operational requirements for eventual consistency that many teams discover only after data goes wrong.

Summary

This guide walks through a full microservices design path, starting with the decision to split. It argues that decomposition should follow business capabilities, not technical layers, and that a service only counts as independent if it has its own data, build pipeline, runtime resources, and backward-compatible contracts. The article stresses starting coarse (3–8 core domains) and refining later, and watching for the distributed monolith: services that still must be released together, which are worse than a single monolith.

On communication, it recommends synchronous HTTP/gRPC for queries and core paths with timeouts, and asynchronous messaging for side paths and commands that allow eventual consistency. It covers the role of an API gateway and BFF (Backend for Frontend) for aggregation and endpoint adaptation, while noting BFFs add overhead for small teams. Governance topics include service registration and discovery with graceful shutdown, config centers with gray push and defaults, circuit breaker state machines with fallback strategies, and the three pillars of observability.

The final section addresses distributed transactions, comparing 2PC, TCC, Saga, local message tables, and transactional messages. It lands on the local message table plus scheduled compensation as the lowest-cost approach, with the key points that the message table and business table must share a local transaction, consumers must be idempotent, and dead-letter queues with alerts are mandatory. It closes with three consistency rules: don't sacrifice availability for strong consistency, make every retried write idempotent, and reconcile core money flows.

Takeaways
— Microservices trade code complexity for distributed systems complexity; there is no free lunch.
— Split by business capability (DDD bounded contexts), never by technical layer such as Controller or DAO services.
— A service is independent only if it has its own data store, build/release pipeline, runtime resources, and backward-compatible contracts.
— Start with 3–8 core domain services and refine later; alignment with team boundaries follows Conway's law.
— If two services always have to release together, they form a distributed monolith that is harder to maintain than a monolith.
— Use synchronous calls for queries and core paths, asynchronous messaging for side paths; always set timeouts on sync calls.
— An API gateway handles auth, rate limiting, routing, and canary releases; a BFF layer is optional for small teams or few clients.
— Circuit breakers operate as Closed → Open → Half-Open state machines with fallback data or feature degradation.
— Config changes should be pushed to one instance first (gray push) and sensitive values must be encrypted with default fallbacks.
— Observability requires logs with traceId, metrics like P95/P99 latency, and cross-service tracing.
— The local message table + scheduled compensation pattern is the lowest-cost eventual consistency approach; the message table shares a transaction with business data, the consumer must be idempotent, and dead-letter queues plus alerts are required.
— Do not sacrifice availability for strong consistency; most business accepts second-level eventual consistency, and core money flows need scheduled reconciliation.
Conclusions

The article's strongest claim is that a split is only real when independent deployment is possible; teams often celebrate service boundaries while still coupling releases, and that is exactly what turns a microservice architecture into a worse monolith.

The guidance to prefer eventual consistency almost everywhere, with idempotency and reconciliation as non-negotiable, is a sensible default for most web businesses, but it assumes teams can actually enforce idempotency at the design level, which requires discipline across every service.

The advice to start coarse and refine only when needed directly contradicts the common instinct to over-engineer microservices from day one; this positions the article as a practical counterweight to the "microservices everywhere" hype.

The author acknowledges that BFF adds a layer only justified when there are multiple client types with divergent needs, which is a pragmatic check against the tendency to adopt every pattern in the book.

Concepts & terms
Bounded Context
A core DDD idea: the boundary within which a particular domain model applies. In microservices, each service should own one bounded context so that business changes stay cohesive.
BFF (Backend for Frontend)
An aggregation layer between the API gateway and backend services that tailors APIs for a specific client type (Web, mobile, mini-program), reducing request count and isolating frontend-driven changes.
Distributed monolith
A system that has been split into services but still requires coordinated releases across many of them for every feature. It has the operational cost of microservices and the coupling of a monolith, making it worse than either.
2PC/XA
A distributed transaction protocol that tries to guarantee strong atomicity across multiple databases, but with low performance and high invasiveness, so it suits only a few internal strong-consistency scenarios.
TCC (Try-Confirm-Cancel)
A distributed transaction pattern with three phases per operation. Each service tries to reserve resources, confirms on success, and cancels on failure. It gives eventual consistency with medium performance and high implementation complexity, often used for money and inventory.
Saga
A long-running distributed transaction pattern that breaks a business process into a sequence of local transactions with compensating actions for rollback. It offers high performance and eventual consistency, suitable for multi-step flows.
Local message table
An eventual-consistency pattern where business data and a message record are written in the same local transaction, then a scheduled task delivers the message to a queue. The downstream consumer must be idempotent, and failures go to a dead-letter queue.
Dead-letter queue
A queue that receives messages that have failed repeated processing attempts. It allows for manual or automated intervention when a consumer cannot handle a message, which is important for long-term consistency guarantees.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗