查尔斯·狄更斯在《双城记》中写道:“这是一个最好的时代,也是一个最坏的时代。”移动互联网的快速发展,出现了许多新机遇,很多创业者伺机而动;随着行业竞争加剧,互联网红利逐渐消失,很多创业公司九死一生。笔者在初创公司摸爬滚打数年,接触了各式各样的Java微服务架构,从中获得了一些优秀的理念,但也发现了一些不合理的现象。现在,笔者总结了一些创业公司存在的Java服务端乱象,并尝试性地给出了一些不成熟的建议。 1.使用Controller基类和Service基类1.1.现象描述1.1.1.Controller基类常见的Controller基类如下: 常见的Controller基类主要包含注入服务、静态常量和静态函数等,便于所有的Controller继承它,并在函数中可以直接使用这些资源。 1.1.2.Service基类常见的Service基类如下: 常见的Service基类主要包括注入DAO、注入服务、注入参数、静态常量、服务函数、静态函数等,便于所有的Service继承它,并在函数中可以直接使用这些资源。 1.2.论证基类必要性首先,了解一下里氏替换原则:
其次,了解一下基类的优点:
所以,我们可以得出以下结论:
综上所述,Controller基类和Service基类只是一个杂凑类,并不是一个真正意义上的基类,需要进行拆分。 1.3.拆分基类的方法由于Service基类比Controller基类更典型,本文以Service基类举例说明如何来拆分“基类”。 1.3.1.把注入实例放入实现类根据“使用即引入、无用则删除”原则,在需要使用的实现类中注入需要使用的DAO、服务和参数。 1.3.2.把静态常量放入常量类对于静态常量,可以把它们封装到对应的常量类中,在需要时直接使用即可。 1.3.3.把服务函数放入服务类对于服务函数,可以把它们封装到对应的服务类中。在别的服务类使用时,可以注入该服务类实例,然后通过实例调用服务函数。 1.3.4.把静态函数放入工具类对于静态函数,可以把它们封装到对应的工具类中,在需要时直接使用即可。 2.把业务代码写在Controller中2.1.现象描述我们会经常会在Controller类中看到这样的代码: 编写人员给出的理由是:一个简单的接口函数,这么写也能满足需求,没有必要去封装成一个服务函数。 2.2.一个特殊的案例案例代码如下: 访问结果如下: curl http://localhost:8080/test/access 为什么参数systemName(系统名称)没有被注入值?《Spring Documentation》给出的解释是:
意思是说:@Value是通过BeanPostProcessor来处理的,而WebApplicationContex和ApplicationContext是单独处理的,所以WebApplicationContex不能使用父容器的属性值。 所以,Controller不满足Service的需求,不要把业务代码写在Controller类中。 2.3.服务端三层架构SpringMVC服务端采用经典的三层架构,即表现层、业务层、持久层,分别采用@Controller、@Service、@Repository进行类注解。 表现层(Presentation):又称控制层(Controller),负责接收客户端请求,并向客户端响应结果,通常采用HTTP协议。 业务层(Business):又称服务层(Service),负责业务相关逻辑处理,按照功能分为服务、作业等。 持久层(Persistence):又称仓库层(Repository),负责数据的持久化,用于业务层访问缓存和数据库。 所以,把业务代码写入到Controller类中,是不符合SpringMVC服务端三层架构规范的。 3.把持久层代码写在Service中把持久层代码写在Service中,从功能上来看并没有什么问题,这也是很多人欣然接受的原因。 3.1.引起以下主要问题
3.2.把数据库代码写在Service中这里以数据库持久化中间件Hibernate的直接查询为例。 现象描述:建议方案:关于插件:阿里的AliGenerator是一款基于MyBatis Generator改造的DAO层代码自动生成工具。利用AliGenerator生成的代码,在执行复杂查询的时候,需要在业务代码中组装查询条件,使业务代码显得特别臃肿。 个人不喜欢用DAO层代码生成插件,更喜欢用原汁原味的MyBatis XML映射,主要原因如下:
当然,既然选择了使用DAO层代码生成插件,在享受便利的同时也应该接受插件的缺点。 3.3.把Redis代码写在Service中现象描述:建议方案:把一个Redis对象相关操作接口封装为一个DAO类,符合面对对象的编程思想,也符合SpringMVC服务端三层架构规范,更便于代码的管理和维护。 4.把数据库模型类暴露给接口4.1.现象描述上面的代码,看上去是满足SpringMVC服务端三层架构的,唯一的问题就是把数据库模型类UserDO直接暴露给了外部接口。 4.2.存在问题及解决方案存在问题:
解决方案:
4.3.项目搭建的三种方式下面,将介绍如何更科学地搭建Java项目,有效地限制开发人员把数据库模型类暴露给接口。 第1种:共用模型的项目搭建共用模型的项目搭建,把所有模型类放在一个模型项目(example-model)中,其它项目(example-repository、example-service、example-website)都依赖该模型项目,关系图如下:
风险: 表现层项目(example-webapp)可以调用业务层项目(example-service)中的任意服务函数,甚至于越过业务层直接调用持久层项目(example-repository)的DAO函数。 第2种:模型分离的项目搭建模型分离的项目搭建,单独搭建API项目(example-api),抽象出对外接口及其模型VO类。业务层项目(example-service)实现了这些接口,并向表现层项目(example-webapp)提供服务。表现层项目(example-webapp)只调用API项目(example-api)定义的服务接口。
风险: 表现层项目(example-webapp)仍然可以调用业务层项目(example-service)提供的内部服务函数和持久层项目(example-repository)的DAO函数。为了避免这种情况,只好管理制度上要求表现层项目(example-webapp)只能调用API项目(example-api)定义的服务接口函数。 第3种:服务化的项目搭建服务化的项目搭,就是把业务层项目(example-service)和持久层项目(example-repository)通过Dubbo项目(example-dubbo)打包成一个服务,向业务层项目(example-webapp)或其它业务项目(other-service)提供API项目(example-api)中定义的接口函数。
说明:Dubbo项目(example-dubbo)只发布API项目(example-api)中定义的服务接口,保证了数据库模型无法暴露。业务层项目(example-webapp)或其它业务项目(other-service)只依赖了API项目(example-api),只能调用该项目中定义的服务接口。 4.4.一条不太建议的建议有人会问:接口模型和持久层模型分离,接口定义了一个查询数据模型VO类,持久层也需要定义一个查询数据模型DO类;接口定义了一个返回数据模型VO类,持久层也需要定义一个返回数据模型DO类……这样,对于项目早期快速迭代开发非常不利。能不能只让接口不暴露持久层数据模型,而能够让持久层使用接口的数据模型? 如果从SpringMVC服务端三层架构来说,这是不允许的,因为它会影响三层架构的独立性。但是,如果从快速迭代开发来说,这是允许的,因为它并不会暴露数据库模型类。所以,这是一条不太建议的建议。 后记“仁者见仁、智者见智”,每个人都有自己的想法,而文章的内容也只是我的一家之言。 谨以此文献给那些我工作过的创业公司,是您们曾经放手让我去整改乱象,让我从中受益颇深并得以技术成长。 作者:中间件小哥 |
|