Closed darky closed 2 years ago
Hi @darky, I'm glad you like it!
I suggest reading the series of blog posts I wrote to get a better impression of how to compose a business pipeline/flow out of these three components (Decider, View, Saga): https://fraktalio.com/blog/ (you can start from the first). Second post is very interesting, teaching us how to structure the various data of our domain (commands, events, state) as ADTs (Kotin Sealed inheritance is effectively modeling OR/SUM, and data classes are modeling AND/PRODUCT).
The decider is a data type that represents the main decision-making algorithm. These functions are available on the Decider component. The most interesting one is a combine
function which is effectively making a Decider a Monoid. By using this combine
function you can merge/aggregate/combine two (or more) deciders into one bigger decider.
The logic execution will be orchestrated (composed and combined) by the outside (application layer
) components that use the domain components to do the computations. This project is proposing these two application layer components:
The saga is usually used for mapping different events from one 'decider' into the commands of other 'decider'.
Similar functions/methods exist on the Saga as well. combine
included, making it a monoid.
The view component represents the Query model of the CQRS pattern. It is usually used with event sourcing (in the case of Event-sourcing aggregate) because Event-sourcing aggregate is modeling the Command side only. In the case of State-stored aggregate, the View is not needed because in this traditional approach you model the command and the query side altogether.
Similar functions/methods exist on the Saga as well. combine
included, making it a monoid.
Similar to Decider, we have an 'application layer' component available( materialized view ) enabling you to compose and combine diferent View components/computations under it.
To your example:
Let's imagine someone is streaming to you Events of some sealed
type MyEvent
.
Let's imagine you have to View components modeled independently: View1<MyState1, MyEvent1?>
and View2<MyState2, MyEvent2?>
. Notice that: MyEvent1
extends MyEvent
and MyEvent2
extends MyEvent
.
Now you can construct a materialized view by using this function:
fun <S, E> materializedView(
view: IView<S, E>,
viewStateRepository: ViewStateRepository<E, S>,
): MaterializedView<S, E> =
object : MaterializedView<S, E>, ViewStateRepository<E, S> by viewStateRepository, IView<S, E> by view {}
in where the view
parameter is going to be: view
= view1.combine(view2)
.
Saga is not needed in this case. The MaterializedView
is going to subscribe to these events (streamed via Kafka, for example) and delegate them to your combination of View
components in a type-safe way (with exhaustive pattern matching in place).
You can also use other methods that are defined on the View: mapLeftOnEvent
(Contravariant) or dimapOnState
(Profunctor).
view
= view1.combine(view2).mapLeftOnEvent().dimapOnState
I hope to write more about this (time is always an issue :) )
This library should also help with the transition from Traditional (State-stored) to Event Sourced information systems.
It makes sense to consider our opinionated application
module at first. We are showing the way how these 'domain' components could be combined and composed, taking into account two contexts: state-stored and event-sourced
This library depends on Kotlin Coroutines and the Flow (you might compare that to https://github.com/ReactiveX/RxJava), and it is optimized for streaming from the start (backpressure included).
It is way easier to create async and concurrent programs in Kotlin than in Java (so far), IMHO. Creating custom Flow operators is easy ;)
Best.
@darky I am closing this issue. Thanks for the feedback!
Feel free to initiate further discussions on https://github.com/fraktalio/fmodel/discussions
Hello @idugalic! Awesome work! :+1:
Abstractions, provided by this library, so good on their own. But feels lack of effective way how to compose abstractions together in common pipeline.
For example, I want to pass some
AR
toSaga
and then receive flow ofA
And then, use this flow ofA
as events for multipleView
for evolving theirState
And then, so on... Something like::point_up: It's just example for
View
andSaga
. Composition should work with any direction with any abstractionSources of inspiration:
https://effector.dev/docs/api/effector/effector Effector provides only 4 abstractions:
Event
,Effect
,Store
andDomain
, and huge count of utils how to compose abstractions with each otherhttps://gcanti.github.io/fp-ts/modules/ fp-ts provides Haskell like ADT and many ways how compose one data type to another
https://github.com/ReactiveX/RxJava RxJava provides huge count of operators how to compose one
Observable
to another