从过度工程到务实设计:后端架构模式的真实价值
我最近接手了一个项目。这个项目要处理的数据量不大,每天几百次请求,业务逻辑也相对简单。但打开代码的那一刻,我愣住了。
项目里有完整的领域驱动设计分层。聚合根、实体、值对象、领域服务、应用服务、仓储接口、工厂、规约……几乎所有你能想到的模式,这里都有。整个项目代码量超过两万行,而真正核心的业务逻辑,可能只需要两千行。
我问团队的同事,为什么搞这么复杂。他说这是前任架构师留下的设计,当时说要面向未来扩展,要支持高并发,要做好领域建模。可问题是,这个系统运行了三年,最高并发也没超过每秒十个请求。
这个经历让我思考一个问题:在后端开发中,架构模式的真实价值到底在哪里,我们又在什么时候陷入了过度工程的陷阱。
一、架构模式的两张面孔
架构模式本身没有问题。问题在于我们使用它们的方式。
好的架构模式解决真实的问题。领域驱动设计帮助理清复杂的业务逻辑。CQRS为读写分离场景提供清晰的边界。事件溯源让金融系统的审计追溯变得可靠。适配器让第三方依赖变得可替换。策略模式消除了长长的if-else链条。工厂模式将对象创建逻辑集中管理。
坏的架构模式使用方式,是为了模式而模式。没有真实的需求变化,却为幻想中的扩展预留了接口。业务逻辑简单直接,却为了套用设计模式而引入不必要的抽象层。团队的规模和能力根本撑不起复杂的架构设计,却硬要上高难度的模式。项目的生命周期和业务价值,根本配不上投入的架构成本。
二、过度工程的典型症状
症状一:抽象层级过深。一个简单的查询请求,要经过Controller、Service、Manager、Processor、Handler、Repository六层才能到达数据库。每一层都定义了接口,每一层都有实现类。追踪一个简单的数据流转,要跳转十几个文件。
症状二:模式泛滥。能看到的模式就有十几种。策略模式、工厂模式、建造者模式、装饰器模式、观察者模式、适配器模式、代理模式。每一个模式都用得中规中矩,但组合在一起,整个系统变得异常臃肿。
症状三:为扩展而扩展。明明只需要支持一种支付方式,代码里却有支付接口、抽象支付类、具体支付实现、支付工厂、支付策略、支付上下文。代码写着支持所有支付方式的扩展,但项目做了三年,没有增加过任何一种新支付方式。
症状四:过度解耦。模块之间没有任何直接依赖,全部通过消息队列或事件总线通信。追踪一个业务流程,需要在三四个不同的服务之间跳转,看七八个事件处理器才能搞清楚数据流向。
三、过度工程的代价
过度工程不是免费的。它带来了实实在在的成本。
首先是开发效率的下降。写一行业务代码,可能需要先定义接口、再写实现、再配置依赖注入、再编写单元测试、再集成到现有流程。原来一天能做完的功能,现在需要三天。
其次是维护成本的上升。新同事接手项目,光是理解这些抽象层和模式就需要一到两周。修改一个功能时,不确定改动会不会影响其他地方,因为解耦太彻底,调用链难以追踪。
再次是性能的损耗。每一层抽象都带来额外的函数调用开销。每一个模式都增加运行时的方法分派成本。虽然单次开销可以忽略,但累积起来,系统的响应时间明显变慢。
最后是团队士气的打击。新人看不懂代码,老人懒得解释。大家都在小心翼翼地添代码,没人敢删那些看起来没用的抽象层。代码越来越臃肿,维护越来越痛苦。
四、务实设计的核心原则
务实的设计不是不设计,而是做减法。
原则一:从简单开始。三层架构足以应对绝大多数业务场景。Controller接收请求,Service处理逻辑,Repository访问数据。简单直接,人人能懂。
原则二:为变化而设计,但不是为所有变化而设计。只对即将发生或大概率会发生的需求变化做扩展预留。不要为想象中的变化投入真实成本。
原则三:先有重复,再有抽象。不要在只有一处使用时就提取抽象。等到第三处重复出现时,再考虑提炼公共逻辑。过早的抽象往往是不准确的抽象。
原则四:让架构匹配规模。日活一千的系统,不需要支撑日活一千万的架构。项目只有一个人维护,不需要二十人团队的代码组织方式。
原则五:代码是写给人看的。机器执行代码不需要优雅,但人阅读代码需要清晰。可读性比精巧性更重要。
五、什么时候应该使用复杂架构
复杂架构有其用武之地,但需要满足特定条件。
条件一:业务逻辑确实复杂。比如电商的库存管理系统,涉及库存扣减、库存锁定、库存释放、库存预警、库存调拨等多种操作,确实需要领域驱动设计来理清边界。
条件二:性能确实有瓶颈。比如读请求是写请求的几十倍,数据库扛不住了,确实需要CQRS来分离读写路径。
条件三:团队规模确实够大。三五十人的团队协作开发同一个系统,确实需要清晰的架构边界和规范,防止代码互相影响。
条件四:系统确实需要高可用。比如金融支付系统,全年可用性要求达到99.99%,确实需要复杂的容灾和降级设计。
如果这些条件都不满足,简单架构就是最好的架构。
六、如何识别过度工程
在代码审查时,我会问自己几个问题。
这个抽象层解决了什么问题。如果答不上来,或者答案是为了将来扩展,那我就会怀疑它是否必要。
这个设计模式有没有更简单的替代方案。if-else能不能搞定,枚举能不能搞定,一个简单的Map能不能搞定。如果能,就不要用模式。
没有这个设计,代码会变差多少。如果没有它,代码依然清晰可读,那它可能就是多余的。
下一个接手的人能看懂吗。如果我自己都觉得解释起来费劲,那这个设计可能太复杂了。
这个项目真的需要这么高的扩展性吗。系统做了一年两年都没有新增的需求,我凭什么认为它将来会变。
七、务实的架构演进路径
务实的设计不是一步到位的,而是逐步演进的。
第一步,用最简单的三层架构把功能跑起来。Controller、Service、Repository,足够清晰。
第二步,当出现重复逻辑时,提取公共方法或工具类。不要急于抽象,先让重复发生两三次再说。
第三步,当if-else链条变得难以维护时,引入策略模式或状态模式。让分支逻辑变得可管理和可测试。
第四步,当第三方依赖替换频繁时,引入适配器模式。让核心业务逻辑不受外部库的变更影响。
第五步,当领域逻辑确实复杂时,引入领域驱动设计的核心概念。但只取所需,不要全套照搬。
第六步,当性能确实出现瓶颈时,按需引入缓存、读写分离、CQRS等优化手段。问题出现之前,不要提前优化。
每一步演进都基于真实的痛点,而非对未来的猜测。这就是务实设计的精髓。
八、写在最后
架构的复杂度应该与业务的复杂度成正比,而不是与程序员的炫技欲望成正比。
好的架构不是写出最能体现设计模式功力的代码,而是写出让下一个接手的人能快速理解、安心修改的代码。在绝大多数场景下,简单直接就是最高级的架构设计。
后端开发中,克制比炫技更有价值,务实比理论更加难得。
