前言
从认识Java这个语言开始,就有人告诉我们这是个面向对象的语言。它有三个基本特性:封装、继承、多态,当我们使用Java实现一些功能时它在Java中体现就是:对象==类(class关键字)、继承(extends关键字)、多态(重载、重写),其实并不怎么明白对象和class的关系,特别是在初开始参加工作就开始使用Mvc模型进行项目的开发,就给我们一种感觉,以为new Model()就是创建一个对象,在MVC模型中一切以Model‘对象’为载体,去编程,去实现。也是在最近做公司微服务架构实际搭建时,了解到领域驱动模型(后面简称DDD)才打消了一个一直伴随我成长的疑惑,恍然大悟。
我的疑惑就是,我们一直在采用的开发模式即:Controller -> Service ->Dao ,Service就是一个上帝类,所有的业务逻辑都往里塞,最后造成了Service层的无限膨胀。这种方式真的是对的吗,答案显然是不对的。但事实上很多人对此还是无动于衷,But ~究竟要如何改进呢?
在之前的公司做过的项目也放下过豪言壮语,无论多复杂的系统,只要一次性过确定需求,不要求对其进行进一步的维护和后续的业务修改。我都能很快的做出来,因为这种开发模式确实是将CURD表现淋漓尽致,一个资深工程师的代码实现结果,一个菜鸟也同样做到(这里说的是结果,不是过程,然而实际是过程也可能一样,这就很难受了)。
第二点,其实也是延续着第一点而来,随着经验的丰富,开始领悟到面向对象和软件工程,那么问题来了,在这种传统的开发模式下,面向对象究竟体现在哪里,我们学到的设计模式,究竟在哪里展开实践,而实际上我们往往是挂羊头卖狗肉,羊头是面向对象,狗肉是(面向数据库)面向过程开发。
面向对象到底是什么概念呢?它和面向过程有什么区别呢?
有一天你想吃鱼香肉丝了,怎么办呢?你有两个选择
1、自己买材料,肉,鱼香肉丝调料,蒜苔,胡萝卜等等然后切菜切肉,开炒,盛到盘子里。
2、去饭店,张开嘴:老板!来一份鱼香肉丝!
看出来区别了吗?这就是1是面向过程,2是面向对象。
面向对象有什么优势呢?首先你不需要知道鱼香肉丝是怎么做的,降低了耦合性。如果你突然不想吃鱼香肉丝了,想吃洛阳白菜,对于1你可能不太容易了,还需要重新买菜,买调料什么的。对于2,太容易了,大喊:老板!那个鱼香肉丝换成洛阳白菜吧,提高了可维护性。总的来说就是降低耦合,提高维护性!
其实这个问题大家都能说上来一两句,面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。而面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了。面向对象的底层其实还是面向过程,把面向过程抽象成类,然后封装,方便我们我们使用的就是面向对象了。
为什么说MVC是面向过程开发,DDD初识
在传统的MVC开发模式中,我们的第一个问题是如何处置承担过多的职责的Service上帝类。一个字很好解决,就是拆,笔者曾经尝试按着类的单一职责的指导,将一个依赖注入过多其他类的一个Service类,可能有十几二十个,读者你可以检查一下自己的项目,这就是坏代码,这带来的好处其实只有功能点封装,并不能解决不让Service类臃肿的问题,他还是会慢慢臃肿起来的,而本质仍是面向过程开发,还有一个问题是,就算是这种做法解决Service类臃肿的问题,他无法解决一个很关键的东西,即这里面的代码,没有体现出业务性。什么叫做没有体现出业务性?比如说激活用户状态,我们一般会怎么开发?
public class UserService{
public void updateAvailableById(long id,boolean available){
userDao.updateAvailableById(id,available);
}
}
这是很典型的面向过程开发的代码,在我们的项目中处处可见,丧失了面向对象中对象的概念,而现如今我们在开发的时候,反而被因为使用了数据库而束缚了我们的思维,我们在接收到需求的第一时刻,在分析设计阶段,往往是以数据表为核心进行分析设计, 也就是根据需求首先得到数据表名和字段,然后培训程序员学会SQL语句如何操作这些数据表,那么程序员为实现数据表的前后顺序操作,必然会将代码写成过程式的风格,以数据表为核心进行分析设计,代码很难避免不演变成过程式的代码,因为我们的重点放在了操作某张表,以及相关的某个字段。
这里就是所谓的贫血模型了,UserDao是关于用户表的SQL语句的集合,在项目中的表现就是写了一堆SQL语句,UserService则是作为Transaction Script的入口以及夹杂着其他杂七杂八的Service类。而这一整个过程中,user对象都没出现过。而领域驱动倡导的做法如下:
@Data
public class User{
private Long id;
private Boolean available;
public void activate(){
this.setAvailable(true);
}
}
//领域驱动服务
@Service
public class UserOperationService{
@Autowired
private UserRepository userRepository;
public void activate(User user){
user.activate();
userRepository.save(user);
}
}
首先第一点是类富含行为(即充血模式),这才是真正倡导的面向对象开发,业务系统所关注的点是业务功能而不是想着这个功能我们应该如何建表,如何写SQL语句,一直以来我们的思维都因为使用了数据库而被绑架了,一上来就开始建表写代码,代码写的非常冗余,完全是过程式的思考方式,最后导致系统非常难以维护。业务功能只需要明白是激活功能,而不是把user表中available设为true,数据库只是基础设施,我们不应该关注以及知会对应的SQL语句是怎么样的,所以DDD强调的是Repository而不是DAO,Repository翻译为仓库资源,所需即所得,不用去关注其背后,而DAO我们不得不去关心SQL语句如何写。可以前往阅读这篇文章进入更深入的思考对象和数据库的天然阻抗,如果看完之后还不是很明白的话可以在留言区留下疑惑。
我们不应将原属于领域模型的行为方法等划放在服务中实现,对象不但有属性还有行为将数据和行为封装在一起,并与现实世界的业务对象相映射。回归到我们最初刚学习面向对象教我们的那样,万物皆是类,而不是只有一个上帝类,我们需要多创建有合适逻辑的类,各类具备明确的职责划分,使得逻辑分散到合适对象中。这样的对象就是“充血模型”,而这个过程我们也称之为领域建模,这才是面向对象的真谛啊
说到这里,大家可能对以上的示例存在着一点疑惑:
-
怎么这么简单的功能,用DDD的方式下却整的这么复杂?
DDD为什么称之为复杂软件的设计之道?因为系统够复杂,才能全面展现DDD的功效,业务逻辑站在至高点使得任何业务逻辑的修改扩展都能做到兵来将挡水来土掩,极大的降低了刚接触该系统的同事上手入门的门槛,以及更好的避免存在隐藏BUG(你总会因为不小心改了一行代码,而导致出现了业务异常)。简单的系统功能,只有简单的增删查改,所有的业务逻辑实际只有简单的增加和查询,根本无法提炼出来领域模型,若直接运用DDD的全部理论,实则浪费时间,杀鸡用牛刀。但是没有亘古不变的代码,重构是一直伴随着整个系统的演变,因此建议是即便是简单系统,最好一开始就搭好DDD的样子
-
但是一切说到底不都是需要对数据的增删查改吗
初步认识DDD并想展开实践的,确实会存在这个疑惑,DDD难在一点的是,他需要你思维的转变,你需要开始不去关注数据库,不去关心SQL语句, 从数据建模转向领域建模,而这个过程又会将你面向对象过车功能的思维短板暴露出来,很抱歉,长期以来你并没有用面向对象的思维来开发。 Alistair Cockburn 提出了六边形架构,旨在解决了传统的分层架构所带来的问题。倡导的是屏蔽技术细节,强调业务逻辑,最终目的是实现业务逻辑可重用,组织为一个可重用的自封闭的业务模型,即拷贝不走样。仔细想想在贫血模式下,最大的不足就是业务逻辑不能重用,业务逻辑没有组织为一个可重用的自封闭的业务模型