I’m currently in the process of looking at how you can evolve Java EE applications into microservices and wanted to share some initial thoughts and observations as I go through this process. So what I write now, I may change my mind about later on, and of course these thoughts and opinions are my own (I think that’s enough of a disclaimer).
One of the things I’ve noticed is that there are plenty of articles and words of wisdom available about converting monolithic apps. What I find though is that they tend to present either a ‘golden path’ or are at such a high level that you understand the principles but the moment you try and apply it to your application, reality asserts itself and things start to go wrong. So I’m interested in understanding what issues are specific to Java EE applications and how to deal with them, or at least have some appreciation of the cost or risk involved.
I currently have a number of working drafts about the processes I’m following and work I’ve done, but being a developer I’m always looking for re-use. This got me to thinking about whether or not there is a common set of things that can be considered when assessing an evolution? Sure, they won’t all apply all the time, but it might help you think about what issues you’re going to have. It’s not an exhaustive list, just the things that I’ve observed or encountered so far – see my earlier disclaimer :-).
When looking at an evolution, I’m assuming that I’m trying to end up with something that has functional equivalence with what I’ve already got. That doesn’t mean I have to always be using the same code, environment, or services, but I’m also not trying to sneak in a whole new set of features at the same time. There is, of course, the opportunity to get lots of added benefits from the final solution, such as new DevOps pipelines for faster time to market or operational savings by moving to the cloud.
The challenge I’m setting myself here is to see if I can develop a re-usable set of high level principles/rules that I can use to guide me during the evolution process. So far, based on what I’ve experienced (see disclaimer above – you’re probably going to see me write that a lot in these posts!), I have initially come up with:
- Change as little business logic or function as possible: If I have a currently working system, it may contain some potentially complex business logic. I may or may not understand the business logic or have access to the source, so I want to avoid changing it if possible. I’m going to be looking to make use of proxies, interceptors, filters etc. to handle changes for me. This also provides a nice fall-back option in case something I try doesn’t work out, in that I can just remove it.
- The final destination is not always the cloud: When evolving an application there may be sensitive components or data that you’re just not going to be comfortable running in the cloud. Alternatively,you probably still need to connect to other internal systems. When evolving applications a hybrid cloud solution is a highly likely outcome.
- Strangle, port, refactor, or new – whatever you do, the end result is removal from the monolith: Evolution is going to involve the removal of some functionality from the original monolithic Java EE applications. It may not move very far (i.e. it could still be on the same physical hardware) but you are going to need to know how to find it.
- Things are going to move: This sort of follows on from the previous point but goes further in that there is the distinct possibility that services you rely on may move. For example, you might start with something locally but then move it to the cloud at a later point in time.
If I accept these principles I’m going to need to consider the following questions:
- What are the APIs that you are going to create? Lots of services don’t have user interfaces, they are just APIs. How am I going to design, implement, manage, and expose them? Should I be using REST, WebSockets, messaging? Will I be creating additional business opportunities through my new APIs by being able to offer new services, or will I be able to reduce existing maintenance costs by converging services?
- Where is your data and in what format? There are lots of places that a monolithic application may get its data from: the file system, database, or deployment archive to name just a few. The other thing that you might find is that your data is all lumped together, which was fine when everything was part of a single application but what if you now need to split out just a small part of that data? Do the tools exist to do that, and will it involve creating lots of additional data just to tie everything back up together?
- How do you find them and what is the wiring? As mentioned in the principles, services are going to move so you need to be able to find them. Does this mean using a service registry/discovery mechanism? Possibly (which is normally the most definitive answer you get with microservices), but what it does mean is that you need to externalise your connection and configuration. The wiring aspect refers to how you are now going to access services, which will be influenced, if not set, by your API considerations.
Things about Java EE applications
Finally, some things to think about that are more specifically related to Java EE applications:
- Java EE applications are inward facing: think about it, when you have a single application, you make lots of relative (read internal) references. This needs to change if these functions are now provided by an external service. One example is URLs in web pages which provide your client application. Typically they will reference resources relative to the context root of the application. If these resources are provided elsewhere now, something has got to handle that change. Another example is internal servlet redirection or forwarding, again it was designed to work intra-application, not with external services.
- Is CDI (or any other injection technology) an evolution anti-pattern or red flag? CDI is great in that it allows a developer to code against an interface and have the correct implementation injected at runtime. However, if you are trying to tease apart your monolithic application you may not know what is being injected at runtime and, in that case, how can you move to something equivalent? If it forms some part of an overall framework, do you now also require that framework or can you move to something else?
I’m sure I haven’t exhausted the list of things to consider when evolving a monolithic Java EE application to microservices. What I’ve described here does at least start to give a framework within which you can start assessing your application. It should also help you identify those areas that are perhaps more risky or potentially will cost more to evolve.