Over a million developers have joined DZone.
{{announcement.body}}
{{announcement.title}}

Dynamic, Modular Microservices with Java EE

DZone's Guide to

Dynamic, Modular Microservices with Java EE

Microservices are all the rage these days with new frameworks popping up at every turn. Java EE is a capable microservices platform for many use cases.

· Integration Zone
Free Resource

Modernize your application architectures with microservices and APIs with best practices from this free virtual summit series. Brought to you in partnership with CA Technologies.

Microservices are all the rage these days with new frameworks popping up at every turn. As many have pointed out, Java EE is indeed a capable microservices platform for many use cases.

Even if you feel that microservices might not be right for you, the modularity it touts can greatly help you as your applications grow. Another very useful feature is the ability to reload parts of your application without taking the whole system down. Some of the application modules might run in the same JVM, and so they might not need to talk REST with each other to communicate. In fact, in-JVM communication between modules are likely to be faster and require fewer security constraints than exported services.

This is a small cookbook showing you how you can structure a Java EE application into modules, giving you the benefit of enforced application boundaries and the ability to reload parts of your application with zero downtime using plain Java EE features and a little magic glue.

The code for this demo application is available on GitHub and consists of the following modules:

  • Common: shared domain objects and service interfaces.

  • Service: implementation of the service interfaces using JPA.

  • Rest: JSON Based REST endpoint that calls the services.

  • Frontend: HTML5 Frontend that talks to the REST endpoint.

Our Common module has a simple Customer object with two properties and JPA annotations:

@Entity
public class Customer {
    @Id
    private Integer id;
    private String name;
}

The CustomerService interface also live in the Common module:

public interface CustomerService {
    Customer getById(Integer id);
    List<Customer> all();
}

The Service module implements the CustomerService using JPA for data access:

@Stateless
public class CustomerServiceImpl implements CustomerService {
    @PersistenceContext
    EntityManager em;

    public Customer getById(Integer id) {
        return em.find(Customer.class, id);
    }

    public List<Customer> all() {
        return em.createQuery("SELECT c FROM Customer c", Customer.class).getResultList();
    }
}

We want to be able to access this service from other modules within the same JVM. It could make sense to keep the customer service and the REST endpoint in the same JVM even if we scale out horizontally later on. If we need to separate the two, it will be relatively easy to switch to HTTP client-based access in the REST endpoint, maintaining the module separation and basically keeping the same programming model.

Your application might never have millions of hits and thousands of simultaneous users. Do not over-engineer your application for a situation that might never occur, but make sure you haven't made it difficult to evolve if the need arises.

Hot Module Reloading

Let's have a look at the CustomerResource that exposes the REST endpoint to the frontend:

@Path("customer")
@Produces("application/json")
public class CustomerResource {
    @Inject CustomerService customerService;

    @GET
    @Path("{id}")
    public Customer getCustomer(@PathParam("id") Integer id) {
        return customerService.getById(id);
    }

    @GET
    public List<Customer> all() {
        return customerService.all();
    }
}

The Magic Glue

The observant reader might object at this stageā€”how can we inject the CustomerService when it is coming from a different module? Injection is only available for services declared within the same module. To support this we introduce a small helper that locates the CustomerService and re-exports it with the @Produces annotation:

public class Imports {
    @Produces
    public CustomerService getCustomerService() {
        return Services.getCustomerService();
    }
}

Instead of injecting an EJB and re-exporting it, we call a utility method that is actually a part of the Common module. This makes sure that the Rest module can start even if the Service module is not available at startup. In effect, we now support the dynamic availability of modules with very little code overhead.

This utility method knows how to locate the CustomerService via JNDI, but it also employs a timeout mechanism that will block if the service is not available and return once it comes online. In case the service doesn't become available within a configured timeout, we throw an exception.

This will make it possible to take the service module offline or even reload it with new functionality with zero downtime, provided the new module can be loaded before the timeout. If there are incoming requests to the REST endpoint while the service is reloading, those calls would simply take a bit longer, but not fail.

It is important to note that the implementation of the timeout mechanism is primitive for demo purposes and that it might not be suited for high volume services because it would quickly create congestion if the service is gone for a lengthy period. The timeout value would at a minimum need to be adjusted based on the service pressure, and most likely a more sophisticated algorithm should be used in production (for example, taking backpressure into account when determining the timeout, and fail fast under high load).

As your system grows, the Service utility class would contain methods to expose the new services you add.

In every module where you want to @Inject services produced by other modules, you must introduce an Imports class that re-exports the services you need in that module. Alternatively, instead of injecting the services you can simply call Services.getCustomerService() wherever you need access to the service.

Injection in Singleton Services

Since we don't actually put a proxy in front of our services, this approach would fall through for @Singleton or @ApplicationScoped beans. For these beans you can choose to access the services via the static getXXXService() functions, or inject an Instance<CustomerService> instead:

@Singleton
public class MySingletonService {
    @Inject Instance<CustomerService> customerService;
    public Customer getById(Integer id) {
        return customerService.get().getById(id);
    }
}

This enables us to utilize the timeout feature even for singleton beans.

Let's have a look at the Services utility function. Bear in mind that the implementation is simplistic and not suited for production and that there is no circuit breaker in place.

public class Services {

    /**
    * Lookup the CustomerService. You would add one of these for each service you introduce
    */
    public static CustomerService getCustomerService() {
        return getService("java:global/service/CustomerServiceImpl");
    }

    /**
    * Lookup the given service and wait if the service is offline. After a timeout, throw an exception.
    */
    private static <T> T getService(String lookup) {
        Integer counter = 0;
        while (counter++ < 10) {
            try {
                return (T) new InitialContext().lookup(lookup);
            } catch (NameNotFoundException ex) {
                log.warning(String.format("Waiting for %s to come online...", lookup));
                sleep(1000);
            } catch (NamingException ex) {
                throw new RuntimeException(ex);
            }
        }
        throw new RuntimeException(String.format("Timeout waiting for %s", lookup));
    }
}

Technical notes

This demo is tested in the WildFly 10 application container and it uses JBoss specific deployment instructions. For example, the Rest and Service modules have a module dependency on the Common module so that they can access the shared domain objects and service interfaces. The deployment instructions are placed in a file called jboss-deployment-descriptor.xml, which is placed inside META-INF for a jar module or WEB-INF for a war module.

The dependency on the Common module is expressed like this:

<jboss-deployment-structure>
    <deployment>
        <dependencies>
            <module name="deployment.common.jar"/>
        </dependencies>
    </deployment>
</jboss-deployment-structure>

Pros

Compared to a flat monolithic structure, you are guaranteed to never overstep defined boundaries. By adopting a modular approach, you are forced to maintain a healthy separation of concerns between each module, and calling impl-to-impl will just not work. The design patterns you always knew you should adhere to are now finally enforced for you, so it requires less discipline on your part.

The ability to reload only a part of the application at a time with zero downtime in production can be invaluable. The demo project can be deployed one module at a time by running `mvn wildfly:deploy` inside that module, or at the root of the project to deploy all modules.

Cons

A small amount of boilerplate is involved in defining and consuming services from other modules. This can be thought of as an integral part of the usage pattern rather than a con.

Java EE Event fires only within a single module. You must, therefore, bridge these yourself or use JMS or some other message transport if necessary.

Conclusion

Adopting a modular approach in your applications will make it easier to split your application into microservices if you want or need to down the line. Modularity makes it easier to maintain larger code bases, even for monoliths.

Hot reload of single modules can be very convenient and time-saving in production.

Java EE is more than capable of supporting modern microservices, even if it currently isn't completely buzzword-compliant. Great things are happening in Java EE 8, but we are by no means lost with the currently EE 7 release.

This approach might not be for everyone, and I'm not trying to start a flame war over the best approach to microservices or modularity, but the patterns demonstrated here can be useful both as a monolith migration path and for new projects.

Some of the implementation details are specific to WildFly/JBoss.

Source of inspiration

The inspiration for this article was taken from the WildFly inter-app demo. My version doesn't use the @Remote annotation and doesn't require shared objects to be serializable. It also enables circular module dependencies and the ability for a module to start even when it's dependencies are not available at start time.

The Integration Zone is proudly sponsored by CA Technologies. Learn from expert microservices and API presentations at the Modernizing Application Architectures Virtual Summit Series.

Topics:
java ,javaee ,modular ,integration

Opinions expressed by DZone contributors are their own.

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}