2

I spent some time learning how "Event Sourcing" works and how one would implement that in C# and i got stuck at some point. As it is very difficult to describe my problem without code I'll first give you a simplified version of my code. I removed all the unneeded code and left the relevant parts.

public interface IEvent { }
public class UserCreated : IEvent { }
public class UserDeleted : IEvent { }

public interface IEventSourcable
{
    ICollection<IEvent> History { get; }
    void ApplyEvent(IEvent e);
}

public abstract class EntityBase : IEventSourcable
{
    public ICollection<IEvent> History { get; }
    public void ApplyEvent(IEvent e)
    {
        History.Add(e);
    }
}

public class User : EntityBase
{
    public void ApplyEvent(UserCreated e)
    {
        base.ApplyEvent(e)
    }
}

What I would like to do is to prevent the base method to be used if a matching method is not implemented i. e.

User u = new User();
u.ApplyEvent(new UserCreated());

should work and call the method in User (which it does) but

u.ApplyEvent(new UserDeleted()); 

should not call the base method but give an error at compile time.

I've seen different approaches which would give a runtime error or simply ignore the problem if a matching method is not implemented like

  1. Simply override the method and check the type

    public class User : EntityBase
    {
        public override void ApplyEvent(IEvent e)
        {
            if (e is UserCreated)
                ApplyEvent((UserCreated)e);
            else if (e is UserDeleted)
                ApplyEvent((UserDeleted)e);
            else
                throw new UnknownEventException(); // Or handle it however
        }
    }
    
  2. Use the dynamic operator

    public abstract class EntityBase : IEventSourcable
    {
        public ICollection<IEvent> History { get; }
        public void ApplyEvent(IEvent e)
        {
            History.Add(e);
            ((dynamic)this).Apply((dynamic)e);
        }
    }
    
    public class User : EntityBase
    {
        public override void Apply(UserCreated e)
        {
            // do something
        }
    }
    

I know I could do it in either one of mentioned ways but I'm more interested in whether what I'm thinking of is possible or not.

Alex B.
  • 21
  • 3

1 Answers1

0

You could implement the interface explicitly, which would prevent unwanted event types on a concrete instance:

public abstract class EntityBase : IEventSourcable
{
    ICollection<IEvent> IEventSourcable.History { get; }

    void IEventSourcable.ApplyEvent(IEvent e)
    {
        // Do the magic
    }

    protected void ApplyEvent(IEvent e)
    {
        (this as IEventSourcable).ApplyEvent(e);
    }
}

public class User : EntityBase
{
    public void ApplyEvent(UserCreated e)
    {
        base.ApplyEvent(e);
    }
}

However that wouldn't prevent a consumer from casting to an IEventSourcable and adding arbitrary events:

IEventSourcable u = new User();
u.ApplyEvent(new UserCreated());
u.ApplyEvent(new UserDeleted());
Evan Trimboli
  • 29,900
  • 6
  • 45
  • 66
  • Even though it's not how I would like to have it it's still a nice approach I didn't think of. But as you said the consumer can now even cast the object to bypass the mechanism which actually makes it worse. – Alex B. May 03 '18 at 07:24
  • It doesn't really make it any worse, the consumer can always case it to the interface if it's implemented by the class. Ultimately, I don't think there's a way to prevent it at compile time. – Evan Trimboli May 03 '18 at 07:37
  • That's true but in approach 1 of my post this wouldn't make a difference, isn't it? – Alex B. May 03 '18 at 07:39
  • Right, I was concentrating on how you might be able to mitigate the error at compile time. – Evan Trimboli May 03 '18 at 07:40
  • I guess what I'd like to achieve is not possible. – Alex B. May 03 '18 at 07:44