在開始DDD之前,你需要了解DDD的一些基礎知識,聚合(AggregateRoot)、實體(Entity)、值對象(ValueObject),工廠(Factory),倉儲(Repository)和領域服務(DomainService)。在這里值對象有區別於C#的值類型,請不要將兩者混淆,一開始我也范了這個錯誤。聚合並不是設計的越大越好,相反的我們應該盡量為聚合划分最小范圍。
從一個簡單的例子開始。用戶注冊,三層結構的做法基本上就這樣
public class User { public string Id { get; set; } public string LoginId { get; set; } public string Password { get; set; } public string Name { get; set; } public string Email { get; set; } public string Cellphone { get; set; } ... } public class UserService { private readonly UserDao _userDao; public void Register(User user) { _userDao.Save(user); } } public class UserController { private readonly UserService _userService; public UserController(UserService userService) { this._userService = userService; } public void Register(FormCollection form) { User user = new User(); user.LoginId = form.Get("LoginId"); user.Password = form.Get("Password"); user.Name = form.Get("Name"); user.Email = form.Get("Email"); user.Cellphone = form.Get("Cellphone"); ... _userService.Register(user); } }
上面的代碼就是UI傳什么數據過來就給User相應的屬性賦值,這種做法往往是憑我們的經驗及對這種業務的普遍性做出的設計。他存在一點問題,用戶注冊到底需要哪些信息並不明確,這些具體業務我們並不知道。那么領域專家提出來了,為了簡化用戶注冊流程,只需要新用戶提供登錄名,密碼及郵箱就行了,且用戶名不能修改。
OK。那么UI的代碼就會變成
public class UserController { public void Register(FormCollection form) { User user = new User(); user.LoginId = form.Get("LoginId"); user.Password = form.Get("Password"); user.Email = form.Get("Email"); _userService.Register(user); } }
似乎問題解決了。但仔細想想以上代碼基本上沒有什么業務,也沒有規則,更像是添加一條數據記錄,而且要命的是用戶名不能修改根本沒辦法體現。
如果用了DDD后那么會有怎樣的變化呢?
public class User { public User(string loginId, string password, string email) { this.LoginId = loginId; this.Password = password; this.Email = email; } public string Id { get; private set; } public string LoginId { get; private set; } public string Password { get; private set; } public string Name { get; private set; } public string Email { get; private set; } public string Cellphone { get; private set; } ... } public interface IUserRepository { void Add(User user); void Remove(User user); } public class UserController { private readonly IUserRepository _userRepository; public UserController(IUserRepository userRepository) { this._userRepository = userRepository; } public void Register(FormCollection form) { User user = new User(form.Get("LoginId"), form.Get("Password"), form.Get("Email")); _userRepository.Add(user); } }
第一步應該將模型的描述屬性定義為值對象,所有狀態的變化都是通過業務行為來改變。這樣也就很自然的看到一個新用戶檔案從無到有的過程,即new一個User,需要提供登錄名,密碼及郵箱且用戶名不能修改,這也完全吻合領域專家的要求,有點領域驅動要你怎么做的意思了吧。
接下來領域專家又提出來了,密碼不能采用明文,需要加密,用什么加密方式還不確定。我靠,這還要怎么寫代碼。其實還是可以繼續設計,抽象出一個加密規則的接口
public interface IEncryptionService { string Encrypt(string password); }
至此好像new一個user變得有些復雜了,首先構造函數不能解決密碼加密的問題,怎么辦?那就是通過Factory
public class UserFactory { private readonly IEncryptionService _encryptionService; public UserFactory(IEncryptionService encryptionService) { this._encryptionService = encryptionService; } public User Create(string loginId, string password, string email) { return new User(loginId, _encryptionService.Encrypt(password), email); } } public class UserController { private readonly IUserRepository _userRepository; private readonly UserFactory _userFactory; public UserController(IUserRepository userRepository, IEncrypt encrypt) { this._userRepository = userRepository; this._userFactory = new UserFactory(encrypt); } public void Register(FormCollection form) { User user = _userFactory.Create(form.Get("LoginId"), form.Get("Password"), form.Get("Email")); _userRepository.Add(user); } }
這樣就領域中封裝了業務規則,在前端人員根本不需要感知密碼是怎么加密的這些業務,而且代碼也是簡單的。
接下來領域專家又提出了一個規則就是用戶名必須唯一。那么問題又來了,工廠構造出來的user並不能保證用戶名是唯一的,如果讓這些業務在ui端做判斷,事必增加前端開發人員的工作量,同時按照DDD的原則是不應該將具體業務暴露出來的。怎么辦,那就要引用Domain Service了。簡單修改下代碼
public interface IUserRepository { void Add(User user); bool IsLoginIdExist(string loginId); } public class DomainService { private readonly IUserRepository _userRepository; public DomainService(IUserRepository userRepository) { this._userRepository = userRepository; } public void Register(User user) { if (_userRepository.IsLoginIdExist(user.LoginId)) { throw new Exception("用戶名已存在"); } _userRepository.Add(user); } } public class UserController { private readonly UserFactory _userFactory; private readonly DomainService _domainService; public UserController(IUserRepository userRepository, IEncrypt encrypt) { this._domainService = new DomainService(userRepository); this._userFactory = new UserFactory(encrypt); } public void Register(FormCollection form) { User user = _userFactory.Create(form.Get("LoginId"), form.Get("Password"), form.Get("Email")); _domainService.Register(user); } }
第一篇就寫這么多吧,基本上DDD的相關知識都涉及了一點。接下來將慢慢豐富此例子。