What is this?
There is a very famous (now) book by Mr. Eric Evans that talks about this thing called domain driven design and it is... wordy. But that's the point, Mr. Evans is a master at using the English language to convey a point, that we should let our domain drive the design of our code.
I'm not going to go into super detail as this subject has been talked about at length by much better people than myself here is a link to Martin Fowlers blog where he talks about each different concept: https://martinfowler.com/tags/domain%20driven%20design.html
So what do you want to talk about
I want to talk about domain events.
So domain events are kind of a CQS idea, when you do something in the system and it needs to talk across the boundary of a bounded context you don't want to muddy the waters of either of the contexts, so what you do is create an event object to carry information that is needed from one context to the other, it's a simple idea, but it is kind of ugly in practice. Let me demonstrate, this code I'm about to show is from a plural sight course and is freely clonable form github here: https://github.com/vkhorikov/DddInAction :
So first we have the domain events themselves:
public static class DomainEvents
{
private static List<Type> _handlers;
public static void Init()
{
_handlers = Assembly.GetExecutingAssembly()
.GetTypes()
.Where(x => x.GetInterfaces().Any(y => y.IsGenericType && y.GetGenericTypeDefinition() == typeof(IHandler<>)))
.ToList();
}
public static void Dispatch(IDomainEvent domainEvent)
{
foreach (Type handlerType in _handlers)
{
bool canHandleEvent = handlerType.GetInterfaces()
.Any(x => x.IsGenericType
&& x.GetGenericTypeDefinition() == typeof(IHandler<>)
&& x.GenericTypeArguments[0] == domainEvent.GetType());
if (canHandleEvent)
{
dynamic handler = Activator.CreateInstance(handlerType);
handler.Handle((dynamic)domainEvent);
}
}
}
}
Ah! reflection, all ready I'm not liking this as much as I should but lets carry on, we have agrregate roots which can publish an event:
public abstract class AggregateRoot : Entity
{
private readonly List<IDomainEvent> _domainEvents = new List<IDomainEvent>();
public virtual IReadOnlyList<IDomainEvent> DomainEvents => _domainEvents;
protected virtual void AddDomainEvent(IDomainEvent newEvent)
{
_domainEvents.Add(newEvent);
}
public virtual void ClearEvents()
{
_domainEvents.Clear();
}
}
The event:
public class BalanceChangedEvent : IDomainEvent
{
public decimal Delta { get; private set; }
public BalanceChangedEvent(decimal delta)
{
Delta = delta;
}
}
And then you've got the method to actually send the event:
public virtual void TakeMoney(decimal amount)
{
if (CanTakeMoney(amount) != string.Empty)
throw new InvalidOperationException();
Money output = MoneyInside.Allocate(amount);
MoneyInside -= output;
decimal amountWithCommission = CaluculateAmountWithCommission(amount);
MoneyCharged += amountWithCommission;
AddDomainEvent(new BalanceChangedEvent(amountWithCommission));
}
I don't know about you but I hate reflection.
So I was wondering whether there was a better way to implement this, and I've been thinking about this for a while with little to no success. Here are a few solutions and why I think they're not great solutions:
- Have an observer pattern
- Where would you subscribe to the events?
- would this break the Open-Closed principle?
- Mediator
- Same issues as above
- https://github.com/jbogard/MediatR Ah now you're talking
Ok so MediatR is a project that is in all essence an implementation of the mediator pattern as a library. The only problem I have seen with this library is that it absolutely depends on dependency injection and I don't know whether this is a good thing or a bad thing.
Basically if you create a bounded context you do not want anything leaking out of that library, any silly implementation details such as DI and using other libraries should very much not be the concern of anyone using the library, they should be able to depend on you're library without having to know what you depend on (this is my opinion anyway).
So to achieve this you have to hide away the implementation of not only MediatR but also the DI.
So here we go:
Got a class to add everything the DI including MediatR
internal static class DependencyExtensions
{
public static IServiceCollection AddConfig(this IServiceCollection serviceCollection, IList<Assembly> assemblies)
{
assemblies.Add(Assembly.GetAssembly(typeof(DependencyExtensions)));
serviceCollection.AddMediatR(assemblies.ToArray());
serviceCollection.AddScoped<ITellerMachine, Atm>();
serviceCollection.AddScoped(typeof(ConfigFactory<>));
return serviceCollection;
}
}
Got a class to boot things into life:
public static class Config
{
public static IConfigContext Init(List<Assembly> assemblies)
{
var serviceProvider = new ServiceCollection()
.AddConfig(assemblies)
.BuildServiceProvider();
return new ConfigContext(serviceProvider);
}
}
Now this is where it gets weird, a context class:
internal class ConfigContext : IConfigContext
{
private readonly IServiceProvider _serviceProvider;
public ConfigContext(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;
public T Create<T>()
=> _serviceProvider.GetService<ConfigFactory<T>>().Produce();
}
And a factory to create objects:
internal class ConfigFactory<T>
{
private readonly IServiceProvider _serviceProvider;
public ConfigFactory(IServiceProvider serviceProvider)
=> _serviceProvider = serviceProvider;
public T Produce()
=> _serviceProvider.GetService<T>();
}
All of this so I can do something like:
var context = Config.Init(new List<Assembly>());
var tellerMachine = context.Create<ITellerMachine>();
This hides away all of the implementation of the library, and I'm not sure if this is me going completely insane or if this is actually any good.
The list of assemblies there (if you're wondering) is so that you can pass in any assemblies that are listening to the domain events to register a handler you simply add a class such as this one:
public class ThingHandler : IDomainEventHandler<BalanceChangedEvent>
{
public Task Handle(BalanceChangedEvent notification, CancellationToken cancellationToken)
{
return Task.CompletedTask;
}
}
Again I've wrapped the MediatR request response stuff tightly into my library so consumers don't have to know.
At this point I feel like Dr. Frankenstein I've created a patchwork monster and I'm just not sure whether I want to befriend it or let it loose onto the world.
Top comments (0)