28 September 2011

CQRS and Design by Contract

Getting closer to CQRS and its valuable java implementation Axon Framework, I found that it goes very well with one of my favorites concepts in computer programming: Design by Contract  (something that I really miss in my daily work on java - but that's another story...)

The reason of my appreciation is that I see in Design by Contract the application of a more general principle, as part of the Domain Driven Design, that leads to "Make implicit concepts explicit".

Thinking about the causes of this affinity, the relationship between CQRS and DbC becomes immediately clear: CQRS incorporates and expands the scope of CQS from the design level, in which it was confined at the beginning, to an architectural point of view.
But CQS and DbC were both conceived by the same father, Bertrand Meyer...

I don't want to go through well known concepts that were explained by others better than I could ever do. I just want to highlight  this small positive fact:
Separation of command handling from event handling gives, among the others benefits, a clear distinction between where to perform the contextual validation of the command - that is necessary to ensure the respect of client obligations (by applying preconditions) - and where to enforce the validation of your business rules  - that is necessary to ensure the respect of server obligations (by applying postcondtions and invariants)

A simple demonstration
Suppose that you have a Ranking class which is responsible for the ranking list of a game's participants.

The Ranking class in this example acts as an aggregate root, that, by extending Axon Framework AbstractAnnotatedAggregateRoot class, obtains for free event sourcing persistence and event raising and handling capabilities.

Please note that the same capabilites are offered by Axon to child entities of the aggregate, but in this case the class to extend is the AbstractAnnotatedEntity class.
In such cases you should take a closer look at paragraph named Complex Aggregate structures in the documentation.

Now let's imagine that you want to add a player in a particular position of this ranking.
You would create some RankPlayerCommand to dispatch command values and a specialized Command Handler that handles such a command.

I let these tasks to your own fantasy. Now let's focus on the rankPlayer method on the Ranking class that is invoked by the command handler:

     *  Add a player at the specified rank
     *  Precondition 1: Cannot assign the same rank to more than one player.
     *  Precondition 2: Each player cannot get more than one position.
     * @param rank 
     * @param player 
    public void rankPlayer(int rank, Player player) {
        boolean isFreeRank = !players.containsKey(rank);
        boolean isNew = !players.containsValue(player);
        Contract.REQUIRE.isTrue(isFreeRank, "Cannot assign the same rank to more than one player.", "The rank [{}] is already assigned.", rank);
        Contract.REQUIRE.isTrue(isNew, "Each player cannot get more than one position.", "The player [{}] is already in this ranking.", player);
        apply(new PlayerRankedEvent(rank, player));

As you can see in the example above, the main responsibility of the rankPlayer method is, as said before, to ensure the respect of client obligations specified in the contract.
These obligations are enforced by applying preconditions that, for clarity's sake, are reported in the javadoc. Then the method is free to raise the appropriate event for its case.
The applied event is handled locally by the same entity, in this case our Ranking class, through a method annotated with @EventHandler in the following way:

     * Postcondition 1: Players list must contains the new player.
     * Postcondition 2: Players list size must grows by one.
     * @param ev @see RankingPlayerInsertEvent
    void onPlayerRanked(PlayerRankedEvent ev) {
        int initialSize = players.size();
        players.put(ev.getRank(), ev.getPlayer());
        boolean isContained = players.containsValue(ev.getPlayer()) && players.get(ev.getRank()).equals(ev.getPlayer());
        final boolean isSizeEncreased = players.size() == ++initialSize;
        Contract.ENSURE.isTrue(isContained, "Players list must contains the new player.", "Missing element");
        Contract.ENSURE.isTrue(isSizeEncreased, "Players list size must grows by one.", 
                "Unexpected size! Actual size: [{}] expected: [{}]", players.size(), initialSize);

     * Invariant 1: Ranking players must be lesser or equals than Game participants 
    private void testInvariants() {
        int playersSize = players.size();
        int participantsSize = getGameParticipantsSize();
        boolean isPlayersSizeValid = (playersSize <= participantsSize); 
        Contract.INVARIANT.isTrue(isPlayersSizeValid, "Ranking players must be lesser or equals than Game participants", 
                "Game participants are [{}] lesser than the number of players [{}] in this ranking", participantsSize, playersSize);

In the event handler, finally, you can apply the appropriate state changing logic but, before exiting, you need to ensure that your implementation respects the obligations towards the client which are stated in the contract.

Again, these obligations are exposed explicitly in the javadoc and enforced by the application of postconditions.

Moreover, due to the state changes applied, the event handler must also ensure the respect of class invariants, that, being class scoped, are verified through an external method.

The Test
It's time to stress our business logic to verify if the contract that applies to our RankPlayerCommand behaves as expected. Let's make some tests:

Axon framework includes promising testing features that lets build up easily test scenarios which took the following form: given - when - then.
This type of scenario can be used to map user stories that usually are shaped in this way: - As a <role>, I want <goal/desire> so that <benefit> - and it turn out to be useful to test the behavior of the application and to derive acceptance tests.

First we want to test the application behavior in the flow of the regular use case:

public class RankingCommandHandlerTest {
    private FixtureConfiguration fixture;
    private Player player1 = new Player("P1", "Player1");

    public void testAddPlayer() {
        RankPlayerCommand c2 = new RankPlayerCommand(fixture.getAggregateIdentifier().asString(), "P1", "Player1", 1);
        PlayerRankedEvent ev = new PlayerRankedEvent(1, player1);
        fixture.given(new RankingCreatedEvent("MyGame")).when(c2).expectEvents(ev);

Then we test the behavior in misuse cases that have been covered by the preconditions:


   private Player player2 = new Player("P2", "Player2");


    public void testFailWhenAddSameRank() {
        RankPlayerCommand c2 = new RankPlayerCommand(fixture.getAggregateIdentifier().asString(), "P1", "Player1", 1);
        PlayerRankedEvent ev = new PlayerRankedEvent(1, player2);
        fixture.given(new RankingCreatedEvent("MyGame"), ev).when(c2).expectException(PreConditionException.class);
    public void testFailWhenAddSamePlayer() {
        RankPlayerCommand c2 = new RankPlayerCommand(fixture.getAggregateIdentifier().asString(), "P1", "Player1", 1);
        PlayerRankedEvent ev = new PlayerRankedEvent(2, player1);
        fixture.given(new RankingCreatedEvent("MyGame"), ev).when(c2).expectException(PreConditionException.class);

In both cases we expect an exception to be thrown:

No comments:

Post a Comment