Per Brage's Blog

const String ABOUT = "Somehow related to code";

Event Broker using Rx and SignalR (Part 3: Event Consumers)

If you go back and look at the result from the first post of the series, you will notice that during registration of subscriptions, we add filter predicates and assign actions to be executed as events are consumed. This isn’t really such a good idea, since we will end up having all our logic in our configuration and it will clutter the code among other bad things! So what can we do about it?

Event Consumers

The solution to our problem is to outsource both filtering and processing into event consumers. For those of you familiar with CQRS, just think of a command handler that implements IHandle<T>, but for an event. With event consumers we end up with classes that handle each particular event. We will add simplicity, separation and if named correctly, end up with a more declarative code.

Let’s start with an interface describing an event consumer, and also an abstract class implementation that will provide a register method, which will help us with registering a Func<TEvent, Boolean> (e.g. a filter). The func is added to our multicast delegate property, which will allow us to add several filters on an event consumer.

    public interface IEventConsumer<in TEvent> : IHandle<TEvent>
    {
        Func<TEvent, Boolean> Filters { get; }
    }
    public abstract class EventConsumer<TEvent> : IEventConsumer<TEvent>
        where TEvent : IEvent
    {
        public Func<TEvent, Boolean> Filters { get; private set; }
 
        protected void Register(Func<TEvent, Boolean> filter)
        {
            if (Filters == null)
                Filters = filter;
            else
                Filters += filter;
        }
 
        public abstract void Handle(TEvent message);
    }

Let’s implement the ProductOrderedEventConsumer that will be used at the component stock in our scenario. When the component stock receives a ProductOrderedEvent, we will have to make sure it won’t act upon events for laptops and computers, as the computer stock will handle those two types of products. To accomplish this we just register a filter in the constructor to exclude all events for products in the laptop or computer product group. The handle method will now only process events matching our registered filter.

Speaking of the Handle method, it won’t do much more than publish a ProductShippedEvent, and at random, the ProductOrderPointReachedEvent which will simulate that our inventory is getting low on a particular product.

    public class ProductOrderedEventConsumer : EventConsumer<ProductOrderedEvent>
    {
        private readonly IEventBroker _eventBroker;
        private readonly Random _random;

        public ProductOrderedEventConsumer(IEventBroker eventBroker)
        {
            _eventBroker = eventBroker;
            _random = new Random();

            Register(x => x.ProductGroup != "Laptop" && x.ProductGroup != "Computer");
        }

        public override void Handle(ProductOrderedEvent @event)
        {
            _eventBroker.Publish(new ProductShippedEvent
            {
                ProductName = @event.ProductName
            });
            
            if (_random.Next(10) > 5)
                _eventBroker.Publish(new ProductOrderPointReachedEvent()
                {
                    ProductName = @event.ProductName
                });
        }
    }

Using Specification pattern to apply filtering

Nitpicker corner: Yes, specifications can be a bit cumbersome and yes, it does add a lot of code that could at times be written with a few simple lambdas. But remember the ubiquitous language? Specifications allows us to communicate about rules and predicates with everyone involved in developing our software! To be able to talk about the ‘Items in laptop or computer product group’ instead of the ‘x rocket x dot product group equals laptops or x dot product group equals computers’ – predicate, provides a lot of value not to be neglected! At the same time we need to be pragmatic about it and not implement specifications for every little equality check we create, as that would definitely overwhelm our code base. For this particular use-case we might be overdoing it, but I wanted to show a simple example of using Specifications

The specification pattern in its essence matches an element against a predicate, and respond with a boolean if the input data is satisfied by the predicate. This interface describes it rather well.

    public interface ISpecification<TElement>
    {
        Boolean IsSatisfiedBy(TElement element);
    }

There is also a simple abstract class (available in the full source) that I use to avoid repeating myself. There are far more advanced implementations of the specification pattern available online if you are interested, and I would suggest using one of them if you want to start using specifications in your code. Below is the implementation of the ItemsInLaptopOrComputerProductGroup specification I mentioned earlier, which we will use in our scenario to apply filtering of incoming events in the computer stock.

    public class ItemsInLaptopOrComputerProductGroupSpecification:Specification<ProductOrderedEvent>
    {
        public ItemsInLaptopOrComputerProductGroupSpecification()
        {
            AssignPredicate(x => x.ProductGroup == "Laptop" || x.ProductGroup == "Computer");
        }
    }

Now we have a specification that declares intent with a name instead of a lambda. All we need now is a way of registering this specification into our event consumer, and that can be done with the addition of this method.

        protected void Register(ISpecification<TEvent> specification)
        {
            Register(specification.IsSatisfiedBy);
        }

Registering a specification now becomes a single line of code within our event consumers.

        Register(new ItemsInLaptopOrComputerProductGroupSpecification());

Links

Source
Full source at my GitHub repository

Navigation
Part 1: A Fluent API
Part 2: Implementation
Part 3: Event Consumers
Part 4: Solving the Scenario

About these ads

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Follow

Get every new post delivered to your Inbox.

%d bloggers like this: