1

Thank you in advance for your help and attentation!

My project is dedicated only for learning purposes and I'm totally confused with DDD and have the following situation:

There is the ubiquitous language of my domain where I have users and documents. It says the following:

- 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.

That's enough to understand and answer my question:

Is it normal that a User can contain such methods as: CreateDocument(params), SendDocumentForApproval(docId), ApproveApprovalStepOfDocument(stepId)?

I'm comfused with it because It looks in code a bit strange.

For example for the document creatation process we have something like that:

    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 approval process:

//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?

I want to explain my thoughts in more details. Let's have a look at the user. They manage documents. A Document cannot exist without the user. Does it mean that the User is an aggregate root through we need to create, update, delete its aggregates.

And the document is also an aggregate root due to it contains an apporval process. The ApprovalProcess cannot exist without the document.

Does it mean that I need to do something like this:

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
}

Thank you so much and sorry for my terrible English! Best regards

Ivan avn
  • 77
  • 7

2 Answers2

1

Is it normal that a User can contain such methods as: CreateDocument(params), SendDocumentForApproval(docId), ApproveApprovalStepOfDocument(stepId)?

In most domain models, the method belongs with the entity that manages the state that is going to change.

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

The fact that your sample is updating the document repository here is a big hint that it is the document that is changing, and therefore we would normally expect to see SendDocumentForApproval as a method on the document.

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

(Yes, the code doesn't read like written or spoken English.)

When creating a new document... creation patterns are weird. Udi Dahan suggests that there should always be some entity in your domain model that is responsible for creating the other entities, but I'm not convinced that the result is actually easier to work with in the long term.

How can we model the approval business process

General answer: business processes are protocols, which is to say that you can normally model them as a state machine. Here's the state we are in right now, here is some new information from the outside world, compute the consequences.

(Often, the data model for a process will just look like a history of events; the domain model's job is to then take the new information and compute the right events to store in the history. You don't have to do it that way, but there are interesting possibilities available when you can).

VoiceOfUnreason
  • 52,766
  • 5
  • 49
  • 91
  • Thank you. I think I got you. But I thought that the user entity is an AggregateRoot due to documents can't exist without users and they manage them. If you don't mind, could you answer one more question? A user sends a document for approval. Now we need to change the status of the document. After we need to initiate an approval process. And It contains its steps that we need also to create. – Ivan avn Jan 17 '19 at 15:29
  • I think we are dealing with aggregate roots, aren't we? Document, Process are aggregate roots but Step is an aggregate. Now If I'm not mistaken we can modify, create and delete child entities only through the aggregate root. – Ivan avn Jan 17 '19 at 15:29
  • How can we model the approval business process. First I get a document and do the following: document.SendDocumentForApproval() The next step is to raise an Event that document's status is changed to approving. And I have the following event handler: – Ivan avn Jan 17 '19 at 15:30
  • ChangedStatusEventHandler: //Here I'd like to create an approval Process. If it's the right place for it, how can I do it? Must I create the process through the document entity or I can do just like below? process = new ApprovalProcess(); processRepository.Add(process); – Ivan avn Jan 17 '19 at 15:30
  • I made a side note at the end of my post to show what I mean. Thank you – Ivan avn Jan 17 '19 at 15:56
  • I got it. Thank you! – Ivan avn Jan 18 '19 at 09:16
0

You are headed in a right direction, User and Document both are aggregates as they are created in separate transactions. When it comes to who references whom, IDDD principle of scalability says that aggregates should refer aggregates only via their IDs.

I think sticking to the ubiquitious, language your code should look something like this

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)

I would highly recommend reading Vaughn Vernon's three part aggregate design paper series better aggregete design

Pankaj
  • 302
  • 4
  • 7