2002年,Martin Fowler 出版《企业应用架构模式》时,Java EE 还是企业开发的主流,.NET 刚起步,Ruby on Rails 还没诞生。20多年过去了,微服务、云原生、Serverless 早已成为主流,这本书还有读的价值吗?
答案是:非常值得。
理由有三:
@OneToMany 只需要一行注解,但如果不理解背后的 Identity Map、Unit of Work 模式,遇到 N+1 查询、懒加载异常、事务边界问题时就会束手无策。正如 Fowler 在书末所说:架构的核心不是技术选择,而是控制复杂性。这个道理,20 年来从未改变。
Fowler 开篇就定义了企业应用的特征,这些特征决定了为什么需要专门的架构模式:
| 特征 | 说明 | 典型挑战 |
|---|---|---|
| 数据持久化 | 数据是核心资产,比程序本身更重要 | 数据库迁移、数据一致性、备份恢复 |
| 多人并发 | 成百上千用户同时操作 | 并发冲突、死锁、性能瓶颈 |
| 复杂业务逻辑 | 业务规则动态变化,难以穷举 | 需求变更、分支爆炸、测试覆盖 |
| 集成需求 | 需要与其他系统对接 | 数据格式转换、协议适配、版本兼容 |
| 长期演进 | 系统生命周期 5-15 年 | 技术债务、架构腐化、团队更替 |
| UI 多样性 | 桌面、Web、移动、API 多端支持 | 逻辑复用、视图分离 |
Hugo 的实战教训:在做电商订单系统时,最初把业务逻辑直接写在 ASP.NET MVC 的 Controller 里,3 个月后代码就像意大利面条。后来重构为分层架构,虽然前期多花了 2 周,但后续 2 年的维护成本大幅降低。
这三者之间天然存在张力——展示层追求灵活多变,数据层追求稳定一致,业务层追求精确可靠。架构的本质就是在这三者之间找到平衡点。
分层架构是企业应用架构中最基础也最重要的模式。Fowler 定义了三个核心层次:
┌─────────────────────────┐
│ 表现层 (Presentation) │
├─────────────────────────┤
│ 领域层 (Domain) │
├─────────────────────────┤
│ 数据源层 (Data Source) │
└─────────────────────────┘
分层的关键原则:
表现层的职责:
常见反模式:在表现层做数据验证之外的业务判断。比如在 Controller 中判断订单金额是否超过信用额度——这应该是领域层的责任。
这是全书的核心。Fowler 花了大量篇幅讨论领域层的设计方式,提出了三种主要模式:
本质:业务逻辑以过程化的方式组织,一个方法对应一个业务操作。
# 事务脚本模式
def transfer_money(from_account_id, to_account_id, amount):
from_account = account_repo.find(from_account_id)
to_account = account_repo.find(to_account_id)
if from_account.balance < amount:
raise InsufficientBalance()
from_account.balance -= amount
to_account.balance += amount
account_repo.update(from_account)
account_repo.update(to_account)
create_transaction_log(from_account, to_account, amount)
适用场景:
优点:简单直观,容易理解,开发速度快。
缺点:业务逻辑复杂时容易重复代码,难以维护。
本质:用一个类封装对一张表的所有操作。这个类管理该表的全部业务逻辑。
class OrderTableModule:
def calculate_total(self, order_id):
order = self.find(order_id)
items = self.find_items(order_id)
subtotal = sum(item.price * item.quantity for item in items)
tax = subtotal * 0.08
shipping = 10 if subtotal < 100 else 0
return subtotal + tax + shipping
def validate_order(self, order_id):
# 业务验证逻辑
pass
适用场景:业务逻辑围绕数据表组织,适合 DataSet/DataTable 模式(如 ADO.NET)。
本质:业务逻辑封装在业务对象中,对象之间存在关联、继承、组合等关系。
class Order:
def __init__(self):
self.items = []
self.status = OrderStatus.DRAFT
def add_item(self, product, quantity):
self.items.append(OrderItem(product, quantity))
self.recalculate_total()
def place(self):
if not self.items:
raise ValueError("Cannot place empty order")
if self.total > self.customer.credit_limit:
raise InsufficientCredit()
self.status = OrderStatus.PLACED
for item in self.items:
item.product.reduce_stock(item.quantity)
适用场景:
优点:面向对象,代码复用率高,业务规则集中。
缺点:学习曲线较陡,与关系数据库存在阻抗不匹配。
数据源层的职责是封装数据库访问细节,向上层提供简洁的数据访问接口。
对象和数据库表一一对应,最常用的方式:
class User:
def __init__(self, id, name, email):
self.id = id
self.name = name
self.email = email
self.is_active = True
def deactivate(self):
self.is_active = False
self.deactivated_at = datetime.now()
对象关系和业务逻辑更丰富,存在继承、多态:
from abc import ABC, abstractmethod
class PaymentMethod(ABC):
@abstractmethod
def charge(self, amount):
pass
class CreditCard(PaymentMethod):
def charge(self, amount):
# 调用支付网关
pass
class WechatPay(PaymentMethod):
def charge(self, amount):
# 调用微信支付
pass
Fowler 没有单独把服务层列为一个层,但承认在事务脚本和领域模型之间,经常需要一个服务层来组织应用级操作:
class OrderService:
def __init__(self, order_repo, payment_gateway, notification_service):
self.order_repo = order_repo
self.payment_gateway = payment_gateway
self.notification_service = notification_service
def place_order(self, cart, customer):
order = Order.create_from_cart(cart)
order.assign_customer(customer)
self.order_repo.save(order)
return order
Hugo 的实践:在 .NET 项目中,我的标准做法是 API Controller → Application Service → Domain Service + Repository。Application Service 负责事务管理、权限检查、事件发布;Domain Service 负责纯业务逻辑。这样 Controller 只有 10-20 行代码,逻辑清晰。
Fowler 提出了三种主要的数据库交互模式:
一个类封装一张表的所有 SQL 操作:
class UserGateway:
def find(self, id):
return db.execute("SELECT * FROM users WHERE id = ?", id)
def insert(self, user):
return db.execute("INSERT INTO users (name, email) VALUES (?, ?)",
user.name, user.email)
def update(self, user):
db.execute("UPDATE users SET name=?, email=? WHERE id=?",
user.name, user.email, user.id)
优点:简单直接,适合和事务脚本配合。
缺点:返回的是行数据(RecordSet),不是领域对象。
一行数据对应一个对象,每个对象封装对该行的 CRUD 操作:
class UserRow:
def __init__(self, id, name, email):
self.id = id
self.name = name
self.email = email
def insert(self):
db.execute("INSERT INTO users VALUES (?, ?, ?)",
self.id, self.name, self.email)
def update(self):
db.execute("UPDATE users SET name=?, email=? WHERE id=?",
self.name, self.email, self.id)
优点:每个数据行都是一个独立对象,便于传递和操作。
缺点:对象和数据库绑定紧密。
每个对象既包含数据也包含 CRUD 操作,同时包含部分业务逻辑:
class User(ActiveRecord):
def validate(self):
if not self.email.contains('@'):
raise ValidationError("Invalid email")
def send_welcome_email(self):
# 业务逻辑
pass
# 使用
user = User.find_by_email("test@example.com")
user.name = "New Name"
user.save()
优缺点:
这是 ORM 层的核心模式。领域对象完全不知道数据库的存在,映射器负责两者间的转换:
class UserMapper:
def find(self, id):
row = db.execute("SELECT * FROM users WHERE id=?", id)
return User(id=row['id'], name=row['name'], email=row['email'])
def update(self, user):
db.execute("UPDATE users SET name=?, email=? WHERE id=?",
user.name, user.email, user.id)
# 领域对象完全不知道数据库
class User:
def __init__(self, id, name, email):
self.id = id
self.name = name
self.email = email
优点:领域对象纯业务、可测试;数据库模式和对象模型可以不同。
缺点:实现复杂,需要处理懒加载、脏数据跟踪、关联映射等。
| 模式 | 推荐框架 | 适合场景 |
|---|---|---|
| Active Record | Rails ActiveRecord, Laravel Eloquent | 小型项目、CRUD 为主、快速迭代 |
| Data Mapper | Hibernate/JPA, Entity Framework Core | 中大型项目、复杂业务、需要单元测试 |
| Raw SQL + Mapper | Dapper, MyBatis | 高性能场景、SQL 优化需求强 |
Hugo 的经验:在大型企业项目中,不要迷信"全自动 ORM"。EF Core / Hibernate 确实能提高效率,但也引入了巨大的心智负担——N+1 查询、懒加载陷阱、一级缓存问题。我在一个项目中遇到 EF Core 生成的 SQL 多出 20 个不必要的 JOIN,排查了整整一天。建议策略:
- 80% 的 CRUD 用 ORM
- 20% 的性能敏感路径用 Dapper/MyBatis
- 核心交易路径用存储过程(被很多人鄙视,但确实可靠)
背景:在 Web 应用中,用户读取一条记录,然后去喝咖啡,30 分钟后提交修改。这期间其他用户可能已经修改了同一记录。
解决方案:使用版本号或时间戳,更新时校验版本是否变化:
class Order:
def __init__(self, id, version):
self.id = id
self.version = version
self.items = []
def add_item(self, product, quantity):
self.items.append(OrderItem(product, quantity))
# 更新时
def update_order(order, new_version):
affected = db.execute("""
UPDATE orders
SET version = ?, status = ?
WHERE id = ? AND version = ?
""", new_version, order.status, order.id, order.version)
if affected == 0:
raise ConcurrentModificationError("Order was modified by another user")
版本检查:
UPDATE orders
SET version = version + 1, status = 'confirmed'
WHERE id = 123 AND version = 5
-- 如果影响行数为 0,说明版本已变
适用场景:冲突概率高,或者冲突代价极大(比如两个人同时编辑同一份合同)。
实现方式:用单独的表记录锁定信息:
CREATE TABLE locks (
entity_type VARCHAR(50),
entity_id BIGINT,
locked_by VARCHAR(100),
locked_at TIMESTAMP,
expires_at TIMESTAMP
);
注意问题:
Hugo 的教训:在一个客户管理系统中,我们用悲观锁防止两个人同时编辑客户资料。结果发现 QA 团队经常锁住记录不释放,导致其他人无法工作。后来加的自动过期机制解决了问题——锁定 30 分钟后自动释放。
问题:当一组相关的对象需要作为一个整体锁定时怎么办?
定义一个根对象,锁定根对象就等于锁定整组:
# 锁定订单,就等于锁定订单上的所有行项目
lock_order(123, current_user)
# 此时其他用户不能修改 order 123 的任何 line item
class User:
def __init__(self):
self.profile = None # Profile 对象
class UserMapper:
def find_profile(self, user_id):
# 可以立即加载,也可以懒加载
pass
class Order:
def __init__(self):
self.items = [] # List[OrderItem]
class OrderMapper:
def load_items(self, order):
# 策略1: 立即加载(Eager Load)
rows = db.execute("SELECT * FROM order_items WHERE order_id=?", order.id)
order.items = [OrderItem(row) for row in rows]
# 策略2: 懒加载(Lazy Load)
order.items = LazyList(lambda: self._load_items(order))
Hugo 的 N+1 惨案:一个订单列表页面,显示 100 个订单和每个订单的商品数。代码用了懒加载,结果页面触发 1 + 100 条 SQL 查询。排查后改成
IN查询一次加载所有订单的商品:SELECT order_id, COUNT(*) FROM order_items WHERE order_id IN (1, 2, 3, ..., 100) GROUP BY order_id从 101 条 SQL 变成 2 条。懒加载虽好,批量场景慎用。
class Course:
def __init__(self):
self.students = []
# 关联表: enrollment (course_id, student_id, enrolled_at)
Fowler 提出了三种将面向对象继承映射到关系数据库的方式:
| 模式 | 策略 | 优点 | 缺点 |
|---|---|---|---|
| 单表继承 | 所有子类存一张表,用 type 字段区分 | 简单,无需 JOIN | 空列多,表宽 |
| 类表继承 | 每个类一张表,类之间 JOIN | 符合关系范式 | JOIN 多,性能差 |
| 具体表继承 | 每个子类一张独立表,包含所有字段 | 查询单表即可 | 父类变更需改多张表 |
选型建议:
Hugo 的实践:在 EF Core 中,单表继承(TPH)是最常用的,也是 EF Core 性能最优的方式。我用过类表继承(TPT),查询生成的 SQL 有 4-5 个 JOIN,性能翻车。除非有明确的范式理由,默认选 TPH。
这是 ORM 中最核心的模式之一。它维护一个变更跟踪列表,在合适的时候一次性提交所有变更:
class UnitOfWork:
def __init__(self):
self.new_objects = []
self.dirty_objects = []
self.removed_objects = []
def register_new(self, obj):
self.new_objects.append(obj)
def register_dirty(self, obj):
if obj not in self.dirty_objects:
self.dirty_objects.append(obj)
def register_removed(self, obj):
self.removed_objects.append(obj)
def commit(self):
with db.transaction():
for obj in self.new_objects:
mapper.insert(obj)
for obj in self.dirty_objects:
mapper.update(obj)
for obj in self.removed_objects:
mapper.delete(obj)
self.new_objects.clear()
self.dirty_objects.clear()
self.removed_objects.clear()
EF Core 的 DbContext 和 JPA 的 EntityManager 本质上就是 Unit of Work。
确保同一个数据库记录在同一个事务中只被加载一次,避免同一个对象在内存中有多个副本:
class IdentityMap:
def __init__(self):
self._map = {}
def get(self, class_name, id):
key = f"{class_name}:{id}"
return self._map.get(key)
def add(self, obj):
key = f"{type(obj).__name__}:{obj.id}"
self._map[key] = obj
为什么需要 Identity Map?
本质:对象被创建时,不立即加载所有关联数据。等到真正访问时才去数据库加载。
class LazyOrder:
@property
def items(self):
if self._items is None:
# 延迟加载
self._items = self.mapper.load_items(self.id)
return self._items
优缺点:
当对象被懒加载时,如果关联的对象已经被删除了怎么办?这就是脏读的一种变体。解决方案:
无状态(Stateless):每次请求都是独立的,服务器不保存客户端状态。这是 RESTful 架构的基础。
有状态(Stateful):服务器保存客户端会话信息,简化开发但带来扩展性问题。
| 模式 | 存储位置 | 优点 | 缺点 |
|---|---|---|---|
| 客户端会话 | Cookie/LocalStorage | 可扩展性好 | 数据量限制(4KB) |
| 服务器端会话 | 内存 | 快,简单 | 无法扩展(Session Affinity) |
| 数据库会话 | 关系数据库 | 持久化 | 速度慢,数据库压力 |
| 分布式缓存 | Redis | 快,可扩展 | 需要额外基础设施 |
Hugo 的建议:对于中等规模的企业应用,Redis + Spring Session / ASP.NET Core Distributed Cache 是最佳实践。Session 尽量只存用户身份信息,不要存大量业务数据。如果应用需要横向扩展,绝对不要用进程内 Session。
问题:在分布式系统中,领域对象的细粒度方法调用会导致大量的网络往返。
// ❌ 错误:远程调用过于细粒度
Order order = orderService.find(orderId);
order.addItem(product1, 2);
order.addItem(product2, 1);
order.applyCoupon("SAVE10");
order.checkout();
// 这会产生 5 次远程调用!
// ✅ 正确:使用远程外观
orderService.submitOrder(orderId, items, couponCode);
// 一次远程调用,服务器端完成全部操作
问题:在远程调用中,需要传输的数据往往和领域对象不同。直接暴露领域对象会导致:
解决方案:创建专门用于传输的数据对象:
class OrderDTO:
def __init__(self, order):
self.id = order.id
self.total = order.total
self.status = order.status.value
# 只传输需要的数据
self.items = [
ItemDTO(item) for item in order.items
]
class ItemDTO:
def __init__(self, item):
self.product_name = item.product.name
self.quantity = item.quantity
self.subtotal = item.subtotal
# 不暴露内部 ID 和审计信息
Fowler 在书中提出了一个直到今天仍然极其重要的观点:
"分布式对象设计的第一定律:不要分布你的对象。"
可以用下面的决策树来判断是否需要分布式:
业务逻辑是否需要部署在不同的进程中?
├── 否 → 所有逻辑在同一个进程内,走普通方法调用
└── 是 →
├── 是否可以通过批量操作减少网络往返?
│ └── 是 → 设计粗粒度接口(Remote Facade)
└── 是否必须实时调用?
├── 是 → 使用 DTO + Remote Facade
└── 否 → 考虑异步消息(MQ)
Hugo 的反思:我见过太多项目因为"技术时髦"而走向分布式。一个内部管理系统,每天只有几百个用户,却上了微服务 + 消息队列 + 分布式事务。代价是开发周期翻倍、线上问题排查难度指数级上升。Fowler 说的对:先考虑单进程,实在不行再拆。
Fowler 反复强调:模式不是银弹,是工具箱。读这本书的真正价值不是记住 50 多个模式,而是学会:
2002 年 Fowler 就警告:不要为了性能而分布式。20 年后,微服务架构大行其道,但业界已经开始反思过度分布式的代价——分布式事务、网络延迟、运维复杂度。
Fowler 的忠告在今天依然适用:
模式 实践应用
─────────────────────────────────────
Domain Model → Spring Boot @Entity
Unit of Work → JPA EntityManager / EF DbContext
Identity Map → Hibernate 1st-level cache
Lazy Load → JPA @OneToMany(fetch=LAZY)
Optimistic Lock → JPA @Version
DTO → Spring DTO / AutoMapper
Repository → Spring Data JPA Repository
理解模式,就是理解你已经在使用的框架背后的设计哲学。
初学阶段(1-2周):
实践阶段(日常开发):
深入阶段(架构师视角):
阶段 1: 事务脚本(MVP 原型)
└── Controller → DAO → Database
(所有逻辑在 Controller 里,快速验证)
阶段 2: 分层架构(版本 1.0)
└── Controller → Service → Repository → Database
(引入领域层,逻辑从 Controller 中抽出)
阶段 3: 领域模型 + 工作单元(稳定期)
└── Controller → ApplicationService → Domain Model → Data Mapper
(引入 DDD 概念,复杂业务用领域对象建模)
阶段 4: 模块化 + CQRS(扩展期)
└── 写操作: Controller → Command → ApplicationService → Domain → Repository
└── 读操作: Controller → Query → ReadModel (直接从数据库投影)
每个阶段的演进都是有代价的——更复杂的架构意味着更高的维护成本。只在必要时才进入下一阶段。
| 维度 | 评价 |
|---|---|
| 理论深度 | ⭐⭐⭐⭐⭐ 定义了企业应用开发的核心理论框架 |
| 实践价值 | ⭐⭐⭐⭐⭐ 每个模式都可以在现有框架中找到对应实现 |
| 可读性 | ⭐⭐⭐⭐ Fowler 的写作风格清晰优雅,但部分章节偏涩 |
| 时效性 | ⭐⭐⭐⭐ 核心思想 20 年未变,但具体技术示例偏旧 |
| 推荐度 | ⭐⭐⭐⭐⭐ 所有企业级开发者都应该读 |
📚 读书笔记 | 架构师书架系列