In the world of Microservices, reusing code between Microservices isn’t a prominent practice. But as we all know, duplicating code doesn’t help either.

Besides, reusing code within a Microservice boundary is not a question at all. The dilemma is about reusing code between Microservices.

When we consider several Microservice teams, we want each team to operate with minimal dependencies between each other. Even when there are dependencies, we don’t want them to change often, affecting each teams’ autonomy. However, when we reuse code across Microservices, we fall into the trap of dependence, which could potentially disrupt the independence and agility of Microservice teams. Therefore, the challenge is to find the right balance between reuse and reliance.

Package Reusable Code as a Library

One of the well-known approaches to reuse code is to package it as a library maintaining it separately outside the Microservice code. Then the library could be published to a package repository so that others can use it.

Using Libraries to Reuse Code

Let’s look at several considerations when reusing code as libraries in the context of Microservices.

A particular team needs to take the ownership and governance of the library and its code repository.

One of the main limitations of this approach is that the Microservices should share the same programming language and platform to reuse the libraries effectively.

Make sure you organize and name the libraries in a meaningful way so that its more natural for other teams to discover.

Make sure the code packaged isn’t likely to change often.

Use the absolute minimum amount of code and functionality in libraries by design. Having more code increases the likelihood of change, which we need to reduce.

Consider using open source libraries over maintaining your own if possible. For example, if you need to extend an open-source library with a thin layer of code, duplicating that layer across Microservices as required is better than maintaining the entire thing as a custom library.

Use semantic versioning from the beginning.

More suitable for utility or helper code that doesn’t change often and has meaning across the Microservices.

Maintain a reasonable unit test coverage, which will also accelerate the development.

Identifying Reusable Components in Code and Sync (Managed Copy-Paste) Between Microservices

Think of you having sharable code like authentication layer validating tokens, authorization of Microservices, or shared UI components like navigation in an application (more applicable for Micro-Frontends). If we package them and publish them as libraries, it becomes a challenge to make minor adjustment while the application evolves since all the dependent Microservices needs to be updated. Besides, as we discussed previously, duplicating the code will also cause maintenance overhead, where we need to propagate the changes between all the Microservices.

So how about keeping your copies of reusable code within each Microservice as required and use a tool to synchronize the changes (managed copy-paste) across Microservices?

Platforms like Bit (Github) naturally solve this problem by allowing teams to organize components as collections and sync them automatically between Microservices. It also makes it easier for other Microservice teams to discover the components and keep their focus only on what matters to them.

Sync (Managed Copy-Paste) Code Components Between Microservices

Let’s look at several considerations when reusing code as components in the context of Microservices.

If the changes to reusable code make sense to be synchronized often to other Microservices, using components makes more sense over libraries.

Code that is more of evolving nature. For example, adding a new navigation link for a shared UI component is a part of the evaluation that we should synchronize when using Micro-Frontends. Another example would be to synchronize the authentication layer of Microservices.

This approach also has the dependency of using the same programming language and platforms to share code across Microservices.

We should only consider the reusable code for components that don’t have specific dependencies with each Microservice.

Will require to organize the shared components appropriately for improved discoverability and reuse.

The advantage of this approach over libraries is that the propagation of changes to shared components is fully autonomous by using tools like Bit, and its easier to test the shared code since its already a part of the Microservice.

This approach fits even to share business logic between Microservices.

One of the main challenges of this approach is that the changes to components should be carefully considered avoiding adverse effects to other Microservice teams.

Dynamic Code Injection at Runtime

If you consider the approaches mentioned above, integrating the reusable code happens at the build time. But how about dynamically fetching the reusable code and linking them at the runtime?

Embeddable frontend components with hosted JavaScript

This approach is easily achievable in a web browser environment for Microfrontends. We can serve frontend components in the form of bundled JavaScript files and embed them in other Microfrontends.

Let’s look at several considerations when reusing code with dynamic code injection in the context of Microservices.

This approach is practical for Microfrontends. Implementing this for Microservice backend code is challenging.

Changes to a component need to be managed appropriately and backward compatible.

Changes made to components need to be tested with integration to assure no conflict with the overall application. We can use End to End tests as a safety net to verify integrations before publishing the changes of embeddable code.

We need to standardize the communication across all the components.

This approach has the advantage of not requiring to worry about the internals of shared components. Besides, the new versions are readily available without any additional efforts to absorb the changes at build time.

However, each shared component should be owned by a Microservice team for governance.

Sharing Code as a Microservice

There are instances where it becomes difficult to reuse code in the form of libraries and components. For example, if we consider a piece of business logic that is likely to change and has a broad enough scope for a team to manage, packaging it in a library or a component doesn’t serve the purpose. These changes need to be absorbed by a Microservice team and exposed via an API.

Using a Microservice to Expose Business Logic

Let’s look at several considerations in this approach in comparison to the other strategies discussed above.

Since Microservice runs in its process and exposes an API, any consumer can use it without programming language limitations.

Requires to use API versioning.

Easy for governance and not needed to inform the consumers unless there are breaking changes.

One of the significant challenges is when implementing services for persisting data as part of a complex business transaction. For these kinds of scenarios, we need to facilitate the consumers of the Microservices to perform a strategy like the Saga pattern by adding revert operation as well within the Microservice.

Easier to use for business logic that involves read operations.

A team owning the Microservice will require to consider the management of resources and optimize accordingly.

Summary

As you can see, it is not easy to decide on absorbing the code into a Microservice, create a library, or duplicate with managed copy-paste. However, before making a decision, you need to consider the weigh between the maintainability of the code vs. the agility of Microservice teams.

If your software is at the early stages of the development and still following Microservices Architecture, defer the decision of reusing business logic unless the bounded contexts of the Microservices are well understood.

At last, I want to emphasize that don’t be rigid in following one approach. Use duplication, libraries, duplication with managed copy-paste, and expose as Microservices depending on the use case and be open to change if it doesn’t work down the line.

Learn More