目录:网上冲浪指南

为什么在搬砖项目中我不用三层架构

2017/11/21

最近在做老师的一个项目,一个医疗报销信息管理系统,然后同学就顺便拿这个东西去用作软件过程管理这门课的课程设计课题了。今天他写文档的时候问我,为什么我写的这个后端代码没有分三层,数据库操作就直接写死在 Controller 里面了?当时我就简单的回答了一下说,因为项目比较简单,而且时间紧,就直接这么写了。然后他就继续对着我的非教科书范式的代码憋文档去了。

我觉得三层架构中最没有存在感的应该就是数据访问层了,毕竟在 2017 年,写搬砖项目应该很少有人不用上 EF 或者其他类似的 ORM 工具了,在用上了这样的强力 ORM 情况下,数据访问层的代码很可能就长下面这样:

// IEmployeeDAO.cs
public List<Employee> GetAllEmployeeByCompanyId(string compId);

// EmployeeDAO.cs
public List<Employee> GetAllEmployeeByCompanyId(string compId)
{
    return _context.Employees.Where(e => e.CompanyId == compId).ToList();
}

然后业务逻辑层就会像下面的代码一样去调用 DAO:

var employees = empDAO.GetAllEmployeeByCompanyId(compId);

业务逻辑层通过 IoC 容器获取实现 DAO 接口的实例,完全不用理会具体的数据访问是如何实现的,到时候要是换个数据库什么的,逻辑层的 Service 一行代码都不用改,替换一下 DI 里面的策略就好了,看起来是如此的完美。然而,也就是想想会觉得美滋滋。且不说更换数据库这个需求是多么的罕见,就算业务逻辑层出现了 EF 的 Linq 代码,在不指明的情况下也很难分辨操作的是一个 IEnumerable 还是一个 IQueryable,这就起到了屏蔽底层实现的作用,更何况 EF 通过更换 DbProvider 是真的可以在几乎不修改 Linq 的情况下来切换到不同种类数据库的。这么看来,在逻辑层中直接使用 EF 不仅没有牺牲掉 DAO 带来的好处,而且减少了大量的代码,所以 DAO 就可以砍掉了。

在我们的项目中,前端使用了 Angular 这个框架,后端主要做的事就是提供 Web API 供 Angualr 应用调用了。映射到三层架构中,表现层应该就是 Web API Controller 了,所以在 Controller 中就需要校验前端发来的数据是否合法,接着把这些数据作为参数用来调用逻辑层中相关的接口,那么常见的代码可能就长下面这个样:

// RecordsController.cs
if(ModelState.IsValid() == false)
{
    return BadRequest(ModelState);
}
recordService.Audit(model);
return Ok();
// IRecordService.cs
public void Audit(Record record);

// RecoredService.cs
public void Audit(Record record)
{
    var entity = _context.Where(r => r.Id == record.Id).FirstOrDefault();
    entity.State = AuditState.Pass;
    _context.SaveChanges();
}

情况看起来跟之前被砍掉的 DAO 非常相似,但是即便如此还是不能贸然砍掉这个逻辑层,因为需要考虑到这里的操作是有其业务上含义的,而且很有可能会在其他地方被其他的服务所调用。即便如此,我还是砍掉了这个业务逻辑层,因为诸如此类的 Service 其中包含的方法都可以算得上是纯函数了(假设数据库绝对稳定可靠 😛 ),这些 Service 本身并没有需要维持的状态,所以,这些 Service 最终都可以变成包含一堆只静态方法的静态类,唯一的作用就是根据不同的业务逻辑,组合不同的数据库动作来操作输入的数据,最后把结果返回出去。所以我更愿意把这些东西定义成一组静态的 Function 来调用,最后,逻辑层也就这么轻松的砍掉了。

在上面,虽然我用 EF 替换了 DAO 并将其内嵌进了逻辑操作中,然后又把逻辑层弄成了一组又一组的静态函数,但是这也没有降低代码的可测试性。因为业务逻辑被拆分成了一个又一个的近似纯函数的东西,所以可以很容易的通过 Mock DbContext 来针对这些负责处理业务逻辑的函数进行测试,更方便的是,EF 本身提供了一个 InMemoryDb,这让 Mock 数据库的任务变得更加轻松了。

当然,以上的所有内容都是基于这个搬砖项目展开的,所谓的搬砖项目就是几乎完全由 CRUD 组成的各种信息管理系统,并且,系统中出现的所有实体均是贫血模型。针对这样的 XXX 管理系统,三层架构除了能够对代码行数有所提升,其余并没有什么帮助,所以最终我选择放弃三层架构。

本网站所展示的文章由 Zeeko Zhu 采用知识共享署名-相同方式共享 4.0 国际许可协议进行许可

Zeeko's blog, Powered by ASP.NET Core 🐳