Disclaimer: I know that DDD/CQRS might not be a perfect choice for below use case, but it was working great so far for other tasks in the project and I want to stick with it for learning purposes (eg. learn the hard way where not to use it ;).
Let's assume I'm building a Blogging Platform and have 3 types of posts: LifestylePost
, RecipePost
and ReviewPost
. They have the same abstract base class PostBase
which is an Aggregate Root and all of them share some properties like Author
or implement methods like Publish
, Delete
etc. which change their Status
and verify whether the state is valid. Each created post starts as a Draft
which barely requires any validation, but switching to Published
verifies nearly all properties.
Implementing commands/handlers like PublishPost
or DeletePost
was a breeze, but the problem starts when I'm thinking about CreatePost
or UpdatePost
. These are some dilemmas I'm currently facing:
- Create/update commands/handlers for each post types or one for all
Having them separately for each post types gives me more control over the logic, allows to tailor each command model precisely, but makes my API more complex for the client. With the second option I can resolve the post type by some discriminator in the command and check whether all necessary properties for that particular type were provided.
- Create empty post draft first or already filled with the data
When a user creates a new post (draft with an initial state) I can already call the API and add an empty post to the database, which would then be updated or I can wait until user inputs any data and clicks save. It's basically a matter of the arguments in the constructors of the posts.
- Handling post updates in general
This is the point where I'm having the biggest issues now. Since there are quite many properties that could or could not be changed by the user, it's hard to split them to particular methods inside the Aggregate Root different than just Update
with huge amount of nullable arguments where null would mean that the property has not been provided by the client and therefore not changed. Also adding Status
property here would mean that the proper method for validating the state would have to be resolved. This solution somehow doesn't feel like a proper DDD design...
Which decisions would you make in each points and why?
CodePudding user response:
- Create/update commands/handlers for each post types or one for all
Depends on whether the difference in post types has influence over the API or not. If your clients have differentiated interactions with your API depending on the post type, then I would have specific commands for each type. If your API is post type agnostic, then this is internal business concerns. You should then ensure that domain implementation is sufficiently different from type to type, because polymorphic domain is a lot more work than a simple "category" property.
- Create empty post draft first or already filled with the data
Depends on whether knowing a user has started working on a draft has any value for your system. If it's like a personal blog, probably not. If you're writing software for a newspaper, then it could be insightful to anyone. Ask your domain experts.
- Handling post updates in general
This is an interface design question rather than a business logic one in my opinion. If your users want to do a lot of small changes, you should consider providing PATCH routes in your API, with a standard like JsonPatch. Depending on your implementation technology, you could benefit from libraries that does the object patching for you, saving a lot of code writing.
CodePudding user response:
Is there really a difference in behaviour between the post types? Don't they all have Draft()
, Publish()
, Cancel()
behaviours?
Given that inheritance means X is a Y
, then you're basically saying they are all the same. This feels to me like a single Post
aggregate with the possibility of a "PostType" value somewhere that might be part of an invariant (e.g. if you had a business rule that says "Review Posts cannot be published until a cooling-off period has elapsed).
This would mean a single set of application services to invoke those methods (and validate the invariants they implement).