在我的上一篇博客:对企业级应用开发的思考--分层 中,从个人的经验分享了关于程序分层方面的内容,得到了众多园友的支持。里面包含对业务逻辑层三种实现方式(事务脚本、活动记录集和领域模型)的简单描述。并没有深入去实现。本文来深入探讨一下。
本文以下面这个实体结构与数据库结构为例:
两个分别表示用户与学科信息(学科名称与分数)
假设有两个业务:
|
数据访问部分统一使用EF,因为数据访问部分不是重点,所以未给出代码,方便大家阅读。来看看几种方式的实现:
事务脚本
先贴代码:
public class TransactionScript { private TestContext dbContext = new TestContext(); //得到平均分 public int GetAverageScore(string username) { int total = 0; int count = 0; var subjects = dbContext.Subjects.Where(s => s.UserInfo.UserName == username); foreach (var s in subjects) { total += s.Score; count++; } if (count == 0) return 0; return total/count; } //修改指定账号的学科分数 public void ChangeScore(string username, string subjectName, int score) { var subject = dbContext.Subjects.FirstOrDefault(u => u.Name == subjectName && u.UserInfo.UserName == username); if(subject == null) throw new Exception("not found"); subject.Score = score; dbContext.SaveChanges(); } }
这里,因为测试需要我的类名起的是TransactionScript,如果把它换成XXService或XXBll的话,我想100%的人都会觉得太眼熟了。就像我在上面那篇文章说的:事务脚本遵循面向过程的开发方式,而不是面向对象的方法。核心思想是为每个业务创建一个过程,每个过程都包含完成业务事务所需要的所有业务逻辑,包括从工作流、业务规则和验证检查到数据库持久化保存的所有内容。
这里操作数据库的方式不管你用普通的Ado.net,还是像文章中高大上的EF或者你用诸如Dapper之类的Orm,如果你的代码风格是这种,很不幸,别和我谈什么领域模型,你就写了个事务脚本,仅此而已!因为在以此为基础的前提下进行开发,在可以想像的未来,你的代码都逃不过这种风格。因为这时候选择的构架模式在后期基本没有重构的可能。
活动记录集
先贴代码:
public partial class UserInfo { private static TestContext _db = new TestContext(); public static UserInfo Find(string name) { return _db.UserInfoes.FirstOrDefault(u => u.UserName == name); } //得到平均分 public int GetAverageScore() { int total = 0; int count = 0; foreach (var s in Subjects) { total += s.Score; count++; } if (count == 0) return 0; return total / count; } //修改指定账号的学科分数 public void ChangeScore(string subjectName, int score) { var subject = Subjects.FirstOrDefault(u => u.Name == subjectName); if (subject == null) throw new Exception("not found"); subject.Score = score; _db.SaveChanges(); } }
注意,这段代码使用了partial关键字,是对EF中的UserInfo类进行扩展,代码风格和上面的事务脚本已经有很大的改变。回忆一下活动记录集的定义:存在一个业务对象代表数据库中的一行或者一张表(至于是行还是表,完全取决于自己选择表数据入口还是行数据入口,和业务层关系不大),在业务对象中包含数据和行为,同时包含用于持久化对象的方式及添加新实例和查找数据集合的方法。
这种风格已经初具面向对象的意识,避免了程序开发中的贫血模型,对象中既包含了数据,又有行为。在业务逻辑没有复杂到把领域模型单独提出来的时候,这是最为理想的开发模式。
领域模型
先贴代码:
//实体 public partial class UserInfo { //得到平均分 public int GetAverageScore() { int total = 0; int count = 0; foreach (var s in Subjects) { total += s.Score; count++; } if (count == 0) return 0; return total / count; } //修改指定账号的学科分数 public void ChangeScore(string subjectName, int score) { var subject = Subjects.FirstOrDefault(u => u.Name == subjectName); if (subject == null) throw new Exception("not found"); subject.Score = score; } } //仓储接口 public interface IRepository { UserInfo Find(string name); void Update(UserInfo user); } //仓储实现 public class Repository :IRepository { private TestContext _db = new TestContext(); public UserInfo Find(string name) { return _db.UserInfoes.FirstOrDefault(u => u.UserName == name); } public void Update(UserInfo user) { var old = _db.UserInfoes.FirstOrDefault(u => u.id == user.id); //automapper user to old _db.SaveChanges(); } } // 应用服务 public class UserService { private IRepository _repository; public UserService(IRepository repository) { _repository = repository; } public int GetAverage(string username) { var user = _repository.Find(username); return user.GetAverageScore(); } public void ModifyScore(string username, string subjectname, int score) { var user = _repository.Find(username); user.ChangeScore(subjectname, score); _repository.Update(user); } }
这是一个基于传统DDD架构风格的代码实现,应用层UserService只负责协调仓储与实现,实体本身不具备持久化能力。但代码量几乎是成倍的增长,如果没有复杂的业务流程及复杂的数据映射结构进行实践,你完全不能体会领域模型的优点。
通过这三段简单的代码可以看出,只有适合自己的,没有哪种业务模式可以成为银弹。
最后总结一下三种业务实现方式(表模块与活动记录集可以合并为一个,只是表数据入口与行数据入口的区别)的优点及适用范围: