core-java icon indicating copy to clipboard operation
core-java copied to clipboard

Consider implementing a mechanism determining whether the command dispatching is a "transactional" operation

Open yevhenii-nadtochii opened this issue 3 years ago • 0 comments

Intro

From a simplified point of view, dispatching of a command to an Aggregate consists of two main steps:

  1. Sending a command to a dedicated command-handling method.

    1.1. This method should validate the received command and decide whether it can be accepted or not. 1.2. If accepted, the handler emits one or more events describing consequences of the command execution. Otherwise, a rejection is returned.

  2. The emitted events are sent to a corresponding event-applying methods in order to modify the aggregate's state.

Subject

The Aggregate's state is usually described through a Protobuf message. Fields in those messages can have validation rules, for example:

message Employee {
  option (entity).kind = AGGREGATE;
  EmployeeId id = 1 [(required) = true];

  // Employee's compensation for their work.
  //
  // Let's take 200$ as the lowest compensation
  // which employers can legally pay their employees.
  int32 salary = 2 [(min).value = "200"];
}

Setting value of salary below 200 would lead to a corruption of the Aggregate's state. The event which led to invalid state would not be stored.

Command-handling methods are supposed to validate commands and NOT emit events which corrupt the Aggregate's state. But what the framework's expected behavior if this happens? There are two cases:

  1. A command resulted into a single event which corrupts the state. This event is neither applied nor stored. This behavior can be considered as intuitive. No questions arise.
  2. A command resulted into two or more events, may be hundreds of events. Let's suppose, x events were applied successfully, and x+1 event failed the state. This event is neither applied nor stored. The rest are considered irrelevant. We will not even try to apply them. But do we have to keep x events, which were already applied successfully? Should we follow the principle "all-or-nothing" when dispatching a command?

Possible solution

The suggested solution is not to follow the principle "all-or-nothing" and keep them stored by default. But let the framework users control this behavior. The framework can provide the annotation to mark a command as "transactional". It would mean that events, emitted by this command, make sense only when applied together.

    @Assign
    List<Event> handle(@Transactional Command cmd) {
        // ...
    }

Open questions

  1. Does this feature relate to Reactors which can also produce multiple events to modify the Aggregate's state?
  2. What is the result of a command's non-transactional dispatching when some events were dispatched successfully, and some not? It looks like a failure with a piece of success, which is sort of confusing. As for now, DispatchOutcome is a clear success or failure, but not something in between.

Implementation

  1. BundleDispatchOutome can be added along with BatchDispatchOutome. Bundle would mean that messages inside are "somehow" related to each other.
  2. Looks like some code in AggregateEndpoint should be moved to AggregateCommandEndpoint, or at least reviewed to clean out commandOutome from there, since there is the specific inheritor.
  3. Make sure that the implemented behavior is consistent between cached and non-cached Aggregate. Use AbstractAggregateResilienceTest for that.

yevhenii-nadtochii avatar Jan 13 '22 15:01 yevhenii-nadtochii