貧血模型與充血模型比較 - DDD - The Domain Driven Design

banq發表於2019-06-16

在這篇文章中使用Vaughn Vernon的書[ IDDD,2013 ] 的例子來描述SCRUM模型的情景,並能夠以實際的方式展示貧血模型和富模型的實現之間的區別。

讓我們說產品負責人:

允許將每個積壓項分配給Sprint。如果它已經分配給不同的Sprint,則必須先將其解除分配。分配完成後,通知相關方。

一個非常簡單的場景,可以在下面分層顯示:

貧血模型與充血模型比較 - DDD - The Domain Driven Design

儘管在圖中有表示,但我們將在示例中忽略Task。

貧血模型

領域/實體

public class Sprint
{
    public int Id { get; set; }
    public IList<BacklogItem> BacklogItems { get; set; }
    public SprintStatusEnum Status { get; set; }
    public string Description { get; set; }
    public DateTime BeginDate { get; set; }
    public DateTime EndDate { get; set; }
}

public class BacklogItem
{
    public int Id { get; set; }
    public IList<Task> Tasks { get; set; }
    public int? SprintId { get; set; }
    public int? UserId { get; set; }
    public BacklogItemStatusEnum Status { get; set; }
    public string Description { get; set; }
    public DateTime? BeginDate { get; set; }
    public DateTime? EndDate { get; set; }
}

領域/服務

public class SprintServices : ISprintServices
{
    private readonly ISprintRepository _sprintRepository;
    private readonly IBacklogItemRepository _backlogItemRepository;
    public SprintServices(ISprintRepository sprintRepository, IBacklogItemRepository backlogItemRepository)
    {
        _sprintRepository = sprintRepository;
        _backlogItemRepository = backlogItemRepository;
    }

    public void InsertBacklogItem(int sprintId, int backLogItemId)
    {
        var sprint = _sprintRepository.GetById(sprintId);
        var backLogItem = _backlogItemRepository.GetById(backLogItemId);

        backLogItem.SprintId = sprintId;
        backLogItem.Status = BacklogItemStatusEnum.Committed;

        EmailService.SendMail("destination@email.com",
            $"The backlog item '{backLogItem.Description}' was assigned to Sprint '{sprint.Description}'");

        _backlogItemRepository.Update(backLogItem);
    }
}

請注意,它們的實體沒有業務邏輯,每個規則都依賴領域服務,實體屬性設定沒有任何控制,設定這些屬性後沒有驗證,聚合不生成域事件。

這樣的物件只是資料容器。

現在看看富血模型......

域/實體

public class Sprint : Entity
{
    public Sprint(string description, DateTime beginDate, DateTime endDate)
    {
        Status = SprintStatusEnum.New;
        Description = description;
        BeginDate = beginDate;
        EndDate = endDate;

        Validate();
    }

    public int Id { get; private set; }
    public IList<BacklogItem> BacklogItems { get; private set; }
    public SprintStatusEnum Status { get; private set; }
    public string Description { get; private set; }
    public DateTime BeginDate { get; private set; }
    public DateTime EndDate { get; private set; }

    public void SetStatus(SprintStatusEnum status) => Status = status;
    public void SetDescription(string description) => Description = description;
    public void SetBeginDate(DateTime beginDate) => BeginDate = beginDate;
    public void SetEndDate(DateTime endDate) => BeginDate = endDate;

    public void Validate()
    {
        if (string.IsNullOrEmpty(Description))
        {
            throw new Exception("Description can not be null");
        }

        if (BeginDate > EndDate)
        {
            throw new Exception("EndDate must be greater than BeginDate");
        }

        //more rules...
    }
}
public class BacklogItem : Entity
{
    public BacklogItem(string description)
    {
        Status = BacklogItemStatusEnum.New;
        Description = description;

        Validate();
    }

    public int Id { get; private set; }
    public IList<Task> Tasks { get; private set; }
    public int? SprintId { get; private set; }
    public int? UserId { get; private set; }
    public BacklogItemStatusEnum Status { get; private set; }
    public string Description { get; private set; }
    public DateTime? BeginDate { get; private set; }
    public DateTime? EndDate { get; private set; }

    public void SetSprintId(int? sprintId) => SprintId = sprintId;
    public void SetUserId(int? userId) => UserId = userId;
    public void SetStatusToNew() => Status = BacklogItemStatusEnum.New;
    public void SetStatusToCommitted() => Status = BacklogItemStatusEnum.Committed;
    public void SetStatusToApproved() => Status = BacklogItemStatusEnum.Approved;
    public void SetStatusToDone() => Status = BacklogItemStatusEnum.Done;
    public void SetDescription(string description) => Description = description;
    public void SetBeginDate(DateTime? beginDate) => BeginDate = beginDate;
    public void SetEndDate(DateTime? endDate) => BeginDate = endDate;

    public void CommitToSprint(Sprint sprint)
    {
        if (IsCommittedToSprint())
        {
            UncommitFromSprint();
        }

        SetStatusToCommitted();
        SetSprintId(sprint.Id);

        this.AddDomainEvent(new BacklogItemCommitted
        {
            Id = Id,
            SprintId = SprintId.Value
        });
    }
    public void UncommitFromSprint()
    {
        SprintId = null;

        this.AddDomainEvent(new BacklogItemUncommitFromSprint
        {
            Id = Id,
            SprintId = SprintId.Value
        });
    }
    public bool IsCommittedToSprint() => SprintId != null && SprintId != default(int);

    public void Validate()
    {
        if (string.IsNullOrEmpty(Description))
        {
            throw new Exception("Description can not be null");
        }

        //more rules...
    }
}

應用:

public class BoardApplication : IBoardApplication
{
    private readonly ISprintRepository _sprintRepository;
    private readonly IBacklogItemRepository _backlogItemRepository;
    public BoardApplication(ISprintRepository sprintRepository, IBacklogItemRepository backlogItemRepository)
    {
        _sprintRepository = sprintRepository;
        _backlogItemRepository = backlogItemRepository;
    }

    public void ToAllocateBacklogItemToaSprint(int sprintId, int backLogItemId)
    {
        var sprint = _sprintRepository.GetById(sprintId);
        var backLogItem = _backlogItemRepository.GetById(backLogItemId);

        backLogItem.CommitToSprint(sprint);

        _backlogItemRepository.Update(backLogItem);
    }
}

你能看到區別麼?

第一個例子使用了一種非常以資料為中心的方法,而不是行為方法。它不是一個真正的領域模型。

在我們的Rich Model示例中,我們使用表達泛在語言的域物件的行為。

它不會向客戶端公開資料屬性,而是公開一種行為,該行為明確且清楚地表明客戶可以將Backlog專案分配給Sprint。

如果不將此豐富的行為插入Backlog項,則客戶端必須處理事件,這是非常錯誤的。

在第二個例子中,好處要大得多。

現在,您能看到使用Rich Model的好處嗎?

 

相關文章