Comments (27)
Hi @richardmiller. Yes, aggregate roots of different types but with the same ID appear to receive each other's events. It is assumed you're using UUIDs to generate IDs for your aggregate roots (as auto-increment is not feasible), and collisions are not likely to occur. I have used the DBAL event store and I enforce uniqueness across roots by creating a primary key of (uuid, playhead) on the event stream table.
from broadway.
Hi, I'm using UUIDs - it's not a collision I'm worried about it. If I have a Car aggregate and a Garage aggregate and I have a uuid for a Car and accidentally use that uuid to load something from the Garage EventSourcingRepository then it will find the Car related events and try and replay them in a Garage aggregate instance.
from broadway.
Ah! Yes, you'd want an AggregateNotFoundException
. This seems like an issue that lies in the EventStoreInterface
rather than your aggregate (it should fail before that). Receiving a clean aggregate of a different type is unacceptable to me. @asm @wjzijderveld, any reason the event stream is not limited by the aggregate's class?
from broadway.
Hrm, I think you guys have the wrong @asm...
from broadway.
Heh, @asm89 😅
from broadway.
If I have a Car aggregate and a Garage aggregate and I have a uuid for a Car and accidentally use that uuid to load something from the Garage EventSourcingRepository then it will find the Car related events and try and replay them in a Garage aggregate instance.
I guess my question is, how is this really a problem? You're calling load($id)
on a `EventSourcingRepository`` object, which knows what type of aggregate class it is loading and reconstituting.
Now, while it's true that you could pass $carId
to a Garage repository and it would load the events for that id and replay them, it's likely that a failure would occur when replaying the events to reconstitute. Of course it's possible it wouldn't fail and in that case you'd (I think) still have back a Garage object, it just wouldn't be in a known object state and you'd have other issues anyway.
But, theoretically, how is this different than using any other repository (say, Doctrine)? If I have a car that had an integer id of 7 and a garage with an integer id of 7, and I call $garage = $garageRepository->find($carId)
, it's essentially the same thing. I'm going to get back a valid $garage
object, just not the one I want.
That being said, if there is no present way to ensure this from happening, I guess it would be possible to store the aggregate class in the event store along with the other data and throw an error if the repository is trying to reconstitute events for an aggregate of a different type. I didn't check if there's any sort of mechanism in place for this already
from broadway.
I guess it is the same as in any other case if you not using uuids and there can be ids that are the same for different entities.
from broadway.
@richardmiller Not saying you're wrong, btw. Just pondering from the theory side (in case I wasn't clear from my original reply 😄)
from broadway.
The application should obviously not request a Garage from a GarageRepository with a CarId. However, a situation like this can arise quite simply with someone tinkering with UUIDs (URLs, forms) and can result in illegal actions on an inconsistent aggregate or at least very weird behaviour. It seems like a hard-to-prevent situation that may cost someone hours to track down. Unless performance issues are expected it seems only logical to limit the event stream based on aggregate type.
from broadway.
I ran into this testing an API with dredd where I copied the wrong uuid, so indeed it would not be a normal occurrence in the use of the application. It did result in misleading errors that took a while to work out so it would be helpful if an exception that pointed to the actual cause was thrown.
from broadway.
Yes, we should probably throw an exception and check if the events actually belong to the aggregate root we're loading. I think the aggregate factory could/should be responsible for checking this?
from broadway.
@asm89 @richardmiller @rjkip
How would you think to solve this?
As I don't see a lot of possibilities here... How would the repository (or factory) know which events (or aggregate id's) the aggregate (and it's child entities) can handle?
I'm probably just missing something, so please enlighten me :-)
from broadway.
@wjzijderveld The DomainMessage has the type, we can assert if that's the same as the current object when replaying or when retrieving from the event store?
from broadway.
The DomainMessage has the type, we can assert if that's the same as the current object when replaying or when retrieving from the event store?
@asm89 I also thought this was the case, but it holds the event's type, so it cannot be inferred from that. As the aggregate also accepts events it doesn't handle (eg. chose to no longer handle), afaik it can't be detected at this moment.
The solutions as I see them:
- Add some kind of discriminator to the event store (FQCN with each event, different tables, whatever). This would be a huge BC break as, to upgrade one's event store, one would have to enrich the currently stored events with their aggregate's FQCNs.
- Discriminate on the ID level (eg. some special prefix). When working with VOs, GarageId would then not accept a CarId's format. Weird, but BC. A userland solution.
- Enforce that aggregate roots apply events, explicitly ignore events or throw. Not sure about this.
Nothing appeals to me at the moment. Number two is a solution that can be implemented by users today.
/cc @wjzijderveld
from broadway.
After thinking about his and discussing this with a colleague, option two is silly and three puts the responsibility of checking whether events belong to the root solely on the root.
Regardless of whether an aggregate root should check whether it supports events it receives (Axon does not enforce this), an aggregate root should imo never even receive events that belong to another aggregate root type. This seems to point a finger at the EventStore.
To me, it seems only prudent to let the EventStore discriminate on aggregate root type, just like Axon does. However unfortunate, this involves users having to update existing events with their root's FQCN. That shouldn't be that hard, though.
What are your opinions on this, @asm89 @wjzijderveld?
from broadway.
I'm looking into the snapshotting PR and looked at Axon for that as well. It starts to make more and more sense to me to add that discriminator to the event store.
from broadway.
I think we need this before we can have proper snapshotting support, so I did some looking around in other frameworks/libraries/eventstores how they handled this and found the following:
Project | Method |
---|---|
NEventStore | string bucketId, passed to save/load methods in event store |
AxonFramework | string type, passed to save/load methods in event store |
EventCentric | Contract $contract, passed to save/load methods in event store (can be any string, can be created from an object) |
litecqrs | string Aggregate Classname, retrieved from EventStream |
GetEventStore | StreamId passed to save/load methods |
So most other libraries just pass the identifier around when writing/retrieving the event stream. I would like to take that route with Broadway as well.
Implementing this in Broadway is fairly easy, the migration is tricky, but doable. I would like to start with this asap and hopefully provide some tools for the migration.
For the migration I was know thinking about a simple script (separate from Broadway) that updates the database based on a mapping: Aggregate -> supported Events
from broadway.
Don't forget https://github.com/szjani/predaddy
from broadway.
I didn't list all of them and didn't want too many PHP versions :)
But Predaddy uses a AggregateId object, which also contains the aggregate class.
from broadway.
So most other libraries just pass the identifier around (...)
@wjzijderveld Am I correctly interpreting this to be the aggregate FQCN string? If so, 👍 for your suggestions.
from broadway.
@rjkip Not necessarily, because you are not required to use DDD with event sourcing, you might not have aggregates. I would propose to go with streamId (string)
, which in practice will be the FQCN of the aggregate 😄
from broadway.
Go @wjzijderveld 👍
from broadway.
Just had the realization that streamId isn't the best name.. and also that I didn't look good enough when listing the above, as EventCentric uses a Contract for this. The streamId there is the aggregateId (which makes a lot of sense).
Naming will be decided later, but probably not streamId 😄
from broadway.
Hi,
I'm just browsing a bit through the issues and found this interesting one. @wjzijderveld why not list all PHP EventStores? We can all learn from each other! However, I had to think about the same issue in the past and decided to solve it by using event metadata. IMO the event store shouldn't be aware of aggregates and their types. Responsibiltiy of the event store is to store events in a stream. Responsibility of the EventSourcingRepository is to append these events to the stream and load them to reconstitute an aggregate. So in my ES the repository adds the aggregate type (and also the aggregate id) as metadata to each event and uses the same for loading.
Btw. things get even more interesting when you have something like:
class Car extends Vehicle {
//...
}
Just my two cent.
from broadway.
@codeliner I didn't mention all because there are way to many when looking cross-language :-) I tried to get a list of the bigger / more well known projects.
I like the idea of seeing it as meta-data. But (especially with the aggregateId) I don't really see how you would achieve that? Depending on your storage engine, it would be quit hard to retrieve events based on a source of events (the aggregate in most cases)?
from broadway.
See: https://github.com/prooph/event-store-zf2-adapter/blob/master/src/Zf2EventStoreAdapter.php#L146
I use - well let's call it - duck typing: metadata
is an associative array with the key beeing the column name (esp. aggregate_id, aggregate_type) and the value beeing some kind of string that can be used to query events. It is a generic key-value query logic that enables you to organize your event streams the way you need it.
The metadata can be used for other stuff too. In a project I also add the dispatch_status
of an event as metadata. This allows me to use the event stream as a queue to pull not yet dispatched events from it.
from broadway.
This issue has been automatically marked as stale because it has not had recent activity. It will be closed if no further activity occurs. Thank you for your contributions.
from broadway.
Related Issues (20)
- drop test/Broadway/TestCase
- make PHPUnit a dependency HOT 5
- test the test helpers with examples HOT 2
- Built-in asynchronous way to run processors? HOT 3
- Example/explanation on event sourcing for aggregate roots HOT 1
- What is the overall state of the project? Is it GDPR ready? HOT 2
- Simple Command Bus - Manage Throwable exceptions
- Could you do a new release? HOT 2
- Are there any plans to update broadway? HOT 1
- Asymmetry between EventBus interface and EventListener interface HOT 2
- Broadway does not seem to survive hot upgrades HOT 2
- duck-typing vs interface HOT 1
- Replaying events to rebuild elastic search index HOT 1
- PHP 8 support HOT 2
- Processor after projector HOT 2
- Aggregator HOT 3
- Indirect development dependency used in src ConcurrencyConflictResolvers
- no recent tags? HOT 1
- Get uncommitted events without empty the aggregate HOT 4
- Give serializers flexibility to map based on event type
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from broadway.