mookid8000 / Cirqus

:x: d60 event sourcing + CQRS framework

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

Inheritance of AggregateRoots and Emit issue

h3ko opened this issue · comments

Hello,

First of all, Cirqus is great and thank you for dev it! We use it a lot and recently came across an issue with inheritance. Is there any reason why aggregates should not emit events from aggregates extending them?

Example:

Base aggregate & event:
public class Device : AggregateRoot {}
public class DeviceChanged : DomainEvent

Two classes extending the base aggregate:
public class SpecialDeviceA : Device {}
public class SpecialDeviceB : Device {}

Although SpecialDeviceA is Device, if I create a SpecialDeviceA and call some method implemented by Device (base) emitting DeviceChanged event I get:
System.InvalidOperationException: Attempted to emit event “DeviceChanged” which is owned by “Device” from aggregate root of type “SpecialDeviceA”.

For now I fixed it with checking if Agg type is assignable from X (and only the exact type of X) in the d60.Cirqus.Aggregates.AggregateRoot.cs, line 81:

From: if (typeof(TAggregateRoot) != GetType())
To: if(!typeof(TAggregateRoot).IsAssignableFrom(GetType()))

Aggregate tests still pass :)

Is this ok? As we (and probably others too) use inheritance with Aggregates (nature of our domain), maybe Cirqus could enable this by default?

Kind regards,
Martin

Oh, that is definitely ok 👍 if you submit a PR, I'd be happy to merge it :)

Should the Owner meta data of the event be set to the emitting type instead of the owning type then? I believe it's used when instantiating roots and that needs to be the concrete type.

Yes, it should be the concrete type of the aggregate root, no matter if it's inherited off of some kind of base class.

I'm thinking of this line in RealUnitOfWork.AddEmittedEvent():

e.Meta[DomainEvent.MetadataKeys.Owner] = _typeNameMapper.GetName(typeof (TAggregateRoot));

It takes the type of the owner defined by the event - in this case the base type Device. That means that the DefaultAggregateRootRepository.Get() will try to create an instance of the base type here:

var aggregateRootTypeName = e.Meta[DomainEvent.MetadataKeys.Owner];
var aggregateRootType = _domainTypeNameMapper.GetType(aggregateRootTypeName);
aggregateRoot = CreateNewAggregateRootInstance(aggregateRootType, aggregateRootId, unitOfWork);

So I AddEmittedEvent ought to by changed, I believe, to make it work.

As an aside, I guess Emitter would be a better name for the metadata key. But I guess that'll break some running systems :)

I implemented this with suggested emitter metadata and it seems to work.

In AggregateRoot

            e.Meta[DomainEvent.MetadataKeys.Emitter] = GetType().AssemblyQualifiedName;

In RealUnitOfWork

            var emitterType = Type.GetType(e.Meta[DomainEvent.MetadataKeys.Emitter]);
            e.Meta[DomainEvent.MetadataKeys.Owner] = _typeNameMapper.GetName(emitterType);

Now the problem is that I cannot get type of concrete aggregate root in CirqusTestsHarness.

     void Emit<T>(string id, DomainEvent<T> @event) where T : AggregateRoot
        {
            @event.Meta[DomainEvent.MetadataKeys.AggregateRootId] = id;
            ..
        }

Any idea?

Eventually, I came up with some kind of solution. Please check out latest commits.

I see the problem in the test harness, I think your solution is the right one. But I have a few comments on the UnitOfWork... I'll leave them in the commits :)

I said a few, but in reality just one.

It's a tough one with the TestContext and the harness. I believe that emitted events should not have an owning type at all, but that might be a discussion for another day :)

Take 2, this time without Emitter :) Still some room for improvments in test harness.