We can always follow Udi's advice:
http://www.udidahan.com/2009/06/29/dont-create-aggregate-roots/
IME not creating something because there's some error leads to bad
workflows, because the user is stuck with the problem right now and
can't try again later, otherwise the context and the information they
are trying to send will be lost.
Instead I think it's better to design these kind of operations as
explicit request/proposals and let an inconsistent/incorrect request
lay around while the user eventually corrects and submits it. In this
scenario, every event has an AR and we can even get value from these
partial requests (e.g. which is the most common error).
Best regards,
Daniel Yokomizo
Which aggregate reserves the time slot? Why reservations are handled
by a first come first served strategy? Why leave conflict handling of
the table?
Suppose we have this call:
Schedule.ReserveTimeSlotForAppointment(AppointmentId, TimeSlot)
The Schedule AR (responsible to handle the time of a single
"resource") then checks if it's possible to create the
TimeSlotReservation. Maybe there's a conflict, but let's always create
a TimeSlotReservation. Either one of these is raised
TimeSlotSuccessfullyReserved
ConflictingTimeSlotReserveRequested
The first is straightforward, it creates a TimeSlotReservation. The
second is raised when there's a reservation conflict (i.e. the time
slot or part of it was already reserved, perhaps different events for
the entire or part?) but a TimeSlotRequest is created nonetheless.
Some TimeSlotRequests "become" TimeSlotReservations later.
Now what can we do? Possibly we can show a screen for an user with the
time slot conflicts, perhaps use a bin packing-like algorithm to make
a resolution proposal, create a time slot reservation queue (e.g. if
an appointment is cancelled the second in the list automatically gets
the time slot).
The cancellation scenario can go like this:
TimeSlotReservation.Cancel() --> TimeSlotReservationCancelled
ScheduleConflictSaga (created to monitor
ConflictingTimeSlotReserveRequested of a single Schedule) listens to
TimeSlotReservationCancelled and checks to see if there're any
TimeSlotRequests that can be fulfilled by the time slot cancelled,
possibly more than one. Some sort of priority is used to decide
between candidates.
We always can create an object using an existing parent and the
creation command always can succeed, resulting in something of value
to the business. In a sense a command is always a request, tentative.
If the command can be accepted by the aggregate root, we can create an
permanent expression of the command and let the user submit the
request again.
Another way to express this idea is to think that it's requests all
the way down. Some are transient, because they're immediately accepted
(i.e. successful commands), others are more permanent (i.e. request
entities created when a command "failed"). It (almost) never matters
if the command is a request from an user or a saga, actually it's very
important to don't make distinctions using this criteria, instead
trying to find the real actor behind the command (i.e. who is
interested in the result of the command): whose interests are these
sagas trying to take care of?
> The confusing thing about this whole discussion is that "events
> interesting to the business" get mixed with "events generated by an
> aggregate". I associate "events interesting to the business" with both
> failure and success to complete a command, basically anything that
> "happened" in the system regardless of invariants. While "events used
> to rebuild aggregate state" are things that were allowed to happen
> according to aggregate invariants, even if we don't track that state
> internally. Not things that weren't allowed to happen. So you could
> say I see one as a subset of the other.
>
> Whichever approach is taken, we all tend to agree that failure should
> be communicated (either as an event or a fault message) because
> knowing the "failure" happened is interesting in many ways (steering
> the saga, doing business analysis).
Best regards,
Daniel Yokomizo