3

I have a aggregate root named "Project". It has some basic attributes like name, duration, documents.

I have another aggregate root named "Task". It also has some basic attributes like name, duration,documents and some more.

We have scenario where a user can add more than 200 tasks to project.

Creating List of tasks as one of attributes of Project seems unlikely because of memory consumption.

However we another scenario where when user adds a task and provides duration, we need to find minimum date & maximum date from list of tasks and assign that duration to project

We can't use eventual consistency here, because we want the updates to be immediately reflected.

I was thinking of below approach

 public class TaskService {
       public void addTaskToProject(TaskCommand command)
       {
            Project project = this.projectRepo.getProject(command.projectId);
            Task task = Task.create(command);


            if(task.startDate < project.startDate)
              project.startDate = task.startDate;

          //Multiple agree gate updates
          taskRepo.save(task);
          projectRepo.save(project);
       }
    }

Another approach:

public class TaskService {
           public void addTaskToProject(TaskCommand command)
           {
                Project project = this.projectRepo.getProject(command.projectId);
                Task task = Task.create(command);
                task.assignDuration(command.duration, project);

              //Multiple agree gate updates
              taskRepo.save(task);
              projectRepo.save(project);
           }
        }

public class Task
{
    Date startDate;
    Date endDate;

    public void assignDuration(Duration duration, Project project)
{
      this.startDate = duration.startDate;
      this.endDate = duration.endDate;

      if(this.startDate < project.startDate)
         project.changeStartDate(this.startDate);

     if(this.endDate > project.endDate)
         project.changeEndDate(this.endDate);

}

Reasons for not having tasks list as another attribute of Project is memory and there are chances that multiple people can add tasks to same project.

  • Your Task object stores two dates. Two-hundred tasks would only consume about 12K of memory. Are you sure you have a memory problem? – Robert Harvey Dec 9 '19 at 15:32
  • @RobertHarvey - Each task will not have just dates, it will have title, set of documents, priority and some html editor text. – Rohit Dec 9 '19 at 16:24
  • More worried about html text and documents because those will take up huge space. – Rohit Dec 9 '19 at 17:13
  • Can tasks exist outside projects, i.e. what’s the rationale for having an own aggregate root for tasks from the ddd perspective ? – Christophe Dec 9 '19 at 18:17
  • Your first approach is sound and reasonable, though obviously with the check for end date as well. However I too wish to voice concern over how heavy weight Task is. I would consider using a component system to store the heavier aspects of Task in another object such as TaskDocument (or other aptly named classes) related by a common identifier. That would allows many hundreds of tasks to be addressable in memory, and allow the heavy weight aspects to be shuffled in and out of memory on demand. – Kain0_0 Dec 10 '19 at 0:03
1

There is here a confusion between design and implementation concern.

The problem

According to your requirements, tasks should belong by design to the project aggregate, since they do not exist on their own, and they should be treated as a unit for the purpose of change:

Task's don't exist without project. (...)

However we another scenario where when user adds a task and provides duration, we need to find minimum date & maximum date from list of tasks and assign that duration to project (...)

However, you do not stick to this reality in view of an implementation issue:

Creating List of tasks as one of attributes of Project seems unlikely because of memory consumption.

And the resulting sub-optimal design leasds you to your question regarding consistency. The aggregates are meant to solve exactly the kind of consistency issues you have here !

The solution

The key to success in software engineering is separation of concerns. And it starts with separating design concerns and implementation concerns:

  • You need two entities Project and Task
  • But the both entities belong the the same aggregate root ProjectAggregate
  • Change your API to reflect this design and get rid of the unnecessary service.
  • Solve your implementation issue in the implementation, not in the design. Typically, you do not need to load all the tasks together with the project. You can opt for a lazy load

This solution makes you handle changes to project as a consistent unit. This does not prevent you from storing projects and tasks in separate tables / documents / whatever.

And your concurrency strategy is yet another concern. THis design doesn't force you to lock the aggregate when it is loaded by a user. You can still have the aggregate used and updated by several users consistently concurrently. The question is how to lock data and manage conflicts. And given the relation between project and tasks, regardless of your design you would have to manage multi-user conflicts.

| improve this answer | |
  • Thanks for inputs. So basically we will have most of use cases like "createTask", "changeProjectDuration" in this new aggregate, which will internally use Project & Task to perform operations. Right ? – Rohit Dec 10 '19 at 10:44
  • @Rohit yes, that’s the idea – Christophe Dec 10 '19 at 12:08
1

We can't use eventual consistency here, because we want the updates to be immediately reflected.

If you were modeling your problem domain without software, say with sticky notes for tasks, the project duration wouldn't be instantly updated when someone hands you a new task. You'd accept the sticky first ("create the task"), then go back and (eventually) recalculate the project duration. There's going to be a delay (however small) between putting the sticky up on the board and updating the project duration.

If you were to model this in software, "creating a task" would simply mean storing it in the database and raising an event. Then, upon eventually processing this event, you recalculate the project duration. In practice, the entire process will only take a few milliseconds, which pretty much looks "immediate" to human beings. Yet this solves the technical issues with memory and locking.

Vaughn Vernon has an excellent series of articles on aggregate design where he goes into much more detail about these ideas, and highly recommends keeping aggregates small and using eventual consistency across aggregate boundaries. Here's a relevant quote:

Domain experts are sometimes far more comfortable with the idea of delayed consistency than are developers. They are aware of realistic delays that occur all the time in their business, whereas developers are usually indoctrinated with an atomic change mentality. Domain experts often remember the days prior to computer automation of their business operations, when various kinds of delays occurred all the time and consistency was never immediate.

| improve this answer | |
  • I understand the advantages eventual consistency brings on board. However adding eventual consistency will have extra cost associated with it. User while creating a task will be also shown project duration on UI, Now if I don't update the project duration immediately and wait for event framework to process it ,user won't get updated data immediately. This was actually reported to us by clients who are using system. – Rohit Dec 10 '19 at 10:37
  • 1
    @Rohit The project is eventually consistent after a task is added - it is a fundamental constraint of computing within a Von Neumann machine. The problem you are trying to solve is not "eventually" but "timeliness". Your customer is complaining that the update to the project is not happening in a timely manner after they have added a task. There are numerous ways to do this. The UI could guess and preupdate the cache which is fast but not authoritative. If it has to be reloaded, the UI could somehow identify "dirty" fields by displaying a loading symbol, or greying fields out while it reloads. – Kain0_0 Dec 10 '19 at 23:51
  • @Kain0_0 - That's right. I will surely try out the suggestions mentioned by you. – Rohit Dec 11 '19 at 3:54
  • @Rohit: Kain0_0 has some good ideas on how to solve the UI issues. While this may add a bit of development effort right now, it will make your system easier to extend and more scalable in the long term. Immediate consistency will force you to lock the entire project before adding a task, and that can actually make your UI less responsive than simply accepting a task and updating the duration subsequently (it's very likely that if you fetch the duration after your UI has loaded, the update will already be complete). – casablanca Dec 11 '19 at 3:59

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.