Sharing functionality or data among services #
Architectural patterns manifest several ways of sharing functionality or data among their components. Let’s consider a basic example: calls to two pieces of business logic need to be logged, while the logger is doing something more complex than mere console prints. The business logic also needs to access a system-wide counter.
Direct call #
The simplest way to use a shared functionality (aspect) is to call the module which implements it directly. This is possible if the users and the provider of the aspect reside in the same process, as in a Monolith or module-based (single application) Layers.
Sharing data inside a process is similar, but usually requires some kind of protection, like an RW lock, around it to serialize access from multiple threads.
Make a dedicated service #
In a distributed system you can place the functionality or data to share into a separate service to be accessed over the network, yielding Service-Oriented Architecture for shared utilities or a Shared Repository / Polyglot Persistence for shared data.
Delegate the aspect #
A less obvious solution is delegating our needs to another layer of the system. To continue our example of logging, a Proxy may log user requests and a Middleware – interservice communication. In many cases one of these generic components is configurable to record all calls to the methods which we need to log – with no changes to the code!
In a similar way a service may behave as a function: receive all the data it needs in an input message and send back all its work as an output – and let the database access remain the responsibility of its caller.
Replicate it #
Finally, each user of a component can get its own replica. This is done implicitly in Shards and explicitly in a Service Mesh of Microservices for libraries or Data Grid of Space-Based Architecture for data.
Another case of replication is importing the same code in multiple services, which happens in single-layer Nanoservices.
Summary #
There are four basic ways to share functionality or data in a system:
- Deploy everything together – messy but fast and simple.
- Place the component in question into a shared service to be accessed over the network – slow and less reliable.
- Let another layer of the system both implement and use the needed function on your behalf – easy but generic, thus it may not always fit your code’s needs.
- Make a copy of the component for each of its users – fast, reliable, but the copies are hard to keep in sync.