Skip to content
This repository has been archived by the owner on Mar 14, 2024. It is now read-only.

Reflect-based aggregate system. #10

Closed
jmalloc opened this issue Apr 30, 2018 · 5 comments
Closed

Reflect-based aggregate system. #10

jmalloc opened this issue Apr 30, 2018 · 5 comments
Labels
enhancement New feature or request

Comments

@jmalloc
Copy link
Owner

jmalloc commented Apr 30, 2018

Aggregates are a specialization of sagas that can only handle commands and produce events.

Reflection would be used to wire up an implementation of saga.Saga based on the methods of an "aggregate". This would be a return to the "magic" from early prototypes of dogma (When and Do methods).

@jmalloc jmalloc added this to the 0.2.0 - Public API milestone May 16, 2018
@jmalloc jmalloc changed the title Create "aggregate" system. Reflect-based aggregate system. May 16, 2018
@jmalloc jmalloc added the enhancement New feature or request label May 25, 2018
@jmalloc
Copy link
Owner Author

jmalloc commented May 25, 2018

I've been thinking about how this will work a little more, specifically in regards to autowiring the saga instance mapping, that is, the logic that determines which saga instance a message is routed to.

For a "hand rolled" saga, this is achieved by the two MappingKeyXXX methods " of saga.Saga.

With aggregates, we always want to select an instance directly by its ID (which, by the way, means that the "aggregate ID" is one and the same as the "saga instance ID").

The MappingKeysForInstance() method is a non-issue, we would simply use an implementation that always returned a "key set" containing only the instance ID. (As an aside, we could probably change the saga message handler to avoid using saga.Mapper when using this kind of "instance-id based mapping", eliminating a database round-trip).

The MappingKeyForMessage() method is a little more difficult because it is message specific. That is, for each type of command message that the aggregate can receive, we need to be able to determine the saga instance ID. To give an example, if we were to use this for the "banking example" account aggregate, we'd need to know that the AccountId field is the aggregate ID in those messages.

I'll put forth a few options on how this might be done:

1. Add a method to the message itself

In the early dogma prototypes, this was achieved by having an AggregateID() method on the Command interface.

The benefit of this approach is that it localises the message-type-specific logic to the message itself. However, I've steered clear of this in Ax because the intent is that the messages form the public API. Which saga instance a message is routed to, or whether it is handled by a saga at all is an implementation detail that does not need to be known by the endpoints that the message.

Not only that, but since these messages are the public API, it might be logical to place their definition in some shared library that is consumed by multiple endpoints. It'd be a pain to require a new version of that shared library just because some routing rules/implementation details change.

2. Require the user to implement MappingKeyForMessage()

Just as they would have to for a "hand rolled" saga. Perhaps a constrained version of this method that must return a saga.InstanceID.

One advantage of this approach is that it remains flexible, and we may be able to provide common implementations by embedding behavioural structs.

The obvious downside is that this isn't magic at all! It would likely involve a type-switch on the message type which is one of the things we're trying to avoid in the reflect-based interfaces.

3. Require the user to specify the field-name that contains the instance ID

All commands that are routed to the aggregate would need to share a common field name that contains the aggregate ID. It's a fairly safe bet that this would be the case naturally, and even if a field name didn't match initially it could be changed with relative ease because the messages are always protocol buffers messages.

4. Allow the user to chose from one of the above options

Perhaps the saga message handler could be changed to look for sagas that implement one of the above methods, and change the mapping strategy dynamically.

Each of these mapping strategies could be used regardless of whether you're implementing an aggregate, or "hand rolling" a saga.


I really don't know what's best here. I'm leaning towards the the fourth option, as its the most flexible while retaining simple configuration options for the user, but it'll will be more complex.

@jmalloc
Copy link
Owner Author

jmalloc commented May 25, 2018

This is going to require a change to the saga package that allows the "event applier" (currently provided by implemented saga.EventedData) to be separated from the data itself.

This is because we want to allow users to write When/Do methods on the protocol buffers struct itself, rather than a single ApplyEvent method, and we can't generate that method for the user at runtime due to unresolved issues with Go's reflect package (golang/go#16522 and/or golang/go#15924).

jmalloc added a commit that referenced this issue May 26, 2018
…r than the data.

This is in preparation for #10, which requires separation of the event handling and the data instance.
@jmalloc
Copy link
Owner Author

jmalloc commented May 26, 2018

This is going to require a change to the saga package that allows the "event applier" (currently provided by implemented saga.EventedData) to be separated from the data itself.

This turned out to be quite easy, see #62.

@jmalloc
Copy link
Owner Author

jmalloc commented May 26, 2018

I've made a quick PR for this (#63) -- I went with option 4. from the list above for the mapping strategies. I've only implemented support for the "field name" strategy at the moment, you can see how that's configured by the user here at the bottom of the account example.

@jmalloc
Copy link
Owner Author

jmalloc commented May 31, 2018

Fixed by #63

@jmalloc jmalloc closed this as completed May 31, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

1 participant