DDD problems with aggregates and transactions

Viewed 77

for learning purposes I'm trying to build a rest api with some DDD principles. After reading a lot, some things are not quite clear to me.

Before I ask the questions I will show the domain model and an example use case.

Domain model

TagAR
- id
- workspace_id // Reference to WorkspaceAR
- name

WorkspaceAR
- id
- name
- tags // List of Tag ids. Reference to TagAR

ProjectAR
- id
- workspace_id // Reference to WorkspaceAR
- name
- description
- tags // List of Tag ids. Reference to TagAR

NoteAR
- id
- workspace_id // Reference to WorkspaceAR
- name
- tags // List of Tag ids. Reference to TagAR

Some business rules:

  • A Workspace can have multiple Projects and Notes
  • Projects and Notes can have multiple Tags
  • A Tag belongs to a Workspace

Example use case: Add new Project to Workspace (command)

CreateProjectDto:
  workspace_id: int
  name: string
  description: string
  tagNames: string[]

class ProjectCommandService(projectRepo, tagRepo, worksapceRepo):
  function CreateProjectUseCase(dto: CreateProjectDto):
    workspace = workspaceRepo.FindById(dto.workspace_id)
    if workspace is None:
      return Error

    tagIds = []
    for tagName in dto.tagNames:
      tag = tagRepo.FindByName(tagName)

      if tag is None:
        tag = tagRepo.Create(dto.workspace_id, tagName)
        tagIds.Append(tag.id)

    project = NewProject(dto)
    project.AddTags(tagIds)
    projectRepo.Create(project)

    return project.id

1. A transaction shouldn't modify multiple aggregates but what about creating them? In this case I'm referring to the tags that should have been created if one or more are not existing.

In my first domain model iteration the Tag was an Entity inside the ProjectAR but other AR's are also using Tags. Since there shouldn't be shared entities I made the TagAR and every other AR just store the IDs for reference.

In my head there is a conflict. Tag should be an own AR AND only one AR should be modified within one transaction. What is the solution here?

2. An AR should reflect the transaction/ use cases. So here are two more use cases:

a: UpdateProjectDetailsUseCase Description: Update properties (name, description, ...). Not the tags.

b: AddTagToProject/ RemoveTagFromProject Description: Add/ Remove Tags from Project.

There will be the same use cases for NoteAR.

Should I build separate ARs for this? Because each use case doesn't need the whole ProjectAR (invariations). And if so do I have 2 ARs (ProjectDetailAR, ProjectTagAR) or 3 ARs (ProjectDetailAR for update, ProjectTagAR for tagging, ProjectAR for creation)?

If I split the AR, I would create smaller ones which is a goal of AR design. And if I hat ProjectDetailAR and ProjectTagAR the use cases could run in parallel and there wouldn't be a conflict.

3. The tag-check-and-create-routine will be the same in the CreateNoteUseCase. Should it be extracted and if so where should the logic live? Or is it too much abstraction?

I couldn't find any example DDD projects that rely on a tagging system. If anyone knows some or other resources I would appreciate some links.

Thanks in advance

1 Answers

A transaction shouldn't modify multiple aggregates but what about creating them?

Can run into very similar problems.

One of the problems we need to consider if we are trying to build a robust system is "what if it fails?" Distributed storage and atomic transactions are an expensive combination. The decision to couple multiple aggregates to the same transaction has implications on our storage design.

It's not wrong to say "all of our data is stored in a single relational database", or whatever. But if you had ambitions to perhaps be using streams of events in an event store, you need to consider whether it is possible to make atomic changes involving multiple streams.

Another problem with having multiple aggregates in a single transaction is that you are making the locking of your updates more complicated -- and potentially means both more blocking and more "concurrent modification exceptions".

You'll often find folks with DDD experience extolling the benefits of "small" aggregates; but many of those benefits go away when you start stacking them all in the same transactions.

Now, let me be clear:

That doesn't mean that it's wrong to have multiple aggregates in the same transaction.

That doesn't mean that it's wrong to have multiple aggregates in the same transaction.

That doesn't mean that it's wrong to have multiple aggregates in the same transaction.

Instead what it means is that you have to have an awareness of what's really going on in your specific usage to understand whether the behavior of your solution in edge cases is acceptable.

aka "It depends".

Related