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
DecideFunction - why Vec<E> #7
Comments
Hi, thanks for your question! Decider belongs to the pure domain layer. It is not doing the fetching or storing events or any other side effect on this level. Decider is already returning a list/vector of events. It is valuable to model error events, and in the case of a business error you can publish an error event instead of a Result or Panic. Please notice how these error events are business errors, not infra/technical errors. Events are modeled with Enum, so you can do pattern matching on them, same as Result! I agree that one command should produce only one event. But there are exceptions to that rule sometimes. The Vector is more abstract and it covers all the cases. I hope this blog post will help: https://fraktalio.com/blog/unmanaged-hazards-exceptions.html Once you have your Decider in place you are ready to run it. Application layer components will use that Decider to compute and repositories to fetch/store the data. EventSourcedAggregate is that component. It is using/composing the aggregate API that you can use from your adapters: Web. // Handles the command by fetching the events from the repository, computing new events based on the current events and the command, and saving the new events to the repository.
pub async fn handle(&self, command: &C) -> Result<Vec<(E, Version)>, Error> {
let events: Vec<(E, Version)> = self.repository.fetch_events(command).await?;
let mut version: Option<Version> = None;
let mut current_events: Vec<E> = vec![];
for (event, ver) in events {
version = Some(ver);
current_events.push(event);
}
let new_events = self.decider.compute_new_events(¤t_events, command);
let saved_events = self.repository.save(&new_events, &version).await?;
Ok(saved_events)
} On this level (application level) you have a |
We are also using |
Thanks so much for pushing this work forward as a community project ❤️ 👏🏻 🎉
As a practitioner of Event Sourcing based on Greg Young's training materials across a couple companies and three languages now, I have some in-the-weeds questions if you are open to discussing them here? If not, that's fine, and thanks!
1. Why does
DecideFunction
returnVec<E>
?Yes, it totally makes sense to return Events rather than imperatively store them
In a traditional CommandHandler as taught by Greg Young we often see in C# an interface like this:
👍🏻 I'm 100% understanding (and agreeing!) that a design goal of Fmodel appears to be bringing some power of functional programming to these patterns, so of course we want to return the Events.
By contrast the traditional Greg Young-provided code patterns as above are deeply imperative, and as we see in official examples, the
void Handle(TCommand message)
implies the following dependency management chain being called as side effects:ICommandHandler#Handle
has anIRepository
(see DDD Repository pattern)IRepository#Save(AggregateRoot aggregate, int expectedVersion)
if you are doing Event Sourcing for this aggregate has anIEventStore
IEventStore#SaveEvents(Guid aggregateId, IEnumerable<Event> events, int expectedVersion)
has a concrete implementation of recording the events to the stream ofaggregateId
or returning an Optimistic Concurrency error if theexpectedVersion
is behind the last event saved in the streamSo it makes sense to return the Event(s), but why a
Vec
?Yes, it totally makes sense that sometimes a Command generates 2+ domain Events
Sometimes the best way to model the domain is for one Command to result in multiple Events (e.g. an Axon framework example, an Elixer example, a C# example).
Although I vaguely recall that in one of the training videos, Greg Young suggests that we should always question instances of 1:N Command:Events pattern when it comes up -- I vaguely recall the suggestion that 1:1 pairing between Command and Event are generally best at modeling that "a user intent succeeded and it is now a fact", and so this should be the common case?
So it make sense there could be 0, 1, or more Events, but why a
Vec
?Do we really need to allocate an owning
Vec
on every single Command that succeeds?My understanding is that by making the return type of
DecideFunction
Vec<E>
, we are saying a side-effect of every single decision about aCommand
in the system will be a heap allocation. Is this the right approach? I'm assuming the following:If those assumptions are correct, I was wondering if the return type should be more like:
In other words, an explicitly modeled rejection of the command, or an interface that allows a zero cost implementation such as a slice of 1-2 Events on the stack?
Q1: Would such an approach allow returning a slice containing a single Event on the stack, avoiding allocation but preserving that the return is iterable?
Q2: Why does Fmodel not model Rejection of Commands in a first class way, such via a
Result
type?Thank you!
The text was updated successfully, but these errors were encountered: