Search code examples
c#design-patternsazure-functionscqrs

CQRS - Avoid switch case to call command handler


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;  }
    }

Solution

  • 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);
    }