在我的上一篇博客:對企業級應用開發的思考--分層 中,從個人的經驗分享了關於程序分層方面的內容,得到了眾多園友的支持。里面包含對業務邏輯層三種實現方式(事務腳本、活動記錄集和領域模型)的簡單描述。並沒有深入去實現。本文來深入探討一下。
本文以下面這個實體結構與數據庫結構為例:
兩個分別表示用戶與學科信息(學科名稱與分數)
假設有兩個業務:
|
數據訪問部分統一使用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只負責協調倉儲與實現,實體本身不具備持久化能力。但代碼量幾乎是成倍的增長,如果沒有復雜的業務流程及復雜的數據映射結構進行實踐,你完全不能體會領域模型的優點。
通過這三段簡單的代碼可以看出,只有適合自己的,沒有哪種業務模式可以成為銀彈。
最后總結一下三種業務實現方式(表模塊與活動記錄集可以合並為一個,只是表數據入口與行數據入口的區別)的優點及適用范圍: