Can cross-cutting concerns really exist between services?

Cross-cutting concerns are those areas of functionality that have to be implemented across an entire system. Examples can include generic technical features such as error handling, logging and caching, but the idea can be extended to include common data and behaviours.

These kind of concerns can be hard to implement as there is no clean way of representing shared functionality in an object model. They often tend to become a source of tangled or scattered code so it becomes difficult to make changes to the implementation across an entire system. This problem is exasperated in services where the implementations are distributed across a number of separately deployable units.

The folly of code frameworks

A common solution to these cross-cutting concerns is to implement them in a shared code framework. The promise is of a set of common implementations that allow you to easily cascade changes across the any application. Achieving this is not as easy as it looks and most software re-use initiatives trend to fail.

The problem with code reuse is that it is difficult. Reusable code frameworks have to be designed for a generalised purpose and they rarely emerge as a natural by-product of application development. It is difficult to get the level of abstraction right without making components too specific for general use or too generalised to add any real value.

This means that frameworks can struggle to deliver any value beyond the narrow use cases they were originally designed for. Added to this are difficulties of organising ownership and ensuring adoption which are amplified in larger, more distributed development shops.

Do aspects solve the problem?

Whenever there is a discussion about cross cutting concerns, aspect-orientated programming (AOP) is normally lurking nearby. AOP treats these common behaviours as “aspects” which are expressed in code once and distributed via declarations. This effectively injects features that than code making direct calls to external libraries.

For all the conceptual neatness, the problem with AOP is that it is first and foremost a framework. Each module in your system inevitably becomes coupled to a common code library with all the difficulties of execution and organisation this brings.

Injecting code in this way tends to create distance between executing code and the implementation which obscures control flow. A maintenance developer trying to fix a security bug may struggle to figure out where the code is actually executing. It can also be difficult to determine who should be fixing the bug once it has been found.

Should services share any code?

With a bit of discipline you can implement cross-cutting concerns in a monolithic architecture, but they have less of a place in a decomposed architecture based on services.

One of the more fundamental requirements of services is that they should be autonomous so they do not rely on any other service to operate. Services are unique, or at least they should be, and can be deployed independently of each other. This allows a flexibility of implementation that helps to make service decomposition so powerful.

Service autonomy is undermined by the notion of a shared framework, as you risk coupling your services together via a third party code library. It also tends to assert a common implementation across every service that reduces the scope for individual optimisation, e.g. is it really wise to handle caching in the same way across every service?

Code duplication can seem messy and the urge to re-use can be very strong. I have even seen teams try and implement generic features such as logging and caching as independent services. This really is disappearing down a rabbit warren as you are creating direct and explicit coupling between services.

Some degree of repetition of both services and data is inevitable in a service-based architecture. You can attempt to encourage best practice via preferred patterns or libraries, but this should stop short of tying your services together with common implementations. There are no cross-cutting concerns in service development.