Whenever you start a new project you promise yourself you’re going to do it right this time. But what does that mean, “right”? Too often people swear by the same architecture for every kind of application when in reality some architectures are better suited for specific use cases.
A very simple and often found architecture is the layered one, in which you have the client-facing side (Controllers), the business logic part (Services) and the database part (Repositories) in which calls flow from left to right: Controllers -> Services -> Repositories. For small applications that primarily deal with CRUD this is perfectly acceptable.
I want to present a small variation of this architecture which takes some aspects of Command Query Responsibility Segregation (CSRF) and implements it in a layered architecture. It’s not an architectural pattern you’d apply to an enormous project but I believe it does enforce more code quality control for small to medium sized applications simply by way of its design.
All code for this example can be found on this Github repo.
What are we building?
Let’s say we have an application that manages Products for an e-commerce store. It’s a simple example that doesn’t necessarily need this type of architecture but it will do nicely to showcase it. In order for the application to work properly we need to be able to fetch, create and update products.
How does it work?
At the heart of it we have two types of requests we need to handle, either a Command
(which changes the application’s state) or a Query
(which reads the application’s state). In order to handle either of these requests I have come up with a marker interface called a Dispatchable
:
/** * Marker interface for {@link Query} or {@link Command} */ public interface Dispatchable<TResult> { }
Both a Query
and Command
can be dispatched from within the web layer of the application (i.e. a Controller):
/** * Used for requests that change the application state */ public interface Command<TResult> extends Dispatchable<TResult> { }
/** * Used for requests that read the application state */ public interface Query<TResult> extends Dispatchable<TResult> { }
The <TResult>
generic parameter is used later to signify what the return type for the Command or Query will be.
We can start by building the most basic request, fetching all Products.
Fetching all products using the old way
We add a simple Product
entity:
@Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter @ToString(of = {"id", "name", "unitPrice", "description"}) public class Product { @Id @GeneratedValue private Long id; private String name; private BigDecimal unitPrice; private String description; public Product(String name, BigDecimal unitPrice, String description) { this.name = name; this.unitPrice = unitPrice; this.description = description; } }
With some Lombok annotations to make things easier. Next we create a repository for this entity:
public interface ProductRepository extends JpaRepository<Product, Long> { }
Not much to it, as we’re using Spring Data JPA. In order to pre-populate the database with some Products we can do this using the ApplicationStartedEvent
:
@Component public class DataInit { private final ProductRepository productRepository; public DataInit(ProductRepository productRepository) { this.productRepository = productRepository; } @EventListener(ApplicationStartedEvent.class) public void onApplicationStartup() { Stream.of( new Product("Product 1", new BigDecimal("9.99"), "Product 1 description"), new Product("Product 2", new BigDecimal("4.99"), "Product 2 description"), new Product("Product 3", new BigDecimal("5.99"), "Product 3 description"), new Product("Product 4", new BigDecimal("6.99"), "Product 4 description"), new Product("Product 5", new BigDecimal("7.99"), "Product 5 description") ).forEach(this.productRepository::save); } }
This will ensure we have 5 products in the H2 database. Next we build a RestController that fetches these products (for now we do it the old way, without a service layer in between):
@RestController @RequestMapping("/product") public class ProductController { private final ProductRepository productRepository; public ProductController(ProductRepository productRepository) { this.productRepository = productRepository; } @GetMapping public ResponseEntity<?> getAll() { return ResponseEntity.ok(this.productRepository.findAll()); } }
If you perform an HTTP request to /product you’ll get the five products back. This is what is often done (with or without a service layer in between). Now let’s see how we can use the Query
and Command
we created earlier.
Upgrading to a command/query architecture
What we want to do now is, when the request comes in we need to create either a Command
or a Query
. To fetch all products we want to place the responsibility to handle this request into a separate class, instead of handling all Product*
requests either in the Controller layer or a Service layer.
To do that we create a GetAllProductsQuery
, the name describes very well what it will do:
public class GetAllProductsQuery implements Query<List<Product>> { }
At the moment it has no fields or methods, it’s simply a class that represents the action we want to undertake and implements the Query interface and defines the return type. In our controller we can now create an instance of this:
@GetMapping public ResponseEntity<?> getAll() { GetAllProductsQuery getAllProductsQuery = new GetAllProductsQuery(); // who handles this getAllProductsQuery? }
But the question is, who handles that query? We need a class that we can pass the getAllProductsQuery
to. In order to do that we need to define a BaseHandler
which will be implemented by a specific handler for Commands, and one for Queries:
/** * Base handler for all types of {@link Dispatchable} * @param <TResult> the return type of the dispatchable */ public interface BaseHandler<TDispatchable extends Dispatchable<TResult>, TResult> { TResult handle(final TDispatchable dispatchable); }
As you can see the interface accepts two generic arguments, the first is any class/interface that extends Dispatchable
, which will be either Command
or Query
. The handle
method will return whatever class is specified as TResult
and will accept the Dispatchable
. Now we need a more specific handler for both our Command and Query:
public interface CommandHandler<TCommand extends Dispatchable<TResult>, TResult> extends BaseHandler<TCommand, TResult> { }
public interface QueryHandler<TQuery extends Dispatchable<TResult>, TResult> extends BaseHandler<TQuery, TResult> { }
As you can see they also accept two generic arguments which are defined as TCommand
and TQuery
respectively and then passed on to the BaseHandler
.
Now we need a concrete handler that will perform the logic needed for the GetAllProductsQuery
and we will define it as a non-public class inside the query’s class, this ensures that the user can only get to the handler by creating the GetAllProductsQuery and dispatching it (we will see later how to dispatch it). So our GetAllProductsQuery class then becomes:
public class GetAllProductsQuery implements Query<List<Product>> { } @Service @Transactional class GetAllProductsQueryHandler implements QueryHandler<GetAllProductsQuery, List<Product>> { private final ProductRepository productRepository; GetAllProductsQueryHandler(ProductRepository productRepository) { this.productRepository = productRepository; } @Override public List<Product> handle(GetAllProductsQuery query) { return this.productRepository.findAll(); } }
As you can see the GetAllProductsQueryHandler
is marked as @Service
and @Transactional
because this is where our transaction boundary begins and ends, it represents the request of the user. It also implements the QueryHandler
with the query type and the return type as generic parameters.
How do we now get the ProductController
to create a GetAllProductsQuery
and have the GetAllProductsQueryHandler
to handle it? Instead of injecting all the *QueryHandlers and *CommandHandlers in the Controllers we will create a single service that can dispatch all Command and Queries to their correct handlers. In order to do that, we want each Command and Query to define by which handler they should be, well, handled. We create the following annotation:
/** * Annotation to signify which handler should handle the {@link Dispatchable} */ @Target({ElementType.TYPE}) @Retention(RetentionPolicy.RUNTIME) public @interface HandledBy { Class<? extends BaseHandler<? extends Dispatchable<?>, ?>> handler(); }
This annotation will be placed on a Command or Query and define the class that handles it. In our case we can add it to our GetAllProductsQuery
like so:
@HandledBy(handler = GetAllProductsQueryHandler.class) public class GetAllProductsQuery implements Query<List<Product>> { }
Now we need a service that we can inject in our Controllers that will accept a Dispatchable
and then run it through the defined handler. For that we create a DispatchableHandler
:
@Component public class DispatchableHandler { private final ApplicationContext applicationContext; public DispatchableHandler(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } public <TResult> TResult dispatch(final Dispatchable<TResult> dispatchable) { HandledBy handledByAnnotation = dispatchable.getClass().getAnnotation(HandledBy.class); if (handledByAnnotation == null) { throw new IllegalStateException(String.format("No @HandledBy annotation provided for dispatchable %s", dispatchable.getClass().getSimpleName())); } Class<? extends BaseHandler> handlerType = handledByAnnotation.handler(); Map<String, ? extends BaseHandler> handlers = applicationContext.getBeansOfType(handlerType); if (handlers.isEmpty()) { throw new IllegalStateException(String.format("Dispatchable %s has no handler", dispatchable.getClass().getSimpleName())); } if (handlers.size() > 1) { throw new IllegalStateException(String.format("Dispatchable %s has more than one handler", dispatchable.getClass().getSimpleName())); } BaseHandler<Dispatchable<TResult>, TResult> handler = handlers.values().iterator().next(); return handler.handle(dispatchable); } }
This class retrieves the value in the @HandledBy
annotation of the Dispatchable
and checks that it’s not NULL and that is has exactly 1 handler type defined. Once the handler is found, we invoke the .handle()
method with the dispatchble and return the result. The DispatchableHandler
class itself is marked as a @Component
so can be injected in our Controllers.
We can thus change the code in our ProductController
to this:
@RestController @RequestMapping("/product") public class ProductController { private final DispatchableHandler dispatchableHandler; public ProductController(DispatchableHandler dispatchableHandler) { this.dispatchableHandler = dispatchableHandler; } @GetMapping public ResponseEntity<?> getAll() { GetAllProductsQuery getAllProductsQuery = new GetAllProductsQuery(); List<Product> getAllProductsResponse = this.dispatchableHandler.dispatch(getAllProductsQuery); return ResponseEntity.ok(getAllProductsResponse); } }
Adding a command
Let’s see how easy it is to create a new command. We start by defining the command:
@Value @HandledBy(handler = AddProductCommandHandler.class) public class AddProductCommand implements Command<Long> { String name; BigDecimal unitPrice; String description; }
This is what the command would look like, notice that it implements Command<Long>
because it will return the id of the created Product. In this case the command also has three fields which are needed to complete the command. Our AddProductCommandHandler
would look like this:
@Service @Transactional class AddProductCommandHandler implements CommandHandler<AddProductCommand, Long> { private final ProductRepository productRepository; AddProductCommandHandler(ProductRepository productRepository) { this.productRepository = productRepository; } @Override public Long handle(AddProductCommand command) { if (command.getUnitPrice().compareTo(BigDecimal.ZERO) < 0) { throw new IllegalArgumentException("Price of product cannot be less than 0"); } Optional<Product> existingProductWithName = this.productRepository.findByNameIgnoreCase(command.getName()); if (existingProductWithName.isPresent()) { throw new IllegalArgumentException(String.format("%s is already defined as product", command.getName())); } Product productToSave = new Product(command.getName(), command.getUnitPrice(), command.getDescription()); Product savedProduct = this.productRepository.save(productToSave); return savedProduct.getId(); } }
As you can see I’ve added some business logic code that checks that the price is not less than 0 and that the name is unique. Once the Product is saved, the newly created id is returned. I could simply return the Product entity but in reality you should not work with entities in your controllers (I know I have done so in the GetAllProductsQuery
example). Returning the id and then fetching a DTO or Projection in the Controller is a better practice.
In order to execute this command we need to define and end point in our controller:
@PutMapping public ResponseEntity<?> add(@RequestBody final AddProductCommand command) { Long productId = this.dispatchableHandler.dispatch(command); URI createdUri = ServletUriComponentsBuilder .fromCurrentRequest() .path("/{id}") .buildAndExpand(productId) .toUri(); return ResponseEntity.created(createdUri).build(); }
I have simply returned an empty HTTP 200 with a link to the newly created entity in the Location header. This would then go to the end point that executes the GetProductById
command (not shown here, but avaialble in the source code).
Which advantages do I get from this?
An obvious advantage is that 1 use case is now represented by 1 class. The single responsibility principle (SRP) likes this. You no longer have a service layer that can grow to god-like proportions filled with hundreds or thousands of methods whereby a change in one might have side effects in another.
Furthermore you can quite easily extend this a bit further. Let’s say you want to time how long a Command or Query takes to execute. With this architecture that becomes quite simple. In order to achieve that we want to extract an interface from the DispatchableHandler
with the following method:
public interface DispatchableHandler { <TResult> TResult dispatch(Dispatchable<TResult> dispatchable); }
I’ve renamed our concrete class DispatchableHandler
to DispatchableProcessor
(not the greatest name, but I couldn’t find a better one) which implements the DispatchableHandler
and contains the code from before. One important aspect is that I’ve removed the @Component
annotation because we’ll instantiate it another way.
Now we also need a class that will time how long the Dispatchable
runs, this is the DispatchableTimer
:
@Slf4j public class DispatchableTimer implements DispatchableHandler { private final DispatchableHandler next; public DispatchableTimer(DispatchableHandler next) { this.next = next; } @Override public <TResult> TResult dispatch(Dispatchable<TResult> dispatchable) { long start = System.currentTimeMillis(); TResult result = this.next.dispatch(dispatchable); long stop = System.currentTimeMillis(); long durationInMs = stop - start; log.info("Executing {} took {}ms", dispatchable.getClass().getSimpleName(), durationInMs); return result; } }
Now what’s left is to define a Bean
of type DispatchableHandler
that is injected in our Controllers:
@SpringBootApplication public class SpringBootCqArchitectureApplication { private final ApplicationContext applicationContext; public SpringBootCqArchitectureApplication(ApplicationContext applicationContext) { this.applicationContext = applicationContext; } public static void main(String[] args) { SpringApplication.run(SpringBootCqArchitectureApplication.class, args); } @Bean public DispatchableHandler dispatchableHandler() { return new DispatchableTimer(new DispatchableProcessor(this.applicationContext)); } }
There we have it, we simply wrap one DispatchableHandler
in another ensuring that the final DispatchableHandler
in the chain is always the one that actually executes the Command or Query. If you now perform a Query or Command you’ll see how long it takes.
You could take this further and add as many extra wrappers as you need. One wrapper I use myself is that I have separate objects for all incoming request bodies and the *Command or *Query they represent, I apply no bean validation to this but I do apply bean validation to all the *Query and *Command objects. Instead of handling that for each end point I simply write a DispatchableValidator
and perform the validation there. If interested let me know and I’ll add it to the source code on Github.