Giter Site home page Giter Site logo

Comments (27)

rjkip avatar rjkip commented on July 4, 2024

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.

richardmiller-zz avatar richardmiller-zz commented on July 4, 2024

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.

rjkip avatar rjkip commented on July 4, 2024

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.

asm avatar asm commented on July 4, 2024

Hrm, I think you guys have the wrong @asm...

from broadway.

rjkip avatar rjkip commented on July 4, 2024

Heh, @asm89 😅

from broadway.

mbadolato avatar mbadolato commented on July 4, 2024

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.

richardmiller-zz avatar richardmiller-zz commented on July 4, 2024

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.

mbadolato avatar mbadolato commented on July 4, 2024

@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.

rjkip avatar rjkip commented on July 4, 2024

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.

richardmiller-zz avatar richardmiller-zz commented on July 4, 2024

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.

asm89 avatar asm89 commented on July 4, 2024

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.

wjzijderveld avatar wjzijderveld commented on July 4, 2024

@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.

asm89 avatar asm89 commented on July 4, 2024

@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.

rjkip avatar rjkip commented on July 4, 2024

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:

  1. 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.
  2. 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.
  3. 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.

rjkip avatar rjkip commented on July 4, 2024

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.

wjzijderveld avatar wjzijderveld commented on July 4, 2024

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.

wjzijderveld avatar wjzijderveld commented on July 4, 2024

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.

sstok avatar sstok commented on July 4, 2024

Don't forget https://github.com/szjani/predaddy

from broadway.

wjzijderveld avatar wjzijderveld commented on July 4, 2024

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.

rjkip avatar rjkip commented on July 4, 2024

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.

wjzijderveld avatar wjzijderveld commented on July 4, 2024

@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.

rjkip avatar rjkip commented on July 4, 2024

Go @wjzijderveld 👍

from broadway.

wjzijderveld avatar wjzijderveld commented on July 4, 2024

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.

codeliner avatar codeliner commented on July 4, 2024

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.

wjzijderveld avatar wjzijderveld commented on July 4, 2024

@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.

codeliner avatar codeliner commented on July 4, 2024

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.

stale avatar stale commented on July 4, 2024

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)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo 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.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.