10 | 代码现代化 :代码的分层重构
遗留系统中常见的模式
- 深入了解
- 翻译
- 解释
- 总结
本文介绍了面对遗留代码时的代码分层重构方法。作者首先回顾了遗留系统中常见的模式,指出它们在面对复杂逻辑时的局限性和问题。随后,文章提出了对代码分层的重构方法,即将界面逻辑、业务逻辑和数据库访问分离开来,形成UI、Service、Dao这样的三层结构。通过伪代码和Java代码的对比,展示了重构后的代码结构和逻辑。作者指出,虽然这种分层模式在Service层向Dao层传递数据时使用了对象,但仍然存在贫血模型和代码臃肿等问题。最后,文章强调了在逻辑变得复杂时,服务层的代码会变得越来越臃肿,不同的服务之间也很难相互调用和复用逻辑,每一个服务类都将变成上帝类(God Class)的问题。文章通过实例分析和对比,深入浅出地介绍了代码的分层重构方法及其局限性,对于需要进行代码现代化和重构的开发人员具有一定的参考价值。文章还介绍了应用服务、领域模型、数据映射器、仓库等多种模式,强调了重构遗留代码的重要性和挑战。读者可以通过本文了解到如何应对遗留代码的复杂逻辑,以及如何利用分层架构和领域驱动设计等技术手段进行代码重构和现代化。
《遗留系统现代化实战》,新⼈⾸单¥59
全部留言(15)
- 最新
- 精选
- Michael老师能不能再讲讲DDD里面的 Application Service, Domain Service之间的区别啊?
作者回复: 它们解决的问题是不同的,Domain Service解决的领域逻辑,Application Service解决的是应用逻辑,这两种逻辑的区别就是,领域逻辑是和具体的软件系统无关的,是线下操作的时候就有的逻辑,比如去菜市场买菜,一手交钱一手交菜,而应用逻辑是因为有了软件系统才附加上去的,比如在线支付的安全问题,数据一致性问题。 可以参考我以前写的一篇文章以及后面和同事的讨论:https://mp.weixin.qq.com/s/ZkpVELpKVmmwyG9kP7wvxw
2022-05-309 - 下弦の月public class UserRepository { public void add(User user) { } public void save(User user) { } public User findById(long userId) { } public boolean isBorrowedWith(long bookId) { } } UserRepository.findById(1000L).isBorrowed() UserRepository.isBorrowedWith(1000L) isBorrowed逻辑如果刚好是检查对象上的某个字段的值,那么第一种领域驱动的查询方式会将对象上所有不会使用到的数据都加载到内存。而第二种面相过程的方式则看起来性能更优。 这两种方式如何取舍呢?第二种查询到底属于领域模式,还是传统的事物脚本模式呢?
作者回复: 第二种属于事务脚本模式。Repository要么操作领域模型,要么返回领域模型,想这种既不操作也不返回的,实际上是吧Repository当Dao用了。 用第二种带来的所谓性能提升其实微乎其微,没有必要为了它而放弃面向对象。一旦你用了第二种,未来任何类似的方法都将加到Repository上,User很快就变回了贫血模型。
2022-05-0224 - 雨落~紫竹现在一直是服务于业务 项目都是倒排期 能自己回头看代码的时间很少 难搞喔
作者回复: 要给技术债建卡
2022-06-223 - favorlm请问老师,应用中最常用的多条件查询功能,例如 ,根据书籍类型 关键字查询书籍,那么使用仓储模式,会把其他无关书籍也查询到内存里 这种情况下,我会被直接写sql吸引,老师认为要如何取舍呢?
作者回复: 这种查询业界一般有两种方案: 1. 读写分离(CQRS)。领域模型处理的是领域状态的更改,更适合写操作。读操作,特别是查询,有时候跟模型并不是十分匹配,比如管理员在查询书籍的时候,还想知道是谁借阅了这个书籍,这个模型是从Book到Borrowing的,和我们模型中User到Borrowing不匹配,如果为了查询去修改模型显然是不合适的。读写分离的思想是,在进行复杂查询时不走领域模型那一套写模型,而是重新写一套读模型,在读模型中不适用repo,而是可以直接写SQL,因为有些查询很复杂。 2. 仍然使用原来的模型,只是模型可能会稍作一些修改。 我建议能用原来的写模型就用写模型,实在不行的时候,也可以考虑CQRS。但CQRS的认知负载偏高,很多团队应对不了,所以要慎重。
2022-05-0432 - FelixFly老师,业务逻辑与应用逻辑如何判别?借阅到期发送通知,借阅到期理解为业务逻辑,发送通知为应用逻辑。假如说借阅成功就需要发送通知,这个发送通知作为事件发送,这个应该理解为是业务逻辑还是应用逻辑,是否也应该是应用逻辑?
作者回复: 对于用户验证、日志记录、事务、性能等非功能需求,显然是应用逻辑,这个自不用说。 对于一些难以说清楚的逻辑,我是这么区分的(不一定正确,但你可以参考):对于传统行业来说,将原来的手动流程变为信息化流程的,都属于业务逻辑;而由信息化带来的增值服务(比如自动发短信通知),就属于应用逻辑,也就是软件系统给我们带来的那些逻辑。 对于你的场景,如果事件发送给另一个上下文,我认为发送事件是属于应用逻辑。但也有不少人认为这是领域层的某些业务触发的领域事件,是领域逻辑。
2022-05-0721 - hzecool老师,上面提到仓库接口放在领域层,仓库接口里涉及到的输入输出参数应该有领域对象吧? 而仓库的实现类是要放在更下层的(算基础设施层吧),这样岂不是下层的实现类也要引用到上层的领域对象了,感觉不合适呀。
作者回复: 好问题。基础设施层并不属于领域层的下层,它不是一个层,而是属于能力提供商模式,是可以依赖任何层的。注意看最后那幅图中的箭头。
2022-06-17 - hzecool老师,一个领域模型能直接同步调用另外一个领域模型吗?还是需要在domainService里对这两个领域模型进行集成?
作者回复: 当然可以,一个订单包含多个商品,订单的总价就依赖了商品的价格。
2022-06-17 - Marx领域对象和数据库的映射是怎么解决的呢?
作者回复: 参考这节课的数据映射器和仓库模式。更多内容可以看看《企业应用架构模式》。
2022-06-13 - 飞翔老师业务代码用ddd 但是写infra 框架 比如zookeeper等用什么指导呀 也用ddd嘛?
作者回复: 没太理解你的意思。你是指业务代码用DDD的时候,类似使用zookeeper的代码怎样写到infra层?还是你的工作是写zookeeper这样的工具,应该怎么开发? 如果前者,只是在infra层封装就好了,没什么特别的。如果是后者,也是可以用DDD的,你要分析一下zookeeper这样的工具,它的核心业务是什么,并为此建立模型。
2022-05-10 - 刘大明如果从领域中获取其他信息,是否会存在过长的消息链的坏味道呢? 例如这样 user.getTravel().getFlights().getCabin().getCode(); 之前项目中,总是会有get值报空指针的问题。 请问下老师,这种代码要怎么处理?
作者回复: 这的确是Message Chains坏味道,解决方案可以按照《重构》中说的,使用Hide Delegate手法,将委托关系隐藏到User或Flight里。但这样往往也会造成Middle Man坏味道,比如Code放到User里可能就不合适。所以还是要结合业务来综合对比。 一种可能的方案是,将Flight隐藏到User,将Code隐藏到Flight,得到一个还算OK的设计。 空指针可以在封装的get方法里去处理,不要放在客户端。
2022-05-062