Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Saga implementation #15

Open
sahina opened this issue Jan 14, 2022 · 13 comments
Open

Saga implementation #15

sahina opened this issue Jan 14, 2022 · 13 comments
Labels
enhancement New feature or request

Comments

@sahina
Copy link

sahina commented Jan 14, 2022

Do you have a saga implementation planned?

@davegarred
Copy link
Collaborator

It is under consideration for after v0.3 is released, but it's an area that can go a lot of different directions depending on what we want to support.

More likely would be components that better support building downstream applications which can be used to provide much of the same sort of functionality. Some of these that are planned are:

  • Kafka integration (followed by Kinesis and SQS)
  • simpler event replay (particularly support within GenericQuery)

@serverlesstechnology serverlesstechnology added the enhancement New feature or request label Feb 1, 2022
@jonaslimads
Copy link

jonaslimads commented Apr 29, 2022

Hi,
first off, thanks for the great and clean framework, it has taught me a lot on the subject.

Correct me I'm wrong, but if one has multiple aggregates, each an independent CqrsFramework instance, a Process Manager or Saga wrapper would have to know what is going with those aggregates' events in order to "rollback" (or delete) past events upon any command fail in a given multi-bounded context flow. Plus the views should only be updated once all flow is finished. Is this doable within the current traits/implementation? Or is this even a right approach?

Or if it is not doable yet, what steps are required to achieve? I was looking at your code to see if I was able to hack something around or contribute someway, I'm using this framework to build a marketplace POC for a personal project that I hopefully open source someday.

Thanks a lot!

@serverlesstechnology
Copy link
Owner

Hi @jonaslimads, thanks for the interest!

You're correct that two aggregates should use two framework instances. If these are running on the same server, the easiest way to tie them together is via a query. E.g.,

struct MyQuery {
  downstream_cqrs: CqrsFramework<DownstreamQuery>,
}

impl Query<UpstreamAggregate> for MyQuery {
  async fn dispatch(&self, aggregate_id: &str, events: &[EventEnvelope<UpstreamAggregate>]) {
    for event in events {
      if UpstreamAggregateEvent::EventOfInterest == event {
        self.downstream_cqrs.execute(aggregate_id, DownstreamCommand::TheCorrectResponse);
      }
    }
  }
}

That being said, you should never rollback events after they are committed. An aggregate should have all the information that it needs to make the decision of whether or not to commit events, so it could be that your two aggregates should actually be modeled as a single aggregate.

Assuming you've correctly modeled them as two aggregates, in the event of some sort of error of the downstream aggregate, you should fire a new command on the upstream aggregate that puts it into some sort of error state. Note that this is going to be tricky to do with another query since queries are injected in the framework constructor, I've added an issue to track fixing that and will include that in the next version.

@jonaslimads
Copy link

so it could be that your two aggregates should actually be modeled as a single aggregate

You're right, I was making a separate Warehouse aggregate with stock values instead of having a single Product aggregate that owns its stock, which actually makes the business logic and testing easier.
Data modeling is actually the hardest part of CQRS/DDD when coming from a CRUD + relational DB background because we naturally go for normalizing.

Thanks for your reply, I'll refer to the snippet you posted when dealing with downstreams.

Is it acceptable to generate a view from two separate aggregates' events? Not that I have this need for now, but just to know if and how. GenericQuery only accepts events from its own aggregate, so Query is the one that could do this plumbing like your snippet, right?

@serverlesstechnology
Copy link
Owner

Awesome! One of the benefits of CQRS is that the rigid enforcement of DDD rules helps to identify flaws in your domain model early on when it's easy to fix.

On using views modified by more than one aggregate, absolutely. Views are very cheap in CQRS and it's not uncommon to have many specialized queries/views per aggregate. One full-stack application that I worked on years ago had two aggregates but over 10 different views between them, most were modified by both event sets.

That's difficult to configure with the GenericQuery since it expects the aggregate id to also act as the view id. In the future, event repositories will support secondary indices which will simplify this, but for now you will need to write a custom query.

@jonaslimads
Copy link

It's been quite enlightening to know that CQRS and DDD makes you focus on your domain model and logic whereas other patterns leave you mixing domain model and infrastructure.

Thanks for all the help!

@tk-o
Copy link

tk-o commented Aug 7, 2022

FWIW, it'd be great to use a pub/sub container to broadcast updates from a query to other aggregates.

This way, the original query (of the upstream aggregate) could avoid knowing anything about the consumers interested in its updated state (any number of downstream aggregates).

What do you think, folks?

@tk-o
Copy link

tk-o commented Aug 7, 2022

Also, with the pub/sub model, it might be a good idea to connect the events together, like here:

Would the correlation/causation metadata be useful to attach to the EventEnvelope type?

@serverlesstechnology
Copy link
Owner

serverlesstechnology commented Aug 7, 2022

@tk-o in practice, I isolate every aggregate into it's own service and (when the design allows) pass events between these services asynchronously via a custom query (example here). I prefer using AWS SNS between serverless Lambdas and Kafka between standard application instances, but most any pub/sub service can be used here.

If correlation/causation or other tracking information is desired, it should be added to the metadata within an event. Currently EventEnvelope is not serializable so passing more than just the event payload to a messaging system will require a custom serializable DTO.

@kubehe
Copy link

kubehe commented Feb 19, 2023

Hey, is this idea still considered?

It would be amazing to have a rust alternative to what java world has to offer.
For example in Axon framework saga is quite useful pattern.

@davegarred
Copy link
Collaborator

Hi @kubehe, in my experience the sagas in Axon framework tend to be too rigid for many rapidly changing situations such as adding many upcasters or changes around the business rules that tend to end up in Sagas (and dynamic environments are just the sort of thing that CQRS targets).

A lightweight saga implementation is still under consideration if we can find the right form for it, but we may instead just add an example demonstrating how to implement long-lived, multi-aggregate functionality using views.

@kubehe
Copy link

kubehe commented Feb 25, 2023

I think that such an example would be also a great addition :)

@VanOvermeire
Copy link

(Bump) An multi-aggregate example or lightweight saga would be great!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

7 participants