浅谈对分层设计的认识           

笔者最近在熟悉一个新的项目,觉得现有的分层设计不甚合理,在重新设计过程中发现自己对分层这一块理解模糊。本文在此进行一个简单整理。

在笔者的职业生涯中,接触过各种类型的项目。有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 {
    
}
  
如果对文章有任何不同意见,有两种办法: