DEV Community

Discussion on: Domain-Driven Design and the Hexagonal Architecture

Collapse
 
peholmst profile image
Petter Holmström

Sorry for replying so late, I didn't notice this comment until now.

You could do this in many different ways. The main idea is that whenever you register a new command handler, you need to somehow also specify which commands the handler is able to handle. You could do this manually, like registerCommandHandler(myHandler, MyCommand.class), use introspection to fetch the command class from the generic parameters or just add a supportsCommand(Object) method to the handler itself.

The findHandlerFor method would then just loop through all the registered command handlers and look for a handler that supports the command in question.

Collapse
 
mikel_hamer_50f46faf752f0 profile image
Mikel Hamer

Hey Petter! No worries! Thanks for replying. My solution ended up looking like this :)

/**
 * Common interface that must be implemented by all command handlers.
 *
 * @param <C> The type of the command to handle
 * @param <R> The type of the result returned from handling the command
 */
public interface CommandHandler<C extends Command<R>, R> {

    R handleCommand(C command);

}
Enter fullscreen mode Exit fullscreen mode
/**
 * Registry of all command handlers in the application. Any implementation of {@link CommandHandler} is 
 * added to the registry at application start.
 */
@Component
public class CommandHandlerRegistry {

    private final Map<Class<? extends Command<?>>, CommandHandler<? extends Command<?>, ?>> handlers = new HashMap<>();

    public CommandHandlerRegistry(ApplicationContext ctx) {
        Map<String, CommandHandler> commandHandlers = ctx.getBeansOfType(CommandHandler.class);
        for (CommandHandler commandHandler : commandHandlers.values()) {
            register(commandHandler);
        }
    }

    /**
     * Get the command handler for a given command class.
     *
     * @param commandClass the class of the command to lookup
     * @return the corresponding command handler for a command class
     */
    public CommandHandler<? extends Command<?>, ?> get(Class<?> commandClass) {
        return Optional.ofNullable(handlers.get(commandClass)).orElseThrow(() ->
                new IllegalArgumentException("No command handler found for " + commandClass.getSimpleName()));
    }

    private void register(CommandHandler<? extends Command<?>, ?> commandHandler) {
        // The generics of the command handler are used to create the key/value entry in the handlers map
        Class<?>[] generics = GenericTypeResolver.resolveTypeArguments(commandHandler.getClass(), CommandHandler.class);
        // The type of command is the first generic, and is used to create the key
        Class<? extends Command<?>> commandType = (Class<? extends Command<?>>) Objects.requireNonNull(generics)[0];
        handlers.put(commandType, commandHandler);
    }

}

Enter fullscreen mode Exit fullscreen mode
@Component
public class CommandGateway {

    private final CommandHandlerRegistry registry;

    public CommandGateway(CommandHandlerRegistry registry) {
        this.registry = registry;
    }

    /**
     * Delegates command handling to the registered handler for a command in {@link CommandHandlerRegistry}.
     *
     * @param command the command to handle
     * @param <C>     command type
     * @param <R>     result type
     * @return the result of the command
     */
    @SuppressWarnings("unchecked")
    public <C extends Command<R>, R> R handleCommand(C command) {
        CommandHandler<C, R> handler = (CommandHandler<C, R>) registry.get(command.getClass());
        return handler.handleCommand(command);
    }

}
Enter fullscreen mode Exit fullscreen mode