Skip to content

An implementation of the Mediator and CQRS patterns

License

Notifications You must be signed in to change notification settings

compilit/spgetti

Repository files navigation

Spgetti - everybody loves spaghetti

While spaghetti in real life might be a good thing, in code, it can be an absolute nightmare. Contrary to what the name of this library might suggest, this 'mediator' pattern implementation is meant to make your code more readable, manageable and easier to test. Why call it 'Spgetti' then? Because it's easier to remember than something like 'Unspgetti' :D

The framework consists of several libraries, all split-up into API's and implementations, so you'll never have to rely on any implementation stuff for your code to compile.

The purpose of this library is to take away all the boilerplate code that is connected to implementing this pattern. This library tries to achieve several goals:

  1. Make your application more loosely coupled
  2. Separate concerns
  3. Make your code easier to test
  4. (Optionally) Help enforce CQRS. Which could be an asset in larger projects

Installation

First make sure you are in fact using Spring as your IOC supplier. This project depends on a provided spring-context dependency Get this dependency with the latest version.

<dependency>
  <artifactId>spgetti-spring-boot-starter</artifactId>
  <groupId>com.compilit</groupId>
</dependency>

Or, if you wish to use the CQRS pattern:

<dependency>
  <artifactId>spgetti-cqrs-spring-boot-starter</artifactId>
  <groupId>com.compilit</groupId>
</dependency>

Then add the @EnableMediator or @EnableCqrsMediator annotation to your project:

import com.compilit.spgetti.EnableCqrsMediator;

@EnableMediator ||

@EnableCqrsMediator
@SpringBootApplication
public class Launcher {

  public static void main(String[] args) {
    SpringApplication.run(Launcher.class, args);
  }

}

Now all you now need to do is register RequestHandler, EventHandler implementations (for plain Spgetti), or QueryHandler, CommandHandler implementations if you use CQRS.

Usage

The Mediator pattern is about making your application loosely coupled. CQRS is meant to make the application robust and predictable by separating reading operations from writing operations. Combining them is quite a popular approach. The idea is that a Mediator is in between all requests (requests can be read or write requests), so there is no direct interaction with resources. This means that there are only 2 specific dependencies which connect your api layer to the domain layer: the CommandDispatcher, the QueryDispatcher. Why 2 instead of just 1 'Mediator' class? Because that would introduce the 'Service Locator antipattern' and defeat the purpose of this library. By having a separate interface for Commands and Queries, CQRS is enforced. It should be noted that using direct implementations of RequestHandler does not mix well with the CQRS library, since a RequestHandler is both for querying data and for mutating it.

In the com.compilit.spgetti.api package of spgetti-cqrs-api, you'll find all interfaces which you can use to write your own Commands and Queries, and their respective handlers.

All components which a user of the API can to interact with:

Request-related (when you don't use CQRS)

  • Request: a generic operation which is handled by a single handler. It provides a return value option to return an Id of a created entity for example. Or you could return a Result. This return value should never be filled by a reading operation.
  • RequestHandler: the handler for a specific Request.
  • RequestDispatcher: the main interactor for dispatching Requests.

Event-related

  • Event: something that has happened which other operations (EventHandlers) can subscribe to. Can be handled by multiple EventHandlers.
  • EventHandler: the handler for a specific Event.
  • EventEmitter: the main interactor for emitting Events.

Command-related (when you do use CQRS)

  • Command: a writing operation which is handled by a single handler. It provides a return value option to return an Id of a created entity for example. Or you could return a Result. This return value should never be filled by a reading operation.
  • CommandHandler: the handler for a specific Command.
  • CommandDispatcher: the main interactor for dispatching Commands.

Query-related (when you do use CQRS)

  • Query: a reading operation which is handled by a single handler.
  • QueryHandler: the handler for a specific Query.
  • QueryDispatcher: the main interactor for dispatching Queries.

Here is an example:

import QueryDispatcher;

public class TestQuery implements Query<String> {

  private final String someData;

  public TestQuery(String someData) {
    this.someData = someData;
  }

  public String getData() {
    return someData;
  }
}


public class TestQueryHandler implements QueryHandler<TestQuery, String> {

  //This class could interact with other systems/clients/hibernate etc.
  @Override
  public String handle(TestQuery query) {
    return query.getData();
  }

}

@RestController
public class ExampleController {

  private final QueryDispatcher queryDispatcher;

  public ExampleController(QueryDispatcher queryDispatcher) {
    this.queryDispatcher = queryDispatcher;
  }

  @GetMapping("/some-example")
  public ResponseEntity<String> interact() {
    return queryDispatcher.dispatch(new TestQuery());
  }

}

Without CQRS

The basic API you need is the spgetti-api, which is implemented in the spgetti-core and used in the spgetti-spring-boot-starter. So the most basic way of using Spgetti is by grabbing that dependency and bootstrapping it by annotating one of your configuration classes or beans with @EnableMediator.

You'll have access to a simple API consisting of a RequestDispatcher, and an EventEmitter. These are the only dependencies you'll ever need to inject in any of your classes/services that wish to interact with others. The internal Mediator will handle all of this interaction. The interaction takes place through the respective Requests and Events which are internally connected to their RequestHandler and EventHandler counterparts.

With CQRS

By applying CQRS, you mildly force the programmer to split up their data-retrieving and data-altering implementations. This can be a powerful tool for applications that need to be scalable.

If you wish to collect data from somewhere, write an implementation of a Query. This query can then be dispatched using the registered QueryDispatcher.

If you wish to mutate/add some data somewhere, write an implementation of a Command<InputData, ReturnType>. This command can be dispatched using the registered CommandHandler.

And if you wish to notify your application that some event has occurred, write an implementation of Event and emit it with the registered EventEmitter.

For each implemented Command, Query and Event, you need to write your own implementation of CommandHandler, QueryHandler and EventHandler. These must be registered beans managed by the framework.

To make it a bit more clear what an implementation might look like:

@Service
class CreateUserCommandHandler implements CommandHandler<CreateUserCommand, UserResult> {

  @Override
  public UserResult handle(CreateUserCommand createUserCommand) {
    (...)
  }

}

Other that registering your own handler implementations and dispatching your own request implementations you don't need to do anything. The framework will take care of it all.

Features

Automatic Events

All Handlers will automatically emit the events described in the overridden onAccepted and onFinished methods of each handler. If you don't override these default methods, no events shall be emitted on these life cycle hooks. Due to type erasure, the framework will not see any difference between TestEvent<One> and TestEvent<Two>, keep that in mind.

For those paying attention

If you've looked at my implementation you might have noticed that there is no difference in behaviour between the CommandHandlers and the QueryHandlers. Both have the ability to return values. It is true that returning a value from a Command enables the user of this library to still break with CQRS and perform reading operations inside CommandHandlers. This, however, is always possible. I considered this and decided that it was more important to provide an API that is consistent with other frameworks and libraries. Most writing operations return either the written object, the ID of the written object or some other kind of result. The whole point of having a separate Query and Command class is for reading purposes only. This way it should be clear to the reader that some operation is only about reading or about writing.

For any operation, whether it is retrieving, changing or storing data, you'll write a Request implementation, where R represents the return type. This Request encapsulates the data required for the operations. The operation is an implementation of RequestHandler<T, R> where T is the exact implementation of the request, and R the return type.

Once this RequestHandler is turned into a registered bean and you use the RequestDispatcher to dispatch your Request, the framework will take over an provide you with the correct return type after initiating the logic.

About

An implementation of the Mediator and CQRS patterns

Resources

License

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published

Languages