morleyc
morleyc

Reputation: 2451

Couple related types together to control use in code using generics

I am trying to limit the use of types by chaining the aggregate IAggregate, the aggregate event IDomainEvent, and Identity together with generics, I have snipped the below code to give context of the issue of what I have got so far.

I have the following interfaces:

public abstract class Identity<T>
{
    protected abstract string GetIdentity();
}

public interface IAggregate<T>
{
    Identity<T> Identity { get; }
}

public interface IDomainEvent<TIdentity,TIdentity>
    where T : Identity<TIdentity>
{
    TIdentity Id { get; }
}

I implement with the below:

public class TestUserId : Identity<TestUser>
{
    public TestUserId(string name) { Name = name; }       
    readonly public string Name;
    protected override string GetIdentity() => Name.ToLowerInvariant();       
}

public class TestUser : IAggregate<TestUser>
{
    public TestUser(TestUserId id)
    {
        Id = id;
        var ev = new TestUserCreated()
    }
    public TestUserId Id { get; }
    public Identity<TestUser> Identity => Id;        
}

public class TestUserCreated : IDomainEvent<TestUserId, TestUser>
{
    public TestUserCreated() { }
    public TestUserId Id => throw new NotImplementedException();
}

Then in the command handler, for this event to be used (and for me to be able to obtain the TestUserId which should be member of the domainEvent object).

public interface IDomainEventHandler<TEvent>
{
    void Handle(TEvent domainEvent, bool isReplay);
}

That gives me the code:

public class TesterHandler : IDomainEventHandler<TestUser, TestUserCreated>
{
    public void Handle(TestUserCreated domainEvent, bool isReplay)
    {
        // can access the ID (of type TestUserId)
        var testUserId = domainEvent.Id;
    }
}

So the above TesterHandler is fine exactly how i would want - however the compiler is failing on class TestUserCreated : IDomainEvent<TestUserId, TestUser> with The type TestUserId' cannot be used as type parameter 'TIdentity' in the generic type or method 'IDomainEvent<TIdentity, Type>'. There is no implicit reference conversion from 'TestUserId' to 'Identity<TestUser>'.

What I want is to couple (without OO inheritance) the event to the aggregate, so that it is tied to a specific aggregate type (i.e. specific events are tied to a specific entity, and the entity ID is part of the event type as a field), I want to try and make it impossible to code event handlers for unrelated aggregates.

I am trying to implement but the compiler complains of boxing and implicit casting errors (depending on what i try/guess), in short I am unsure how to code the above.

Upvotes: 1

Views: 103

Answers (1)

morleyc
morleyc

Reputation: 2451

Given I was unable to create running code as per comments requested (hence the reason for the post) and general complexity, I decided using generics in this way was a bad idea with rationale below.

I currently have code which calls the handler as follows (and this is working fine) passing in the sourceIdentity external to the domainEvent object:

public interface IDomainEventHandler<TIdentity, TEvent>
    where TIdentity : IIdentity
    where TEvent : IDomainEvent
    {
        void Handle(TIdentity sourceIdentity, TEvent domainEvent, bool isReplay);
    }

I am passing in the aggregate ID external to the IDomainEvent object (and this is desired to keep the events, from an event sourcing perspective, as simple as possible as simple POCO objects without inheritance or involving any framework).

The reason for the question was I just wanted to explore all options with generics (so the domainEvent object could have an interface that would give an ID field) but it started to get complicated quickly, specifically additional template parameters would be required since we are inferring relationships via templates, rather than OO relationships.

Without OO, the relationship would need to be defined somewhere by adding additional types to templates to tie them together interface IDomainEvent<TIdentity,TAggregate,TEvent> and interface IDomainEventHandler<TIdentity, TAggregate, TEvent>, in this case OO inheritance would be preferred and result in way less code.

All this was done to give an interface to obtain the ID, however as if an ID is really needed it can be incorporated in the event as a normal field (without the need for complex OO relationships or templates).

public interface IDomainEvent
{
    DateTime OccurredOn { get; set; }
    Guid MessageId { get; set; }
}

public class TestUserCreated : IDomainEvent
{
    // id can be accessed by whatever needs it by being
    // defined explicity within the domain event POCO
    // without needing any base class or framework.
    public readonly TestUserId Id;

    public readonly string Name;

    public TestUserCreated(TestUserId id, string name)
    {
         Id = id;
         Name = name;
    }        
}

Upvotes: 1

Related Questions