Scanning assemblies for handlers

Hey ServiceStack,

I was faced with a problem, “What if I want to scan an assembly an register all of the available handlers that are in it?”. I am not sure where this goes or even if it goes here but you can tell me and we can move it accordingly.

For a little background, I came from a recent large application that used EasyNetQ as the library used for interfacing with RabbitMQ. They had a nifty feature that let you scan an assembly and it would register your handlers for you. I just started a new project and decided to use Redis for my MQ solution this time around. When I was going through the API, I noticed there was no functionality to do the same for Redis queues. I created a solution for scanning an assembly, finding all of the implementations of `IRegisterAsHandler and registering the type and accompanying handler. The questions here are:

  1. Did I over-simplify or over-complicate with my implementation
  2. Is there anything you would change?

Here is my solution and I will not go into great detail here because it is pretty straight-forward.

  1. IRegisterAsHandler

    public interface IRegisterAsHandler
    {
    void Register(RedisMqServer mqServer);
    }

  2. Handles<TMessageType>

    public abstract class Handles : IRegisterAsHandler
    {
    private int _totalMessagesProcessed;
    private int _totalMessagesFailed;
    private int _totalRetries;
    private int _totalNormalMessagesReceived;
    private int _totalPriorityMessagesReceived;
    private DateTime? _lastMessageProcessed;

     public Type MessageType => typeof(TMessageType);
    
     public Func<IMessage<TMessageType>, object> RegisterFunc()
     {
         return message =>
         {
             try
             {
                 UpdateStats(message);
                 Handle(message);
             }
             catch (Exception)
             {
                 _totalMessagesFailed++;
             }
    
             return null;
         };
     }
    
     public abstract void Handle(IMessage<TMessageType> message);
    
     public virtual IMessageHandlerStats GetStats()
     {
         return new MessageHandlerStats(GetType().Name, _totalMessagesProcessed, _totalMessagesFailed, _totalRetries, _totalNormalMessagesReceived, _totalPriorityMessagesReceived, _lastMessageProcessed);
     }
    
    private void UpdateStats(IMessage<TMessageType> message)
     {
         _totalMessagesProcessed++;
    
         if (message.RetryAttempts > 0)
             _totalRetries++;
    
         if (message.Priority == 0)
             _totalNormalMessagesReceived++;
         else if (message.Priority > 0)
             _totalPriorityMessagesReceived++;
    
         _lastMessageProcessed = DateTime.UtcNow;
     }
    
     public void Register(RedisMqServer mqServer)
     {
         mqServer.RegisterHandler(RegisterFunc());
     }
    

    }

  3. HelloHandler - Example of the end product

    public class HelloHandler : Handles
    {
    public override void Handle(IMessage message)
    {
    Log.Info(message.Dump());
    }
    }

  4. Tie is all together in the MQ Server Initialization

    mqServer.RegisterHandlers(typeof(Handles<>).Assembly);

That’s it. The other question is, if this is a valuable piece of code to the community, how do I know? Where do we, as ServiceStack developers, go to see who needs what? I appreciate your time and hopefully you can give me some good constructive criticism on how we can make this better (i.e: maybe add some interfaces to make it available for other MQ Server types, etc.)

Thanks,
Ryan

I’ve purposely avoided having a Register all Services in Assembly for MQ Handlers since every registered message handler creates 2 background threads (1 in.q + 1 priority.q - although you can disable priority.q’s) so we make it explicit to declare which Request DTO’s need MQ handlers.

This isn’t something we’d include in the framework as this is creating a new Message Handler type/concept when a large part of the appeal of MQ Services in ServiceStack is that they can re-use existing Services. A lot of this code is also about collecting stats, which is also something built-in to the MQ Servers, available from GetStats().

The User Voice is the best place to see what the most requested features are, so is a good indicator of how useful the feature would be for others. It’s great if this works for you, but not sure if anyone else is going to find it useful for their use-cases. Although happy for anyone else to weigh in on this.

I see, I will take a look at pulling the stats stuff out and using what is build in. Can you point me in a direction for the GetStats() that you are referring to?

Also, you are right about the reuse of services. This was me being stuck in the box and forgetting to think outside of it. I may want to rethink the solution as a whole.

Thanks for your feedback.

Just referring to the existing IMessageService.GetStats() implemented on each MQ Server which goes through and returns aggregated stats from each message handler.