如何開始DDD(續)


上一篇針對用戶注冊案例簡單介紹了如何使用 DDD,接下來我將繼續針對這個例子做一下補充。先將User模型豐富起來,因為目前看上去他和貧血模型還沒有啥大的區別。

首先還是由領域專家來說明業務,他提出了用戶注冊成功后需要完善個人信息,這些信息包括姓名、生日、手機號。還需要用戶提供一些聯系信息,如地址,郵編等。那么我們就可以根據業務定義方法了。昨天netfocus兄指正了loginid所產生的歧義,表示認同,所以今天一並修改了一下。

public class AddressInfo
{
    public AddressInfo(string province, string city, string address, string postcode)
    {
        this.Province = province;
        this.City = city;
        this.Address = address;
        this.Postcode = postcode;
    }

    public string Province { get; private set; }
    public string City { get; private set; }
    public string Address { get; private set; }
    public string Postcode { get; private set; }
}

public class User
{
    public User(string name, string password, string email)
    {
        this.Name = name;
        this.Password = password;
        this.Email = email;
    }

    public string Id { get; private set; }
    public string Name { get; private set; }
    public string Password { get; private set; }
    public string RealName { get; private set; }
    public string Email { get; private set; }
    public string Cellphone { get; private set; }
    public string Birthday { get; private set; }
    public AddressInfo Address { get; private set; }

    public void UpdateBasicInfo(string realName, string birthday, string cellphone)
    {
        this.RealName = realName;
        this.Birthday = birthday;
        this.Cellphone = cellphone;
    }

    public void UpdateAddress(AddressInfo address)
    {
        this.Address = address;
    }
}

那么前端的代碼也很簡單

public class UserController
{
    private readonly IUserRepository _userRepository;
    public void SetProfile(FormCollection form)
    {
        var user = _userRepository.Get(form.Get("id"));

        user.UpdateBasicInfo(form.Get("name"), form.Get("birthday"), form.Get("cellphone"));
    }

    public void SetAddress(FormCollection form)
    {
        var user = _userRepository.Get(form.Get("id"));

        var address = new AddressInfo(form.Get("province"), form.Get("city"), 
            form.Get("address"), form.Get("postcode"));

        user.UpdateAddress(address);
    }
}

以上的代碼很好理解,只是設計了一個AddressInfo的值對象。

接下來將演示一下用戶登錄驗證和修改密碼。一般的做法:

public interface IUserRepository
{
    User GetByName(string loginId);
}

public class UserController
{
    private readonly IUserRepository _userRepository;
    public UserController(IUserRepository userRepository)
    {
        this._userRepository = userRepository;
    }

    public void Logon(FormCollection form)
    {
        User user = _userRepository.GetByName(form.Get("LoginId"));
        if (user == null)
            throw new Exception("loginId", "賬號不存在。");
        if (user.Password != form.Get("Password"))
            throw new Exception("password", "密碼不正確。");

        FormsAuthentication.SetAuthCookie(user.Name, createPersistentCookie);
    }
}

請注意上述代碼比較密碼是錯誤的方式,因為上一篇說明了密碼是加過密的。所以要修改一下,首先要注入IEncryptionService,那么就會這樣判斷

if (user.Password != _encryptionService.Encrypt(form.Get("Password")))

這樣會有什么問題呢。目前IEncryptionService的接口相對還比較簡單,如果IEncryptionService提供了針對不同業務的好多加密接口,那么前端人員就需要詳細了解IEncryptionService的api,增加了復雜度。再對User封裝一個方法,然后對Contoller代碼再稍做修改

public class User
{
    public bool VerifyPassword(string password, IEncryptionService encryptionService)
    {
        return this.Pasword == encryptionService.Encrypt(password);
    }
}

public class UserController
{
    public void Logon(FormCollection form)
    {
        User user = _userRepository.GetByName(form.Get("LoginId"));
        if (user == null)
            throw new Exception("loginId", "賬號不存在。");
        if (user.VerifyPassword(form.Get("Password"), _encryptionService))
            throw new Exception("password", "密碼不正確。");

        FormsAuthentication.SetAuthCookie(user.Name, createPersistentCookie);
    }
}

這樣具體密碼采用了什么加密接口就不用關心了,將此規則封閉在了domain內,還有一個主要目的是為了修改密碼時能夠復用。也許你並不認同這種做法,好像也沒啥變化,當然也沒關系,解決問題就行,我只想表達聚合可以封裝哪些方法。
再仔細考慮我覺得上述代碼表達的業務還是比較多,首先要查詢該登錄名的用戶是否存在,再去驗證密碼,如果需求再有其他規則,如禁用的用戶不能登錄,具有時效性的用戶過期了也不能登錄等等,這樣是不是越來越復雜,前端開發人員需要掌握的業務知識就會越來越多,所以最好將此業務封裝在領域內,ui端只需要傳入登錄名和密碼。
說了這么多,User本身是無法做到這一點的,那么還是要將這些業務規則寫在上一篇提到過的DomainService

public class DomainService
{
    private readonly IUserRepository _userRepository;
    private readonly IEncryptionService _encryptionService;
    public DomainService(IUserRepository userRepository, IEncryptionService encryptionService)
    {
        this._userRepository = userRepository;
        this._encryptionService = encryptionService;
    }

    public User Authenticate(string loginId, string password)
    {
        var user = _userRepository.GetByName(loginId);
        if (user == null)
            throw new Exception("loginId", "賬號不存在。");
        if (!user.VerifyPassword(password, _encryptionService))
            throw new Exception("password", "密碼不正確。");

        return user;
    }
}

public class UserController
{
    public void Logon(FormCollection form)
    {
        try {
            User user = _domainService.Authenticate(form.Get("LoginId"), form.Get("Password"));
            FormsAuthentication.SetAuthCookie(user.Name, createPersistentCookie);
        }
        catch (Exception) {
            throw;
        }
    }
}

這樣是不是更好一點呢?
接下來再來說修改密碼。直接上代碼吧

public class User
{
    public void ChangePassword(string oldPwd, string newPwd, IEncryptionService encryptionService)
    {
        if (!this.VerifyPassword(oldPwd, encryptionService))
            throw new Exception("舊密碼輸入不正確。");

        this.Pasword = encryptionService.Encrypt(newPwd);
    }
}

public class UserController
{
    public void ModifyPassword(FormCollection form)
    {
        try {
            User user = _userRepository.GetByName(User.Identity.Name);
            user.ChangePassword(form.Get("oldPwd"), form.Get("newPwd"), _encryptionService);
            _userRepository.Update(user);
        }
        catch (Exception) {
            
            throw;
        }
    }
}

好吧,這到這里吧,希望對你有幫助。下一篇再繼續討論豐富一下用戶注冊的過程,引入事件驅動。

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM