cingaldi / sagapattern

An example of implementation of a Saga. Spring and Kotlin are used for this example
4 stars 0 forks source link

Leakage of business logic #1

Open meiedomi opened 3 years ago

meiedomi commented 3 years ago

Something seems to be odd here. You host the logic to create commands inside the saga state entity (domain layer), but you map incoming events to follow-up commands in the saga manager (application layer). Shouldn‘t both of these things be part of the state entity such that everything business-related is in one place and no leakage happens?

I'd appreciate your opinion about this objection. ;)

cingaldi commented 3 years ago

Hello @meiedomi sorry for the late reply

I've reflected about these as well. While dispatching command (defined in application layer) inside saga state (domain layer) can effectively appear as a violation of dependency inversion principle, what I've tried to do is to not add domain logic in the saga manager, leaving it only the responsibility of acting as "glue code" between the events and the core logic.

An alternative could be returning a desired state from the saga state model (eg using an enum) and making the association desired state/command in the saga manager

roughly, something like that (in pseudocode)

onEventReceived(event) {
    state = sagaState.checkSomething(event.associationData)

    if (state == SOME_STATE) {
        dispatchCommand(new SomeCommand())
    }
}

And what about you? how would you address this aspect? 🙂

meiedomi commented 3 years ago

Hey @cingaldi what you suggest would be one way of eliminating the asymmetry between commands and events. The other way would have been to shovel the events right into the saga state and thus into the domain layer.

So inside your saga manager, you could just pass the event down into the saga state:

onEventReceived(fooEvent) {
    sagaState.handleFooEvent(fooEvent.property1, fooEvent.property2);
   ...
}

Note that I wouldn't pass the fooEvent DTO directly into the handleFooEvent method, as the former lives in the app layer while the latter is defined in the domain layer. Instead of passing the individual properties, I could have used a domain-specific event type and mapped from the fooEvent to that one first.

This way, both the events and commands reside in the saga state. The manager only translates, without changing semantics or having to know anything about the domain. If you think about it: In your original solution, if the manager receives an event and then calls some saga state method, how does it know which method to call? How does it know how to translate the event into a change in the saga state? That by itself could be considered domain logic.