The simplified scenario is the following: there is a BC (Bounded Context) called "tasks" which contains the Task
Aggregate, and a BC called "meetings" which contains the Meeting
Aggregate.
// in BC "tasks"
class Task extends AggregateRoot {
private TaskId taskId
private string name
private string description
...
static func register(TaskId taskId, ...): Task { ... }
func rename(string newName) { ... }
...
}
// in BC "meetings"
class Meeting extends AggregateRoot {
private MeetingId meetingId
private DateTime meetingDate
...
static func plan(MeetingId meetingId, ...): Meeting { ... }
func postpone(DateTime newMeetingDate): void { ... }
func scheduleTask(TaskId taskId): void { ... }
...
}
You can schedule Task
s for a Meeting
, which will be discussed when the meeting happens, but there are a few rules:
Task
must explicitly mark it as "ready for meeting", because the creation process can be long and the Task
can be "incomplete" for a while (e.g. document must be added but were not sent, the description is not clear or incomplete...)Task
can only be scheduled for a single Meeting
, at the end of which an Opinion
must be expressed on the Task
(something along the line of "is valid", "is invalid", "ok but this needs to be changed")Task
s eligible to be scheduled for the next Meeting
(i.e. not draft but not already added to another Meeting
)I am not sure how and where to model the state relative to the status
of the Task
("draft", "ready for meeting", ...) and about the Opinion
.
What I've tried so far was to add a status
property to Task
which starts at "draft" and can be changed to "ready for meeting" via a specific operation:
class Task extends AggregateRoot {
...
private Status status = Status.draft
...
func markAsReadyForMeeting(): void {
// let's ignore other checks, Domain Event publishing etc.
this.status = Status.readyForMeeting
}
...
}
But at this point I don't know:
Task
availability is on the "tasks" BC (is Task
draft?) and another part is in the "meetings" BC (is this Task
already scheduled in a Meeting
?)Task
and Meeting
, since a Meeting
must hold to a list of TaskId
s, but if I were to add to Task
's Status
the case scheduled(MeetingId)
it would feel like a duplication of information which must be kept in syncOpinion
s are expressed in the context of a Meeting
, but should be saved on a Task
... so what?The other thing I have thought of was to have a "simplified" Task
model in the "meetings" BC and manage the status in there and not in the "tasks" BC. At this point there will be no Status
or Opinion
in the "tasks" BC, and the act of "making a Task ready for meeting" will be implemented on the "meetings" BC and not in the "tasks" one.
I have the feeling that this can be a better approach since it appears to me that the "meetings" BC could operate in autonomy, but it also feels that in this way there is a lot of duplication of data between the two BCs (both have a complete list of all Task
s, albeit the contained information is different).
Is my modeling wrong, there is something I'm missing? Or should more integration effectively exist between the two BCs?
As a final note: the two BCs are more complex than this simplified example and are composed of more parts, and I believe that they should remain separated, but I still remain open to explore a "refactoring" approach.
Bounded contexts should be designed around use cases and not object structures like persistence model do. You are partly right in the approach of putting the ready-for-meeting (RFM) state and the Opinon concepts in the Meeting context. The justification behind that is that these concepts do not exist outside of the meeting context, ie: there would not be a ready-for-meeting status, nor Opinions if there was no meeting in your system.
What you are missing, in my opinion, is that you should not confuse draft and RFM states. Draft status should be handled in the Task context as you already do, as it controls the state of the Task outside the meeting concept. The Meeting context would subscribe to Task "undrafted" events. This would allow the Meeting BC to maintain a list of non-draft tasks, and associate them with meetings. The Meeting context is then able to provide a list of undrafted tasks, not associated with a meeting, which is your definition of RFM tasks.
The Task context don't need to know whether the Task is associated to a meeting or not, and if the meeting is planned or has already happened. If you want to prevent the Task context from altering tasks once they are associated with a meeting, you could maintain a readonly state in the Task context. The Task context would subscribe to a "task associated with meeting" event in the Meeting context and would update the readonly state of the Task.