Modular Monolith or Microservices: A Framework for Deciding Without Dogma
本文为原文前 6,000 字符的节选翻译,完整内容请查看原文。
Modular Monolith or Microservices: A Framework for Deciding Without Dogma
Most discussions about microservices start with the technology, and they should start with the organization and the operational cost. My position, after more than 20 years designing systems: start with a modular monolith and extract services only when a concrete reason demands it. This article is not a defense of the monolith. It is a framework for deciding based on verifiable criteria.
关于微服务的讨论大多始于技术,但实际上应该从组织架构和运营成本出发。在设计系统二十多年后,我的立场是:从模块化单体架构开始,仅在有具体需求时才拆分服务。本文并非为单体架构辩护,而是提供一个基于可验证标准的决策框架。
The Problem: Deciding by Fashion
It’s common to see projects that start with ten services, a message broker and an orchestrator before they have ten users. The typical result is a distributed monolith: everything is still coupled, but now every call goes over the network and every change requires coordinating deployments. The useful question isn’t “microservices or monolith?” but: what concrete problem am I solving, and how much does it cost to solve it this way?
问题:盲目跟风的决策
常见的情况是,项目在还没达到十个用户时,就已经部署了十个服务、消息代理和编排器。其典型结果是“分布式单体”:一切依然耦合,但现在每次调用都要经过网络,且每次变更都需要协调部署。真正有意义的问题不是“微服务还是单体?”,而是:我正在解决什么具体问题,以及用这种方式解决问题的成本是多少?
What Each One Is
Modular monolith: a single deployment unit, but with modules that have clear boundaries, explicit public contracts and controlled dependencies. Microservices: several independent deployment units, each with its own lifecycle and, ideally, its own database. A modular monolith is not “the same old monolith”. The difference is discipline: modules behave as if they were services, but they communicate within the same process.
各自的定义
模块化单体:单一的部署单元,但包含边界清晰、具有明确公共契约和受控依赖关系的模块。微服务:多个独立的部署单元,每个单元拥有自己的生命周期,理想情况下还拥有独立的数据库。模块化单体并非“老旧的单体”。其区别在于纪律性:模块的行为如同服务,但它们在同一进程内进行通信。
The Costs Almost Nobody Counts
Moving to microservices is not free. What you gain in independence, you pay for in: Network: latency, retries, timeouts and partial failures that didn’t exist before. Data consistency: ACID transactions across domains are gone. Sagas, compensations and eventual consistency come in. Observability: without distributed tracing, correlated logs and per-service metrics, debugging an error becomes archaeology. Deployment and operations: per-service pipelines, contract versioning, and multiplied configuration and secrets management. Cognitive load: more pieces to understand, more boundaries to maintain and more places where a change breaks another team. These costs are acceptable when you’re buying something valuable in return. If not, it’s debt with interest.
几乎无人计算的成本
转向微服务并非免费。你在获得独立性的同时,必须付出以下代价:网络:延迟、重试、超时和以前不存在的部分故障。数据一致性:跨域的 ACID 事务不复存在,取而代之的是 Saga、补偿机制和最终一致性。可观测性:如果没有分布式追踪、关联日志和各服务的指标,调试错误将如同考古。部署与运维:每个服务都需要流水线、契约版本控制,以及成倍增加的配置和密钥管理。认知负荷:需要理解更多的组件,维护更多的边界,且更容易因一处变更而影响其他团队。只有当你换取到有价值的回报时,这些成本才是可接受的。否则,这就是带利息的债务。
When Microservices Are Justified
There are real reasons to extract a service: Teams that need to deploy independently. If two teams block each other on every release, the service boundary solves a real organizational problem. Very different scaling profiles. One component that needs 50 replicas while the rest need 2. Failure isolation. A part that must not take down the rest, such as payment processing or bulk ingestion. Compliance or security requirements. Data that must live isolated, with its own access controls and auditing. Different technology out of real necessity, not preference: for example, a compute-intensive component that performs better in another language. If your reason isn’t on this list, you can probably wait.
何时适合微服务
拆分服务有正当理由:团队需要独立部署。如果两个团队在每次发布时都会相互阻塞,那么服务边界就能解决实际的组织问题。扩展需求差异巨大。例如某个组件需要 50 个副本,而其他组件只需 2 个。故障隔离。某些部分(如支付处理或批量摄取)不能导致系统整体瘫痪。合规或安全要求。数据必须隔离存储,并拥有独立的访问控制和审计。出于实际必要而非个人偏好使用不同技术:例如,计算密集型组件在其他语言中表现更好。如果你的理由不在这个列表中,你可能还可以再等等。
What a Well-Built Modular Monolith Looks Like
The rules that make it work: One module per business context (Billing, Inventory, Users), aligned with domain boundaries, hand in hand with DDD. An explicit public contract per module. Other modules only talk to that interface, never to the internals. No cross-module table access. A module doesn’t JOIN against another module’s tables. Information is requested through the contract. Internal events for decoupling. When something happens in a module, it publishes an event; the others react without knowing each other. Rules verified automatically, not by goodwill.
构建良好的模块化单体是什么样
使其有效的规则:每个业务上下文(计费、库存、用户)对应一个模块,与领域边界对齐,并结合领域驱动设计(DDD)。每个模块都有明确的公共契约。其他模块只能通过该接口进行交互,绝不触碰内部实现。禁止跨模块访问表。一个模块不能与另一个模块的表进行 JOIN 操作。信息请求必须通过契约进行。使用内部事件进行解耦。当模块内发生事件时,它会发布一个事件;其他模块在互不了解的情况下做出响应。规则应自动验证,而非依赖自觉。
Laravel Example
A simple structure: app/ └── Modules/ ├── Billing/ │ ├── Contracts/ # the only thing other modules may use │ ├── Internal/ # private module details │ └── Events/ └── Shipping/ ├── Contracts/ ├── Internal/ └── Events/ And an architecture test with Pest that enforces the boundary: arch(‘Billing does not use Shipping internals’) ->expect(‘App\Modules\Billing’) ->not->toUse(‘App\Modules\Shipping\Internal’); arch(‘Shipping does not use Billing internals’) ->expect(‘App\Modules\Shipping’) ->not->toUse(‘App\Modules\Billing\Internal’); If someone breaks the boundary, the CI pipeline fails. A boundary that isn’t verified ends up being a suggestion. Tools like Deptrac serve the same purpose.
Laravel 示例
一个简单的结构:app/ └── Modules/ ├── Billing/ │ ├── Contracts/ # 其他模块唯一可使用的部分 │ ├── Internal/ # 模块私有细节 │ └── Events/ └── Shipping/ ├── Contracts/ ├── Internal/ └── Events/ 以及使用 Pest 进行的架构测试来强制执行边界:arch(‘Billing does not use Shipping internals’) ->expect(‘App\Modules\Billing’) ->not->toUse(‘App\Modules\Shipping\Internal’); arch(‘Shipping does not use Billing internals’) ->expect(‘App\Modules\Shipping’) ->not->toUse(‘App\Modules\Billing\Internal’); 如果有人破坏了边界,CI 流水线就会失败。未经验证的边界最终只会沦为建议。Deptrac 等工具也能达到同样的目的。
Decision Framework
Before extracting a service, answer these questions: Question If the answer is “no” Is there a team that needs to deploy without coordinating with others? You probably don’t need the service yet. Does this component have a clearly different load or scaling profile? Scaling the whole monolith is usually cheaper. Is there a failure-isolation or compliance requirement? A module with good boundaries may be enough. Do we have mature observability and deployment automation? Fix that first, or the operational cost will drown you. Is the domain boundary stable and well understood? Extracting the wrong boundary is expensive to revert. That last one is the most underestimated. A badly drawn boundary inside a monolith is fixed with a refactor. A badly drawn boundary between services is fixed with a migration.
决策框架
在拆分服务前,请回答以下问题:问题:如果答案是“否”:是否有团队需要无需协调即可独立部署?你可能还不需要微服务。该组件是否有明显不同的负载或扩展需求?扩展整个单体通常更便宜。是否有故障隔离或合规性要求?良好的模块边界可能就足够了。我们是否有成熟的可观测性和部署自动化?先解决这些,否则运营成本会淹没你。领域边界是否稳定且被充分理解?拆分错误的边界代价高昂且难以撤销。最后一点最容易被低估。单体内划分错误的边界可以通过重构修复,而服务间划分错误的边界则需要通过迁移来修复。
Evolution Path
It’s not a binary or permanent decision: Start with well-separated modules and automated rules. Measure: deployment times, blocking between teams, load hotspots. When a module meets one of the reasons on the list, extract it. Since it already has a public contract and doesn’t share tables, the extraction is mechanical: the contract becomes an API or a set of events. Apply the strangler pattern: gradually route traffic to the new service without rewriting everything. This way, the microservices decision is made with data and with the migration cost already reduced.
演进路径
这不是一个二元或永久的决定:从划分良好的模块和自动化规则开始。衡量指标:部署时间、团队间的阻塞情况、负载热点。当某个模块符合列表中的理由时,将其拆分。由于它已经拥有公共契约且不共享表,拆分过程是机械化的:契约将变为 API 或一组事件。应用“绞杀者模式”:在不重写所有内容的情况下,逐步将流量路由到新服务。通过这种方式,微服务的决策是基于数据做出的,且迁移成本已降至最低。
Common Mistakes
A shared database between “microservices”. It’s a monolith with added latency. Services that are too small (nanoservices). The coordination cost outweighs the benefit. Extracting before understanding the domain. Boundaries are discovered over time, not on a whiteboard. Ignoring the organization. Architecture ends up mirroring team structure; if they don’t match, friction appears.
常见错误
“微服务”之间共享数据库。这只是增加了延迟的单体。服务拆分过细(纳米服务)。协调成本超过了收益。在理解领域之前就进行拆分。边界是在时间推移中发现的,而不是在白板上画出来的。忽视组织架构。架构最终会反映团队结构;如果两者不匹配,就会产生摩擦。
Conclusion
A modular monolith is the r…
结论
模块化单体是……