Legacy-code monoliths are one of these challenges which lead often to controversy discussions and questions like:
- What are the pros and cons of getting rid of the code-base?
- How to get rid of it, via a big-bang or splitting it step by step?
- What are the efforts?
In this post I’d like to describe a way how to create the first code-sketch which could lead to an insight into the possibilities to tackle the beast step by step. I have to underline that the purpose of creating some code-sketches is not to replace further analysis tasks or producing any production-ready code.
The Beast
The dependencies to other systems, the code condition of the monolith, the data-structures et cetera are some issues to clarify.
If you are lucky, there are none or only a few dependencies, because the monolith owns everything and the code looks more or less structured; with separated components inside. But the reality often looks like pasta driven code with dependencies to everything.
The usage of dependency-, code-quality-tools and metrics is obligatory to get an idea about the conditions, but besides that, a first glance by sketching a specific scenario could help too.
Get in touch lightweight
Firstly try to find a method or class which covers more or less one use-case. Most likely there will be such code-snippets, because it’s a monolith, but unfortunately with bi-directional code-dependencies in the code-base to everything, long and unstructured classes and methods and finally no tests in place. Michael Feathers book Working effectively with legacy code[1] has a couple of tips how to handle legacy code. E.g. to cope with a long method, the pattern Break Out Method Object could be an idea to consider:
In a nutshell, the idea behind this refactoring is to move a long method to a new class. Objects that you create using that new class are called method objects because they embody the code of a single method. After you’ve used Break Out Method Object, you can often write tests for the new class easier than you could for the old method.
There are lot’s of more strategies of Michael’s collection you might need like: Extract Interface, Adapt Parameter, introducing seams et cetera.
In our sketch-context the idea is just to create an executable code-sketch which could work as a happy flow. If you have to handle a lot of issues and errors by touching the code just to get something extracted, you have the first insights and idea of upcoming issues. If you are lucky, the sketching shows a way how to extract a code-snippet respectively a coherent feature and integrate it into a new system-landscape – perhaps in a decent decoupled way.
Eventually the goal is to replace only one old part of the code. Just to emphasize: only one old part as a single goal. Instead of the old code an interface will be introduced as an entry point into the monolith or as an entry point into the world from the monolith’s perspective.
The entry point
Secondly if the code-snippet, method or class has been more or less decoupled and been replaced by an interface, the job of the extracted code-snippet itself can be realized as a service – in these days probably realized as microservice.
But to get the extracted code connected to the monolith again in a decoupled way, often some issues have to be resolved like: which data needs the service to fulfill it’s task, what is the impact of the latency due to the network overhead, do any transactional constraints exist, what are the security concerns et cetera. But these issues are candidates, to be sketched and evaluated in further scenarios.
Anyway, to get the monolith’s entry point connected with the service a couple of possibilities respectively transport-layer exist with different pros and cons and complexity. E.g. via:
- a synchronous connection and http – easy to realize but has its pitfalls and should be avoided
- an asynchronous connection realized as a message bus and topics to publish and subscribe to, or just via a queue – could lead to a good decoupling but needs extra efforts if synchronous/transactional flows are needed
- an asynchronous connection realized as an event-channel according to an event-driven architecture (EDA) – good decoupled way, but needs rethinking in terms of how the data is handled in general (it’s worth it!) [2][3][4]
Depending on the programming language and the available frameworks it could be a straight forward task, but could also be a big deal if no support is available. Luckily in the java-world a couple of frameworks exist to sketch such approaches in a lightweight way.
First sketches
To be flexible regarding the integration and connection possibilities mentioned above I decided to use the Apache Camel framework as described in this post [5]
The roughest way is just to put the extracted legacy code into a bean and integrate it via camel’s bean-component[6] and bean-integration possibilities.
The Bean component allows one to invoke a particular method. Alternately the Bean component supports the creation of a proxy via ProxyHelper to a Java interface; which the implementation just sends a message containing a BeanInvocation to some Camel endpoint.
The bean can be integrated [7][8] into camel routes and finally exposed via camel endpoints as needed.
Bean Binding in Camel defines both which methods are invoked and also how the Message is converted into the parameters of the method when it is invoked.
The annotated [9] source code could look like the following code-snippet:
@Produce(uri ="activemq:my.queue") protected ExtractedLegacyPartService service; public interface ExtractedLegacyPartService { void createPDF(String string); } public class ExtractedLegacyPartEntryPoint { public void createPDF(String string) { service.createPDF(string); } }
… Camel will automatically inject a smart client side proxy at the @Produce annotation
… in this case an instance of the ExtractedLegacyPartService.
When we invoke methods on this interface the method call is turned into an object and using the Camel Spring Remoting mechanism it is sent to the endpoint.
Note:
If it should work like an event-driven architecture as mentioned above, the method should not be named like createPDF and trigger a certain service via a queue. The method rather publishes an event what happened, like „an invoice has been created“, to an event channel. The new service subscribes for such types of events and creates PDFs for invoices if such events occur. But this is not so easily integrateable in legacy-code, because the old location of the extracted part is most likely not the correct or best point where to fire the event ‚what happened‘. More on EDA in further posts.
The counter part on the service-side looks like the quite self-explaining code-snippet:
public class ServiceImpl { @Consume(uri ="activemq:my.queue") public void doSomething(@Body String body) { System.out.println("created" + body); } }
To consume a message you use the @Consume annotation to mark a particular method of a bean as being a consumer method. The uri of the annotation defines the Camel Endpoint to consume from.
First outcome
Preferably the consumer (service) could be sketched as a Spring-Boot application [10] quite lightweight – just to see if the idea works and to find the next issues respectively sketch candidates.
References:
[1] Working effectively with legacy code
[2] Brenda M. Michelson, Event-Driven Architecture Overview, Patricia Seybold Group, February 2, 2006
[3] Event Sourcing
[4] Command Query Responsibility Segregation – CQRS
[5] Prototype sketching with Apache Camel
[6] Bean Component
[7] Bean integration
[8] Bean binding
[9] POJO Consuming, POJO Producing
[10] Application and system sketching based on code templates
Example source-code at github.com.
Featured images are taken from pixabay.com.
Du muss angemeldet sein, um einen Kommentar zu veröffentlichen.