CQRS - Avoid switch case to call command handler

Viewed 443

I am using CQRS design pattern. I have more than 15 command handlers corresponding to each event type. I want to avoid below switch case to call corresponding command handler based on event type.

Here is my Azure Function:

[FunctionName("ReceiveEvent")]
        public static async Task<IActionResult> Run(
            [HttpTrigger(AuthorizationLevel.Function, "post", Route = null)] HttpRequest req,
            ILogger log)
        {
            //log.LogInformation("ReceiveEvent HTTP trigger function started processing request.");

            //log.LogInformation($"Pushing Events to Azure Blob on storage account :-{CloudConfigurationManager.GetSetting("AzureWebJobsStorage")}");

            IActionResult actionResult = null;

            string requestBody = await new StreamReader(req.Body).ReadToEndAsync();

            var command = await _commandMapper.Map(requestBody);

            if (_commandValidator.Validate(req, command, ref actionResult))
            {
                switch (command.EventType)
                {
                    case EventType.CARD_BLOCK:
                        _cardBlockCommandHandler.Handle(command as CardBlockCommand);
                        break;
                    case EventType.CARD_CANCEL:
                        _cardCancelledCommandHandler.Handle(command as CardCancelledCommand);
                        break;
                    case EventType.CARD_UNBLOCK:
                        _cardUnBlockHandler.Handle(command as CardUnBlockCommand);
                        break;

                }
                //TODO
                return actionResult;
            }

Is there any better way to avoid this switch case?

Command handler :

public class CardBlockCommandHandler : ICommandHandler<CardBlockCommand>
    {
        private readonly IAzureBlobStorage _azureBlobStorage;

        public CardBlockCommandHandler(IAzureBlobStorage azureBlobStorage)
        {
            _azureBlobStorage = azureBlobStorage;
        }

        public void Handle(CardBlockCommand command)
        {
            try
            {
                //TODO: Store into blob
                //_azureBlobStorage.UploadMessageContentAsync(storageConnectionString: string.Empty,
                //    storageContainerName: string.Empty, blobName: string.Empty, content: string.Empty);

                throw new NotImplementedException();
            }
            catch
            {

            }
        }
    }

ICommandHandler:

public interface ICommandHandler<TCommand> where TCommand : ICommand
    {
        /// <summary>
        /// Execute command
        /// </summary>
        /// <param name="command"></param>
        void Handle(TCommand command);
    }

Command:

public abstract class Command : ICommand
    {
        public EventType EventType { get; }
    }

public interface ICommand
    {
        EventType EventType { get;  }
    }
1 Answers

This is elaborating on what was mentioned in the comments about using a service. Have a ICommandHandlerService which is injected into your function constructor as a dependency (you are using a DI container aren't you ?). This interface would have the following method:

void HandleCommand(ICommand command);

The implementation of ICommandHandlerService would have a Dictionary mapping between event type and CommandHandlers such as

public class CommandHandlerService : ICommandHandlerService 
{
     Dictionary<EventType, ICommandHandler> handlerDictionary = new Dictionary<EventType, ICommandHandler>(); 
//assuming that all your different handlers implement ICommandHandler ?

     public void CommandHandlerService()
     {
        handlerDictionary.Add(EventType.CARD_UNBLOCK, new CardUnBlockCommandHandler());
        handlerDictionary.Add(EventType.CARD_BLOCK, new CardBlockCommandHandler());
        //setup rest of your associations
     }

}

void HandleCommand(ICommand command)
{
    if(!handlerDictionary.ContainsKey(command.EventType))
    {
       //throw suitable exception ?
    } 

    var commandHandler = handlerDictionary[command.EventType];
    commandHandler.Handle(command);
}

Your Azure function constructor would be passed in a ICommandHandlerService instance by your DI framework and you would call it so:

if (_commandValidator.Validate(req, command, ref actionResult))
{
    commandHandlerService.Handle(command);
}
Related