serverlesstechnology / cqrs

A lightweight, opinionated CQRS and event sourcing framework.
Other
362 stars 37 forks source link

Saga implementation #15

Open sahina opened 2 years ago

sahina commented 2 years ago

Do you have a saga implementation planned?

davegarred commented 2 years ago

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:

jonaslimads commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

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 commented 2 years ago

@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 commented 1 year ago

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 commented 1 year ago

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 commented 1 year ago

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