Skip to content

[Question] Domain Design of Collaboration System in Epilogue #305

Open
@gregbrowndev

Description

@gregbrowndev

Hi there,

First, I want to thank you for your book. It has been a tremendous help in my journey so far in learning DDD. I am beginning to apply what I've learned to a Django project that has caused me much pain over the last 2 years. Many of the larger concepts have started to click into place, which feels amazing. However, I am struggling with the basic domain modelling of my problem.

The only reason I ask such for specific help is that it is similar to the document collaboration system that you talk about in the epilogue.

The app involves allowing transport authorities to publish different types of datasets via basic file uploads. Each dataset type may have different rules and processes, e.g. validation. However, the part that I'm having the most difficulty with is the generic CMS functionality that you would find in any off the shelf CMS.

In brief, the publisher creates a 'draft' version of their dataset which must be submitted, validated and then reviewed before it can be published. And, to edit a published dataset, a new draft must be submitted, reviewed, etc. There can only be a single draft for review at a time, and the user(s) can edit the draft any number of times (though note: real-time collaboration isn't the purpose of the app). Once a version has been published, it cannot be modified.

The challenge I'm having is in defining what the aggregate should look like, where the responsibility should lie for how the client creates and edits the revisions, and how to make the root responsible for these invariants.

The code snippet below defines a 'Publication' aggregate. Partly the reason I'm not 100% confident is that as it states in blue book (in the chapter about factories):

If the client is expected to assemble the domain objects it needs, it must know something about the internal structure of the object.

but it also says:

A Factory Method on the aggregate root encapsulates expansion of the aggregate.

so I can see the start_revision method is like a factory method to expand the aggregate, but it still feels quite awkward that the client has to pass back and forth the revision objects and thus be aware of the Publication's internal structure.

Do you have any tips on how I can improve my approach to tackling this design problem? Or how you dealt with Document version tracking from the project that you worked on? Thank you for any help you may offer.

from pydantic import BaseModel
from pydantic.generics import GenericModel

T = TypeVar("T", bound="Revision")
PublicationId = NewType("PublicationId", int)

class Publication(GenericModel, Generic[T]):
    id: PublicationId
    organisation: OrganisationId

    live: Optional[T]
    draft: Optional[T]
    revisions: List[T]

    # start_revision() -> T       -- return (partial) copy of `live` or empty T object
    # save_revision(revision: T)  -- assigns revision to `draft`
    # publish_revision()          -- assuming valid, assign `draft` to `live` and append to `revisions`


class Revision(BaseModel):  # can be extended for different types of datasets
    publication_id: PublicationId
    created_at: datetime.datetime

    name: str
    description: str
    comment: str

    published_at: Optional[datetime.datetime] = None
    published_by: Optional[UserId] = None

    def can_edit(self):
        return self.published_at is None

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions