Sunday 20 October 2019

Responsibility Segregation



A consistent property of bad code is a lack of segregation between responsibilities. Relatively large classes will implement multiple facets of functionality and therefore be responsible for more than one aspect of a system.

This will lead to code that is difficult to follow, difficult to maintain and difficult to extend. Those large classes will frequently be modified because they are responsible for many things, if some of these changes are sub-optimal then technical debt gradually accumulates and grows. To finish the vicious circle this can compound the original problem leading to more technical debt and the downward spiral continues.

Command Query Responsibility Segregation (CQRS) is a design pattern focused on addressing this situation by defining clear responsibility boundaries and encouraging proponents to ensure these boundaries aren't breached.

Commands and Queries

Within the CQRS pattern functionality is either a command or a query.

A query is a piece of functionality that given a context will interrogate a data source to return the requested data to the caller. Critically a query should be idempotent and not change the state of the underlying data in any way.

A command is more task driven, it is a piece of functionality that given a context will change the state of an underlying data source or system. Because of its inherent side effects it should not be used to return data to the user as this is the role of a query, instead just returning the result of the downstream operation. This requirement around what a command should return can in practice be difficult to achieve, more often than not some data is required to come back from the command but the important aspect is that callers are aware that commands perform operations on data and therefore have side effects.

Although not explicitly part of the pattern an effective CQRS implementation will also not chain queries or command together. The layers of abstraction this builds can make the code difficult to follow and understand, this can lead to unintended consequences when a caller doesn't realise the chain of events that will unravel. Instead queries and commands should be composed by callers making individual and separate calls to each element in turn, potentially passing data between them and building an aggregated response to return upstream.

Database Origins

Although in the last section the pattern is presented in abstract terms, and CQRS can be applied too many different areas, the origins of the approach comes from the application of CRUD when dealing with databases.

Within this world there can be many advantages to treating reads and writes differently. Firstly there can be advantages to using different models depending on whether data is being queried or modified, also the load presented by reads and writes is often not symmetrical so being able to separate the workloads can bring performance and efficiency advantages.

Having strong abstractions over the top of data access also enables more flexibility in the approach to underlying storage with users being protected from the nuances this may involve via the interface presented by commands and queries.

Advantages

First and foremost the advantage of CQRS is the re-use that can be achieved by the promotion of separating concerns. When code does one thing and does it well the opportunity for re-use is increased. Quite often when classes are bigger and do more the functionality they offer will always be almost what you need but not quite. This either leads to a new class being created with a slightly modified interface, leading to duplication, or the existing class being tweaked leading to its integrity being further degraded.

Effectively segregated code is also likely to be easier to test since the interface to the code will be simpler and it is likely to have fewer dependencies.

Finally the code base as a whole will be understandable with a clearer structure. New members of your team will quickly be able to asses what the code base is capable of by looking at the queries and commands that can be executed.

No one pattern can be a solution for all problems but certain qualities of well constructed code should be promoted above all others. Segregation of responsibility is one of those qualities, it is almost the very definition of good architecture to promote this quality and ensure its adoption and adherence.

As a code bases grows and evolves you will likely have to introduce additional concepts alongside that of commands and queries, providing these new elements have a strong identity and clearly defined role within your system then this will enable your code to grow whilst still maintaining the well defined structure that is a recipe for success.


No comments:

Post a Comment