-
Hey! First of all, thanks for a great lib and especially for a great blog -- it's one of the clearest and thorough description of this topic I've ever seen. My question is - how do we reconcile our pristine, pure FP domain core with real-life requirements of interactions with external/third-party services, sending notifications and other side-effecty things? I know that usually we move these things to the "outside" circles, but I can't find exact place where to put it in your Fmodel. Should they belong to Application service? After reading Event Modeling Modelling blog, I stumbled on this:
Here it seems like call to external system is done directly inside command handler ( I'd appreciate if you share how you approach this kind of issues in your practical implementations, thank you in advance! |
Beta Was this translation helpful? Give feedback.
Replies: 1 comment 8 replies
-
Hi Twicell, The short answer is that Decider, View, and Saga should stay pure / without side effects of any kind. SagaManager should be responsible for integrating:
SagaManager is acting as an anti-corruption layer in this case, translating:
In this way, the integration is minimizing the runtime coupling between two systems. SagaManager is acting as a translator in the event modeling workshop format. **I plan to write a blog post on this topic very soon and show you the options you have. ** BTW, you can also override the application layer Aggregate and make it use/compose the third-party service. But, there is a reason we have not promoted that by default in our API, more on that in the blog post... For example, you can control how you instantiate your object (eg. EventSourcingAggregate) This is what you have now: fun <C, S, E> EventSourcingAggregate(
decider: IDecider<C, S, E>,
eventRepository: EventRepository<C, E>
): EventSourcingAggregate<C, S, E> =
object : EventSourcingAggregate<C, S, E>,
EventRepository<C, E> by eventRepository,
IDecider<C, S, E> by decider {} You can create your own interface by extending/composing EventSourcingAggregate and your client interface/behaviour: and a factory/constructor function: fun <C, S, E> MyEventSourcingAggregate(
decider: IDecider<C, S, E>,
eventRepository: EventRepository<C, E>,
myClient: MyThirdPartyServiceClient
): MyEventSourcingAggregate<C, S, E> =
object : EventSourcingAggregate<C, S, E>,
EventRepository<C, E> by eventRepository,
IDecider<C, S, E> by decider,
MyThirdPartyServiceClient by myClient {} and compose the handle method (by using In your case you can choose to use that client now: fun <C, S, E> MyEventSourcingAggregate<C, S, E>.handle(command: C): Flow<E> {
return command
.fetchEvents()
.computeNewEvents(command)
.save()
val 3partyResult = callMethodsFrom`MyThirdPartyServiceClient`(...)
} Best and thanks for your questions! |
Beta Was this translation helpful? Give feedback.
Hi Twicell,
The short answer is that Decider, View, and Saga should stay pure / without side effects of any kind.
SagaManager should be responsible for integrating:
SagaManager is acting as an anti-corruption layer in this case, translating:
In this way, the integration is minimizing the runtime coupling between two systems.
Observe how that response/message from the third-party system is always translated into a Comma…