Monolithic to Microservices Refactoring for Java EE Applications
Have you ever wondered what does it take to refactor an existing Java EE monolithic application to a microservices-based one?
This blog explains how a trivial shopping cart example was converted to microservices-based application, and what are some of the concerns around it. The complete code base for monolithic and microservices-based application is at: github.com/arun-gupta/microservices.
Read on for full glory!
Java EE Monolith
A Java EE monolithic application is typically defined as a WAR or an EAR archive. The entire functionality for the application is packaged in a single unit. For example, an online shopping cart may consist of User, Catalog, and Order functionalities. All web pages are in root of the application, all corresponding Java classes are in the WEB-INF/classes
directory, resources in WEB-INF/classes/META-INF
directory.
Lets assume that your monolith is not designed as a distributed big ball of mud and the application is built following good software architecture. Some of the common rules are:
- Separation of concerns, possibly using Model-View-Controller
- High cohesion and low coupling using well-defined APIs
- Don’t Repeat Yourself (DRY)
- Interfaces/APIs and implementations are separate, and following Law of Demeter. Classes don’t call other classes directly because they happen to be in the same archive
- Using Domain Driven Design to keep objects related to a domain/component together
- YAGNI or You Aren’t Going to Need It: Don’t build something that you don’t need now
Here is how a trivial shopping cart monolithic WAR archive might look like:
This monolithic application has:
- Web pages, such as
.xhtml
files, for User, Catalog, and Order component, packaged in the root of the archive. Any CSS and JavaScript resources that are shared across different webpages are also packaged with these pages. - Classes for the three components are in separate packages in
WEB-INF/classes
directory. Any utility/common classes used by multiple classes are packed here as well. - Configuration files for each component are packaged in
WEB-INF/classes/META-INF
directory. Any config files for the application, such aspersistence.xml
andload.sql
to connect and populate the data store respectively, are also packaged here.
It has the usual advantages of well-known architecture, IDE-friendly, easy sharing, simplified testing, easy deployment, and others. But also comes with disadvantages such as limited agility, obstacle for continuous delivery, “stuck” with a technology stack, growing technical debt, and others.
Even though microservices are all the raze these days, but monoliths are not bad. Even those that are not working for you may not benefit much, or immediately, from moving to microservices. Other approaches, such as just better software engineering and architecture, may help. Microservices is neither a free lunch or a silver bullet and requires significant investment to be successful such as service discovery, service replication, service monitoring, containers, PaaS, resiliency, and a lot more.
don’t even consider microservices unless you have a system that’s too complex to manage as a monolith.
Microservice Architecture for Java EE
Alright, I’ve heard about all of that but would like to see a before/after, i.e. how a monolith code base and how a refactored microservice codebase looks like.
First, lets look at the overall architecture:
The key pieces in this architecture are:
- Application should be functionally decomposed where User, Order, and Catalog components are packaged as separate WAR files. Each WAR file should have the relevant web pages (#15), classes, and configuration files required for that component.
- Java EE is used to implement each component but there is no long term commitment to the stack as different components talk to each other using a well-defined API (#14).
- Different classes in this component belong to the same domain and so the code is easier to write and maintain. The underlying stack can also change, possibly keeping technical debt to a minimum.
- Each archive has its own database, i.e. no sharing of data stores. This allows each microservice to evolve and choose whatever type of datastore – relational, NoSQL, flat file, in-memory or some thing else – is most appropriate.
- Each component will register with a Service Registry. This is required because multiple stateless instances of each service might be running at a given time and their exact endpoint location will be known only at the runtime (#17).Netflix Eureka, Etcd, Zookeeper are some options in this space (more details).
- If components need to talk to each other, which is quite common, then they would do so using a pre-defined API. REST for synchronous or Pub/Sub for asynchronous communication are the common means to achieve this.In our case, Order component discovers User and Catalog service and talks to them using REST API.
- Client interaction for the application is defined in another application, Shopping Cart UI in our case. This application mostly discover the services from Service Registry and compose them together. It should mostly be a dumb proxy where the UI pages of different components are invoked to show the interface (#18).A common look-and-feel can be achieved by providing a standard CSS/JavaScript resources.
This application is fairly trivial but at least highlights some basic architectural differences.
Monolith vs Microservice
Some of the statistics for the monolith and microservices-based applications are compared below:
Characteristic | Monolith | Microservice |
---|---|---|
Number of archives | 1 | 5
|
Web pages | 8 | 8 (see below) |
Configuration Files | 4
| 3 per archive
|
Class files | 12 | 26
|
Total archive size | 24 KB | ~52 KB (total) |
- Code base for the monolithic application is at: github.com/arun-gupta/microservices/tree/master/monolith/everest
- Code base for the microservices-enabled application is at: github.com/arun-gupta/microservices/tree/master/microservice
Issues and TODOs
Here are the issues encountered, and TODOs, during refactoring of the monolith to a microservices-based application:
- Java EE already enables functional decomposition of an application using EAR packaging. Each component of an application can be packaged as a WAR file and be bundled within an EAR file. They can even share resources that way. Now that is not a true microservices way, but this could be an interim step to get you started. However, be aware that
@FlowScoped
bean is not activated in an EAR correctly (WFLY-4565). - Extract all template files using JSF Resource Library Templates.
- Breakup monolithic database into multiple databases require separate
persistence.xml
and DDL/DML scripts for each application. Similarly, migration scripts, such as using Flyway, would need to be created accordingly. - A REST interface for all components, that need to be accessed by another one, had to be created.
- UI is still in a single web application. This should instead be included in the decomposed WAR (#15) and then composed again in the dumb proxy. Does that smell like portlets?
- Deploy the multiple WAR files in a PaaS (#12)
- Each microservice should be easily deployable in a container (#6)
Here is the complete list of classes for the monolithic application:
./target/classes/org/javaee7/wildfly/samples/everest/cart/Cart.class ./target/classes/org/javaee7/wildfly/samples/everest/cart/CartItem.class ./target/classes/org/javaee7/wildfly/samples/everest/catalog/CatalogItem.class ./target/classes/org/javaee7/wildfly/samples/everest/catalog/CatalogItemBean.class ./target/classes/org/javaee7/wildfly/samples/everest/catalog/CatalogItemType.class ./target/classes/org/javaee7/wildfly/samples/everest/checkout/Order.class ./target/classes/org/javaee7/wildfly/samples/everest/checkout/OrderBean.class ./target/classes/org/javaee7/wildfly/samples/everest/checkout/OrderItem.class ./target/classes/org/javaee7/wildfly/samples/everest/checkout/Shipping.class ./target/classes/org/javaee7/wildfly/samples/everest/uzer/Uzer.class ./target/classes/org/javaee7/wildfly/samples/everest/uzer/UzerBean.class ./target/classes/org/javaee7/wildfly/samples/everest/uzer/UzerItem.class
Here is the complete list of classes for the microservices-based application:
./catalog/target/classes/org/javaee7/wildfly/samples/everest/catalog/ApplicationConfig.class ./catalog/target/classes/org/javaee7/wildfly/samples/everest/catalog/CatalogItem.class ./catalog/target/classes/org/javaee7/wildfly/samples/everest/catalog/CatalogItemREST.class ./catalog/target/classes/org/javaee7/wildfly/samples/everest/catalog/CatalogItemType.class ./catalog/target/classes/org/javaee7/wildfly/samples/everest/catalog/ServiceRegistration.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/cart/Cart.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/cart/CartItem.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/catalog/CatalogBean.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/catalog/CatalogItem.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/checkout/Order.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/checkout/OrderBean.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/checkout/OrderItem.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/checkout/Shipping.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/ServiceDiscovery.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/ServiceDiscoveryStatic.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/ServiceDiscoveryURI.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/ServiceDiscoveryZooKeeper.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/uzer/UzerBean.class ./everest/target/classes/org/javaee7/wildfly/samples/everest/uzer/UzerItem.class ./order/target/classes/org/javaee7/wildfly/samples/everest/order/ApplicationConfig.class ./order/target/classes/org/javaee7/wildfly/samples/everest/order/Order.class ./order/target/classes/org/javaee7/wildfly/samples/everest/order/OrderItem.class ./order/target/classes/org/javaee7/wildfly/samples/everest/order/OrderREST.class ./user/target/classes/org/javaee7/wildfly/samples/everest/uzer/ApplicationConfig.class ./user/target/classes/org/javaee7/wildfly/samples/everest/uzer/UserREST.class ./user/target/classes/org/javaee7/wildfly/samples/everest/uzer/Uzer.class
- Once again, the complete code base is at github.com/arun-gupta/microservices.
Future Topics
Some of the future topics in this series would be:
- Are containers required for microservices?
- How do I deploy multiple microservices using containers?
- How can all these services be easily monitored?
- A/B Testing
- Continuous Deployment using microservices and containers
What else would you like to see?
Enjoy!
Reference: | Monolithic to Microservices Refactoring for Java EE Applications from our JCG partner Arun Gupta at the Miles to go 2.0 … blog. |
Most of what I’ve read about microservices elsewhere just talks about the generic idea and the benefits. So, two things that make this an excellent post: – It shows how microservices can be packaged in practice (for Java EE). – It points out that there are tradeoffs and disavantages to consider when following the microservices approach. I have a few comments/questions though: (1) As you pointed out, each microservice (i.e., each archive) has its own database. As you mentioned, the benefit is that each microservice has freedom to evolve and choose the most appropriate datastore. You could also have mentioned… Read more »
Good Article Arun !!.
I think, It would be good if we implement the Microservices Architecture based Application using OSGI as it consist of bunldes/jars instead of wars..
Paulo, Great points. – Referential integrity either needs to move up to the application, or the scope of the microservice needs to be realigned. – Common classes indeed need to be bundled up and I’ll reflect that in the diagram. – Registry is an important and critical step as each microservice may be implemented using different stacks with no connection to Java EE. It needs to be technology agnostic. ZooKeeper fits well there. Look at http://blog.arungupta.me/zookeeper-microservice-registration-discovery/ for more details. – A WAR should only have one resource and all the methods to manage it. So myWebStore/order would be the main… Read more »
Very interesting article: I see a practical application of the Microservices architectural paradigm! Anyway, on top of the advantages of Microservices (i.e. scalability, agility, maintainability), I’d add a few disadvantages like i. testability and ii. performance. In fact, a plethora of independent microservices is normally much more difficult to test, in terms of integration and so functional interactions (the platform needs to be deployed somewhere to let the Developer test). On the other hand, the performances are far less interesting than a monolith: what in a monolith is a flow of in-memory calls, in the microservices becomes a flow of… Read more »