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

Allow the aggregate's expected version to be included in command dispatch #482

Open
slashdotdash opened this issue Apr 27, 2022 · 7 comments

Comments

@slashdotdash
Copy link
Member

slashdotdash commented Apr 27, 2022

Add support for including the aggregate's expected version when dispatching a command.

If the aggregate's version:

  • equals the expected version then the command will be executed;
  • is greater than the expected version then the command will be rejected and an error returned (e.g. {:error, wrong_expected_version});
  • is less than the expected version then the aggregate will attempt to fetch any missing events and retry the command.

By default the expected_version option should be passed as :any_version to match the current behaviour.

Example

:ok = BankApp.dispatch(deposit_money, expected_version: 10)
@yordis
Copy link
Contributor

yordis commented Apr 27, 2022

is less than the expected version, then the aggregate will attempt to fetch any missing events and retry the command.

I am not sure if this would be something that we should do.

Scenario

Given a Bucket of money with $20, Person A wants to put $5 to fill up the bucket for the current week to be $25.

The Person A was looking at the screen the bucket said it had $20, so the person made the decision of putting $5 to fulfill the Person A demand.
While that was happening, Person B put $5 already; therefore, Person A put an extra $5 in the bucket.

Having a $25 is not an invariant; such a limit was decision-making among the peers who owned the bucket. Reasons why I would like to redirect the attention to made the decision based on what information we presented.

Concern

If such a scenario does not reject the Command as soon as there is a mismatch, you could successfully fetch any missing events and retry the command, and then it will put the extra $5 in the bucket.

As I said before, such a limit is not an invariant, so the consistent aggregate check will not help much. Such a decision was purely an Actor making a decision based on the information they had.


What would happen in that scenario?

@slashdotdash
Copy link
Member Author

slashdotdash commented Apr 27, 2022

The case where the aggregate's current version is less than the expected version is only really possible in a multi-node deployment where you have the same aggregate instance process running on different nodes and one of these receives the command but hasn't yet received the most recent event(s). Therefore it's state is stale due to eventual consistency of events being published to all nodes.

In this scenario it is acceptable to attempt to fetch any missing events and then do the expected version check again. This time the aggregate's version should be equal to the expected version (accept command) or could be greater than the expected version (reject command). If the aggregate version is still less than the expected version then the command can be rejected too.

@yordis
Copy link
Contributor

yordis commented Apr 27, 2022

From the Slack conversation,

The expected_version could be a tuple also:

[expected_version: {:gte, 20}]

Supported Filter

  • eq: equal to
  • gte: greater or equal than

@mksaga
Copy link

mksaga commented Jul 26, 2023

@slashdotdash @yordis hello! I've been reading up on Elixir and Commanded and hope to use it for a startup soon, would love to help with closing out this issue!

For the :any_version default option, would it make more sense to leave that as a standalone default case? Trying to think about how to more cleanly capture both the filter suggestion (which I think is a great idea!) & the any_version default.

Thoughts on typing expected_version to be:
{:gte, integer} | {:eq, integer} | {:eq, :any_version} versus
{:gte, integer} | {:eq, integer} | :any_version?

@yordis
Copy link
Contributor

yordis commented Jul 26, 2023

{:gte, integer} | {:eq, integer} | :any_version

I would go for that, but either way, it would work.

I suggest making it work first, and then you can bikeshed over the final version.

Go with your gut feeling for now!

@mksaga
Copy link

mksaga commented Aug 2, 2023

@yordis or @slashdotdash could I get contributor access to push up a pull request branch addressing the issue? Thanks!

@yordis
Copy link
Contributor

yordis commented Aug 2, 2023

@mksaga I will suggest forking the repository. Push the branch to your repository fork and then create a PR.

(Also, I do not have access to the Org at all, I am just another external contributor)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Development

No branches or pull requests

3 participants