笔者最近在熟悉一个新的项目,觉得现有的分层设计不甚合理,在重新设计过程中发现自己对分层这一块理解模糊。本文在此进行一个简单整理。
在笔者的职业生涯中,接触过各种类型的项目。有QPS接近万级别的C端项目,也有逻辑非常复杂的B端项目,也有一些进行所谓CURD的信息管理系统。无论什么类型的项目,在项目之初就应该进行良好的分层设计。如果这一步没有做好,等到后期发现代码混乱想要重新设计时,所花费的精力将是开始时的数倍不止。笔者曾见过一个线上项目,所有逻辑都在spring的Controller类中实现,连方法都不分,更别提分层设计了,行业黑话称之为“屎山”。
笔者首先结合自己的经验探讨下为什么要对项目进行分层设计,以及什么样的分层设计才算好的。为什么要对项目进行分层设计?大多数项目都是随着业务增长不断迭代的,业务逻辑日趋复杂,对业务的各项指标(QPS、响应延时等)也要求越来越高。一个分层设计良好的系统,不同的逻辑在不同的层次实现,每个层次只负责属于自己的业务,这样如果需要修改某个功能,可以快速定位到地方。既有利于业务开发迭代,对新加入团队的成员熟悉业务也很有帮助,甚至编码引起的代码冲突都少多了。分层设计的系统,层次之间只暴露接口,隐藏具体实现细节,实践低耦合高内聚的编程原则。最后分层设计的系统如果后期要做服务拆分,只需进行代码的复制粘贴即可,业务逻辑几乎不用更改,出错率大大降低。什么样的分层设计才能称之为好的呢?在笔者看来,好的分层设计,既可以很好适应当前的业务迭代开放,同时也要对未来一定时间业务变化留有空间。
早期的系统,前后端没有完全分离,前端展示使用JSP等渲染技术。为了开发方便,诞生了像spring、strust一批MVC框架,其分层如下所示:

简而言之,后端逻辑在Controller层实现,生成一个Model对象,然后把这个Model对象传递给View层,用于渲染前端页面。
现在前后端分离已成大势所趋,后端的逻辑也越来越复杂,有必要对Controller层进行进一步的分层。对于常规项目,笔者的经验是根据下图来分层设计,笔者将详细介绍一下各个层以及层次之间的对象的含义。在此声明,图参考自阿里巴巴编程规范。

首先介绍每个对象的含义:
每层的含义如下:
按照上述分层之后,仍然不足以保证你的系统代码整洁。针对每一层,你需要避免出现过于复杂的方法,某些类无限膨胀的现象。你需要在编码过程中不断的抽象,将一些共性的方法,使用泛型、注解等技术提取出来,放到父类或者接口等公共地方,使你的代码逻辑更加清晰。笔者曾经重构过一个Service层的代码,基本思路就是使用泛型将公共操作抽象出来,增加一个模板类(Adapter)。之所以要增加一个模板类,是为了防止在某些实现中,一些接口操作是不需要的,而不得不覆写。另外,也可以防止比如没有分页需求时,需要传入丑陋的null,代码如下:
public interface BaseRepositoryV2 <D, Q, I> {
void save(D d);
void save(List<D> d);
List<D> find(Q q, String... fields);
List<D> find(Q q, Pageable pageable, String... fields);
Optional<D> findOne(Q q);
D findById(I i);
List<D> findByIds(List<I> i);
int total(Q q);
}
public abstract class RepositoryV2Adapter<D, Q, I> implements BaseRepositoryV2<D, Q, I> {
@Resource
protected MongoTemplate mongoTemplate;
protected Class<D> clazz;
protected String collectName;
public RepositoryAdapter(Class<D> clazz, String collectName) {
this.clazz = clazz;
this.collectName = collectName;
}
@Override
public List<D> find(Q q, String... fields) {
return find(q, null, fields);
}
@Override
public D findById(I i) {
return findByIds(Lists.newArrayList(i)).get(0);
}
@Override
public void save(D d) {
save(Lists.newArrayList(d));
}
@Override
public void save(List<D> d) {
mongoTemplate.save(d);
}
@Override
public List<D> find(Q q, Pageable pageable, String... fields) {
Query query = Query.query(q.toCriteria());
for (String field : fields) {
query.fields().include(field);
}
if (!Objects.isNull(pageable)) {
RepositoryUtil.applyPageable(query, pageable);
}
return mongoTemplate.find(query, clazz, collectName);
}
@Override
public Optional<D> findOne(Q q) {
Query query = Query.query(q.toCriteria());
return mongoTemplate.findOne(query, clazz, collectName);
}
@Override
public List<D> findByIds(List<I> i) {
if (CollectionUtils.isEmpty(i)) {
return Lists.newArrayList();
}
Criteria criteria = new Criteria();
criteria.and(CommonFields.MONGODB_ID).in(i);
return mongoTemplate.find(new Query(criteria), clazz, collectName);
}
@Override
public int total(Q q) {
return mongoTemplate.count(Query.query(q.toCriteria()), clazz, collectName);
}
}
public interface HostsRepositoryV2 extends BaseRepositoryV2<Hosts, HostQueryV2, String> {
List<HostsBo> find(String comId, List<String> agentIds, Pageable pageable);
}
public class HostsRepositoryV2Impl extends RepositoryV2Adapter<Hosts, HostQueryV2, String>
implements HostsRepositoryV2 {
}