在理解DDD的过程中出现了一个令人困惑的情况

时间:2019-01-17 14:42:18

标签: oop design-patterns domain-driven-design ddd-repositories ddd-service

在此先感谢您的帮助和关注!

我的项目仅用于学习目的,我对DDD完全感到困惑,并遇到以下情况:

我的域中存在着用户和文档都可以使用的通用语言。它显示以下内容:

- A user can create a document. One of the main purpose of my project is to provide users an ability to create different documents. I mean that the documents cannot exist without the users. So,I think that the process of a document creation belongs to my domain.
- A user can send a document for approval. It's one more thing that belongs to the domain. An approval process is one of the most important part of the project. It has its steps that other users must confirm.
- A user can approve a step of approval process.
- A user can reject a step of approval process.

足以理解并回答我的问题:

用户包含诸如CreateDocument(params),SendDocumentForApproval(docId),ApproveApprovalStepOfDocument(stepId)之类的方法是否正常?

我很困惑,因为它在代码中看起来有些奇怪。

例如,对于文档创建过程,我们有类似的内容:

    public async Task<bool> CreateDocumentCommandHandler(CreateDocumentCommand command)
{

    //We have our injected repositories
    User user = await _userRepository.UserOfId(command.UserId);

    Document document = User.CreateDocoment(command.*[Params for the document]);

    _documentRepostiory.Add(document);

     // It raises event before it makes a commit to the database
     // It gets event from an entity. The entity keeps it as readonly collection.
     // Here it raises DocumentCreatedEvent. This event contains logic which concerns
     // creation some additional entities for the document and log actions.
    await _documentRepository.UnitOfWork.SaveEntitiesAsync();
}

批准过程:

//The first try out to model this process:
public async Task<bool> SendDocumentForApprovalCommandHandler(SendDocumentForApprovalCommand command)
{
    //We have our injected repositories

    User user = await _userRepository.UserOfId(command.UserId);

    //Here I have some problems.
    //Is it okay that the method returns the document?
    //The method that is placed inside the User has this logic:
    //public Document SendDocumentForApproval(int docId)
    //{
    //   Document document = this.GetDocument(docId);
    //   
    //   //Inside this method ChangedStatusToApproving is created
    //   document.SetStatusToApproving();
    //   return document;
    //}
    Document document = User.SendDocumentForApproval(command.DocId);

    _documentRepostiory.Upadate(document);

     // It raises event before it makes a commit to the database
     // It gets event from an entity. The entity keeps it as readonly collection.
     // Here it raises ChangedStatusToApproving. This event contains logic which concerns
     // creation some additional entities for the document and log actions.
    await _documentRepository.UnitOfWork.SaveEntitiesAsync();
}
//Is it okay to do something like the command handler above?

//The second one:
public async Task<bool> SendDocumentForApprovalCommandHandler(SendDocumentForApprovalCommand command)
{
    //We have our injected repositories
    User user = await _userRepository.UserOfId(command.UserId);

    //The same one as we have in the previous method.
    //But here I don't want to put the logic about the changing status of the doucnent inside it.
    Document document = User.SendDocumentForApproval(command.DocId);
    //I see that it breaks the method above (SendDocumentForApproval)
    //Now It doesn't mean anything for our domain, does it?
    //It is only getter like User.GetDocument or we can even do it
    //by using repository - documentRepository.DocumentOfId(docId)
    document.SetStatusToApproving();

    _documentRepostiory.Upadate(document);

    await _documentRepository.UnitOfWork.SaveEntitiesAsync();
}

// So, I think the first one is better, isn't it? It follows the ubiquitous language. 

//And here is the final question: Why can't I do it like this:
public async Task<bool> SendDocumentForApprovalCommandHandler(SendDocumentForApprovalCommand command)
{
    //Here we don't want to use the userRepository. We don't need at all
    //Here as a consequence we also don't need a user entity
    //Everything what we need is:
    Document document = _documentRepository.DocOfId(command.DocId);
    document.ForApproval();

    _documentRepostiory.Upadate(document);

    await _documentRepository.UnitOfWork.SaveEntitiesAsync();
}
//I think that the last approach breaks the ubiquitous language and we're about to having an anemic model.
//But here we have only two queries to the database because we don't need a user.
//Which of the approaches is better? Why? How can I do it more correctly if I want to apply DDD?

我想更详细地解释我的想法。 让我们看一下用户。他们管理文件。没有用户,文档将不存在。这是否意味着用户是我们需要创建,更新,删除其聚合的聚合根。

该文档也是一个汇总根,因为它包含审批流程。没有文档就无法存在ApprovalProcess。

这是否意味着我需要执行以下操作:

public async Task<bool> SendDocumentForApprovalCommandHandler(SendDocumentForApprovalCommand command)
{
    Document document = _documentRepository.DocumentOfId(command.DocId);

    document.SendForApproval();

    _documentRepository.SaveChangesAsync();//Raise a domain event - SentDocumentForApprovalEvent
}

// Here we have a handler for the event SentDocumentForApprovalEvent
public async Task SentDocumentForApprovalEventHandler(SentDocumentForApprovalEvent sentDocumentForApprovalEvent)
{
    //Now I want to create an approval process for the document
    //Can I do the next thing:
    ApprovalProcess process = new ApprovalProcess(sentDocumentForApprovalEvent.DocId);

    _approvalProcessRepository.Add(process);

    _approvalProcessRepository.SaveEntitiesAsync();//Raise a domain event - InitiatedApprovalProcessEvent

    //Or Should I create the approval process through Document?

    //Looks terrible due to we need to call the repostiory amd
    ApprovalProcess process = Document.InitiateApprovalProcess(sentDocumentForApprovalEvent.DocID);//Static method

    _approvalProcessRepository.Add(process);

    _approvalProcessRepository.SaveEntitiesAsync();

    //log
}

// Here we have a handler for the event InitiatedApprovalProcessEvent
public async Task InitiatedApprovalProcesEventHandler(SentDocumentForApprovalEvent sentDocumentForApprovalEvent)
{
    //The same question as we have with handler above.
    //Should I create steps trough the approval process or just with the help of constructor of the step?

    //log
}

非常感谢您,我的英语糟糕! 最好的问候

2 个答案:

答案 0 :(得分:0)

  

用户包含诸如CreateDocument(params),SendDocumentForApproval(docId),ApproveApprovalStepOfDocument(stepId)之类的方法是否正常?

在大多数领域模型中,该方法属于管理将要更改的状态的实体。

Document document = User.SendDocumentForApproval(command.DocId);
_documentRepository.Update(document);

您的样本正在此处更新文档存储库这一事实表明,正在更改的文档是一个很大的提示,因此,我们通常希望将SendDocumentForApproval作为文档上的一种方法。

document.SendDocumentForApproval(command.UserId)
_documentRepository.Update(document);

(是的,代码读起来不像书面或口头英语。)

创建时,创建模式很奇怪。 Udi Dahan建议您在域模型中总应该有一些实体负责创建其他实体,但是我不认为从长远来看,结果实际上更容易处理。

  

我们如何为审批业务流程建模

一般答案:业务流程是协议,也就是说,您通常可以将它们建模为状态机。这是我们现在所处的状态,这是来自外界的一些新信息,可以计算后果。

(通常,流程的数据模型看起来就像是事件的历史; domain 模型的工作是获取新信息并计算出正确的要存储在历史记录中的事件。您不必那样做,但有可能的话有很多有趣的可能性)。

答案 1 :(得分:0)

您的方向正确,用户和文档都是聚合的,因为它们是在单独的事务中创建的。当涉及到谁引用谁时,IDDD的可伸缩性原则表示聚合应仅通过其ID引用聚合。

我认为坚持使用无处不在的语言,您的代码应该看起来像这样

class User {
    private UserId id;
    private String name;    

    User(String name) {
        this.id = new UserId();
        this.name = name;            
    }

    Document createDocument(String name) {
        Document document = new Document(name);        
        document.createdBy(this);
        return document;
    }

    Document approve(Document document) {
        document.approved();
        return document;
    }
}

class Document {
    private DocumentId id;
    private String name;
    private UserId userId;
    private Boolean isApproved;

    Document(String name) {
        this.id = new DocumentId();
        this.name = name;
    }

    void createdBy(UserId userId) {
        this.userId = userId;
    }    

    void approved() {
        this.isApproved = true;
    }

}

// User creation
User user = new User("Someone");
userRepository.save(user);

//Document creation
User user = userRepository.find(new UserId("some-id"))
Document document = user.createDocument("important-document")
documentRepository.save(document)

// Approval
User user = userRepository.find(new UserId("some-id"))
Document document = documentRepository.find(new DocumentId("some-id")) 
document = user.approve(Document)

我强烈建议您阅读沃恩·弗农(Vaughn Vernon)的三部分汇总设计论文系列better aggregete design