Reuse was one of the most important promises of object-orientation in the nineties. “Just inherit code instead of writing it again” was the idea. However, that didn’t really work out and reuse is still rather uncommon. But even today, developers are still have the motto “Don’t Repeat Yourself” (DRY): Every decision should only be made once and must not be implemented more than once in the code.
Using code is already difficult, reusing it is even harder. After all, the code must suite many cases. It must be of high quality, easy to understand, and well documented. Achieving this kind of quality is very costly. Anyone who claims that reuse leads to an economic advantage must consider this additional effort. On the other hand, the higher quality of the reusable code has advantages far beyond reuse of course e.g. for maintainability.
Despite these difficulties, there is code reuse in every project. Every software uses one or the other open source library. Some libraries are used in thousands, if not millions of projects. Why are these projects so successful? What can we learn from the open source model?
An open source project provides not just the source code, but also good documentation and a process to incorporate changes made by external contributors into the project. Internal projects that create libraries for other projects in the same organization can also use the open source model. Usually, internal projects have the problem that they do not directly generate revenue, but only support the projects that make money. Therefore, budgets for internal projects are often limited. They also have difficulty setting the right priorities. If the internal project is organized according to open source ideas, other projects can contribute to the code - and thus develop the internal project prioritized by the requirements of these projects and with the resources of the projects that actually make money. There is also research into this.
Some companies do not only develop such libraries internally, but also publish important internal frameworks as open source. This includes not only US companies such as Netflix, but also German companies such as Otto, ImmobilienScout or Hypoport. Why would they do that? After all, it literally gives away an important competitive advantage. The reason: By publishing the projects as open source, they must further improve the quality of the frameworks. And the software is used in other, external projects. The feedback - e.g. bug reports - from those projects can be used to further improve the quality. Many hope that external developers will even submit changes to the code. But this is rarely the case. However, bug reports can also be very valuable, too.
That the needed quality of code and documentation is expensive and hard to achieve cannot really be an argument against publishing reusable codes as open source. After all, the code will still be used internally. Why would you expect your colleagues to use code that is so bad that you wouldn’t expect an external person to use it?
There is another problem with code reuse. In some situations, reusing code creates deployment dependencies. A change to the code forces a re-deployment of a huge number of services. Concrete example: A project has written a library to simplify the use of a central service. Due to a change in the service, the library had to be changed for all clients and all the services had to be deployed again. As this is a microservice system, we are talking about 50 services to be changed, which are developed by different teams. If we assume that the change and the necessary tests take half a day per service, then we are talking about 25 days of effort. And then the deployment of the services has to be coordinated. Anyone who has ever experienced such a situation will wish for it to stay that way.
Deployment dependencies are therefore a problem. A code dependency does not necessarily lead to a deployment dependency: If the service interface changes backward compatible so that the old client libraries continue to function, there is no deployment dependency. There is no need to re-deploy the clients, but they can continue to run with the old code, since it is still supported.
Common Data Model
Common code can be problematic for other reasons. If several components have a common data model, it becomes difficult to change: The components have to coordinate changes to the data model - this makes changes harder. Such a data model can the result of a common client library. The same applies to other code with domain logic.
Domain Driven Design considers the development of entire systems with Strategic Design. The pattern Bounded Context means that a domain model is only valid in a specific context. For example: For a customer, the shipping address is relevant for delivery, but the billing address and credit card data, or PayPal accounts are interesting for payment. It makes sense to store this data in the relevant module and not in a global model. This makes it easier to change the modules. Even a change to the payment process that requires additional data can be limited to the payment module.
This means that it is simply not possible to model the customer once and reuse it, because a different model is necessary in every Bounded Context. The reuse of data models is therefore particularly problematic. So, if anything, reuse should be limited to technical code.
If reusing code has so many disadvantages, what is the alternative? Redundancy. The customer can be implemented multiple times - once in each Bounded Context. In the same way, access to a service can be implemented multiple times instead of a common client library. This naturally creates redundancies.
But: Freedom from redundancy is a trade-off. The advantage of being redundancy-free is that an implementation is only done once and therefore, for example, only one change in one location is necessary to fix a bug. But then several modules use a common library in which the common functions are implemented. This leads to dependencies, which, as we have seen, can be problematic.
In the end, it’s all about implementing software efficiently. Code reuse can make sense. For example, all projects use open source libraries. Reuse requires a very high quality of the code. The high quality also has advantages beyond reuse, but also increases the effort. Worse still, reuse leads to dependencies - and this can make software hard to modify and develop. So reuse or redundancy is just a trade-off - like so many other decisions. Redundancy-free software and reuse should not be set as objectives in a project without weighing up the trade-off.
Thanks to INNOQ colleagues Andreas Krüger and Michael Vitz for discussing an early version of the blog post!