如何開始DDD


在開始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的相關知識都涉及了一點。接下來將慢慢豐富此例子。


免責聲明!

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



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