Modern Java EE Design Patterns Red Hat
Modern Java EE Design Patterns Red Hat
Design Patterns
Building Scalable Architecture for
Sustainable Enterprise Development
Co
pl
im
en
ts
of
Markus Eisele
Build, host
and scale
apps fast.
Get involved at
developers.redhat.com
Markus Eisele
First Edition
978-1-491-94391-5
[LSI]
Table of Contents
Foreword. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii
Acknowledgments. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix
1. Enterprise Development Today. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
Enterprise Goals and Objectives
Resistant to Change and Economically Efficient
Developers Left Alone
Technology-Centric Versus Business-Centric
Aims and Scope
2
2
3
3
3
6
7
9
15
16
17
19
26
26
32
34
v
Migration Approaches
35
39
40
41
42
43
44
6. Conclusion. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 47
A. Additional Technologies and Team Considerations. . . . . . . . . . . . . . . 49
B. Further Resources. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
vi
Table of Contents
Foreword
For 20 years, Java has dominated the non-Microsoft development
landscape. For almost as long, J2EEand now Java EEhas been
the platform of choice for a significant number of enterprises, rang
ing from banks and hospitals to airplane manufacturers and
national defense departments. Java EE has evolved dramatically over
the years, greatly simplifying the development efforts for people
building complex enterprise applications. As new approaches (e.g.,
web services and REST) and new languages (e.g., Scala, Ruby, and
Ceylon) have emerged Java EE has quickly adapted to support them
all. For almost two decades it has been the go-to development and
deployment platform for mission-critical applications across a wide
range of languages.
However, with the advent of mobile, the Internet of Things, and the
cloud, many people have wondered whether Java and Java EE con
tinue to have a place. The answer is a resounding yes. All of these
new waves of technology have significant tiers or layers in their
application architectures that require the types of enterprise capabil
ities often taken for granted by Java EE developers. In addition, with
the cloud comes DevOps, and from that emerged microservices
(also an offshoot of service-oriented architecture, where Java EE has
played a role over the years). Again, though, Java EE has something
to offer here.
In this report, you will learn about a wide range of technologies and
trends, but Markus Eisele brings them together in a seamless way.
Markus is an extremely experienced developer who has been
involved with Java EE for longer than many. Critically, he has also
wtinessed the use of Java EE in many areas, both correctly and
incorrectly, which gives him experience that few others can match.
Finally, and just as important, he can write well and articulate prob
lems and their solutions in a way that makes them accessible to
many people with a variety of skills and experiences.
Markus starts by giving the reader an overview of the history of Java
and Java EE. What worked? What mistakes did the industry make?
How has service-oriented architecture, in the guise of the enterprise
service bus (ESB), influenced things? How have DevOps, microser
vices, and immutable containers started to have an effect? The
Acknowledgments
ix
CHAPTER 1
CHAPTER 2
History of Java EE
Mistakes We Made
Traditionally, Java EE applications followed the core pattern defined
in the book Core J2EE Patterns and were separated into three main
layers: presentation, business, and integration. The presentation
layer was packaged in Web Application Archives (WARs) while
business and integration logic went into separate Java Archives
(JARs). Bundled together as one deployment unit, a so-called Enter
prise Archive (EAR) was created.
The technology and best practices around Java EE have always been
sufficient to build a well-designed monolith application. But most
enterprise-grade projects tend to lose a close focus on architecture.
The aspects of early Java EE applications outlined in Figure 2-1 dont
make assumptions about their technical capabilities, and are derived
from experience in the field.
(EAM) solutions, and there are even more centralized enterpriselevel infrastructures (e.g., logging, monitoring, and databases).
Because everything was too coupled and integrated to make small
changes, applications also had to be tested with great care and from
beginning to end. A new release saw the light of day once or twice a
year. The whole application was a lot more than just programmed
artifacts: it also consisted of uncountable deployment descriptors
and server configuration files, in addition to properties for relevant
third-party environments.
Even the teams were heavily influenced by these monolithic soft
ware architectures. The multimonth test cycle might have been the
most visible proof. But besides that, projects with lifespans longer
than five years tended to have huge bugs and feature databases. And
if this wasnt hard enough, the testing was barely qualifiedno
acceptance tests, and hardly any written business requirements or
identifiable domains in design and usability.
Handling these kinds of enterprise projects was a multiple team
effort and required a lot of people to oversee the entire project.
From a software design perspective, the resulting applications had a
very technical layering. Business components or domains were
mostly driven by existing database designs or dated business object
definitions. Our industry had to learn those lessons and we man
aged not only to keep these enterprise monoliths under control, but
also invented new paradigms and methodologies to manage them
even better.
10
solution, a new technology found its place in the stack for the next
generation of enterprise applications.
11
12
CHAPTER 3
13
14
15
Domain-Driven Design
The philosophy of domain-driven design (DDD) is about placing
the attention at the heart of the application, focusing on the com
plexity of the core business domain. Alongside the core business
features, youll also find supporting subdomains that are often
generic in nature, such as money or time. DDD aims to create mod
els of a problem domain. All the implementation detailslike per
sistence, user interfaces, and messagingcome later. The most cru
cial thing to understand is the domain, because this is what a major
ity of software design decisions are going to be based on. DDD
defines a set of concepts that are selected to be implemented in soft
ware, and then represented in code and any other software artifact
used to construct the final system.
Working with a model always happens within a context. It can vary
between different requirements or just be derived, for example, from
the set of end users of the final system. The chosen context relates to
the concepts of the model in a defined way. In DDD, this is called
the bounded context (BC). Every domain model lives in precisely
one BC, and a BC contains precisely one domain model. A BC helps
to model and define interactions between the BC and the model in
many different ways. The ultimate mapping for the model is the
inside view of the one related BC.
Assuming we already have a layered application approach (e.g., pre
sentation, application, domain, infrastructure), DDD acts on the
domain layer. While the application layer mostly acts as a mediator
between presentation, domain, and infrastructure (and holds addi
tional crosscutting concerns, such as security and transactions), the
domain layer only contains the business objects. This includes the
value objects themselves and all related artifacts (e.g., property files,
translations) and the module structure, which typically is expressed
in packages (e.g., in Java) or namespaces.
Entities, values, and modules are the core building blocks, but DDD
also has some additional features that will help you to model your
application so that you can build it from domain services. A domain
service corresponds to business logic that does not easily live within
an entity or it can act as a proxy to another BC. While a domain ser
vice can both call or be called by a domain entity, an application ser
vice sits above the domain layer, so it cannot be called by entities
16
within the domain layer, only the other way around. Put another
way, the application layer (of a layered architecture) can be thought
of as a set of (stateless) application services (Figure 3-2).
Service Characteristics
For a first migration assessment, it is helpful to identify and separate
the services into categories by looking at some key characteristics. It
is recommended to only use them in a first round of qualification
for a potential microservices migration and not as a design or refac
toring methodology. Lets discuss the most important ones in the
following subsections.
Service Characteristics
17
Core Services
Core services follow the definition of domain services and expose a
specific domain entity, including all relevant base operations,
directly to a consumer. If you dont have a domain model, you can
watch out for entities named after nouns. Another good starting
point is a use case or user story. You can even find a lot of examples
from common business processes, such as:
Order
Shipping
Catalog
User
Customer
Process Services
Process services follow the business services definition and are
responsible for performing a single, complex task. They usually rep
resent a business action or process related to and relying on one or
more core services. Finding the right partition without a domain
model is time consuming and needs to be thought through before
implementing. Try to keep the focus on the different business capa
bilities of a system. Respect the already-known drawbacks from tra
ditional architectures, and keep the network latency and number of
hops in mind. It might be easier to verbalize a process service by
putting its mission statement up front, such as the following:
This service lists similar courses for a given course.
This service places an order for a customer.
This service reroutes a shipment.
This service logs an order step for a customer.
If the work of a first assessment is done, you also want to see how
much of the existing application already adheres to the basic
requirements for building a microservices architecture.
18
19
21
Use transactions
It is a common misunderstanding that microservices-based archi
tectures cant have or use transactions at all. There are plenty of ways
to use atomic or extended transactions with different technologies
that consider themselves part of the modern software stack. Exam
ples of technologies range from server-supported transaction man
agers, to OMGs Additional Structuring Mechanisms for the OTS
and WS-Transactions from OASIS, to even vendor-specific solutions
like REST-AT. Implementing equivalent capabilities in your infra
structure or the services themselves (e.g., consistency in the pres
ence of arbitrary failures, opaque recovery for services, modular
structuring mechanisms, and spanning different communication
patterns) is something you should consider very carefully.
22
Event-driven design
Another approach to transactions is the event-driven design of serv
ices. This requires some logic to record all writes of all services as a
sequence of events. By registering and consuming this event series,
multiple services can react to the ordered stream of events and do
something useful with it. The consuming services must be responsi
ble and able to read the events at their own speed and availability.
This includes a tracking of the events to be able to restart consump
tion after a particular service goes down. With the complete write
history as an events database, it would also be possible to add new
services at a later stage and let them work through all the recorded
events to add their own useful business logic.
23
even be some rare instances that require the use of even more lowlevel service interfaces based on older remoting technologies. The
performance of the whole shouldnt be sacrificed just to be buzz
word compatible. Further on, it might be valid to test different sce
narios and interface technology stacks for optimal performance.
25
between the server and the client. A different layer of caching tech
nology comes in at the backend. The easiest case is to use a secondlevel cache with a JPA provider or a dedicated in-memory datastore
as a caching layer for your domain entities. The biggest issue is
maintaining consistency between cache replicas and between the
cache and the backend data source. The best approach here is to use
an existing implementation such as JBoss Infinispan.
Crosscutting Concerns
Crosscutting concerns typically represent key areas of your software
design that do not relate to a specific layer in your application. In a
domain-driven design approach, this shouldnt happen. But you
really want crosscutting concerns to be reusable non-domainrelated concerns that arent scattered around the whole project. This
is where design concepts like dependency injection (DI) and aspectoriented programming (AOP) can be used to complement objectoriented design principles to minimize tight coupling, enhance
modularity, and better manage the crosscutting concerns.
Security
Security in microservices applications breaks down into three differ
ent levels (Figure 3-3).
26
27
Logging
Although logging in typical enterprise environments only has to ful
fill a few basic needs (such as developer support, debugging in pro
duction, and business transaction logging), the nature of a dis
tributed system requires a lot more.
Because one service request can be split out to many different subse
quent requests and produce an error somewhere downstream, log
ging should be able to follow the complete request path down to the
error. This might be done with unique service request IDs or even
with the help of an HttpSession or SSL session ID (captured at the
entry service). And all the distributed logging sources need to be
collected in a single application-wide log.
Depending on the existing environment, this can be done with log
ging frameworks that support syslog or other existing centralized
28
logging solutions but also built using the ELK (Elasticsearch, Log
stash, and Kibana) stack.
Health Checks
Health checks are an important part of DevOps. Every part needs to
be controlled and monitored from the very beginning. Besides just
having a simple is-alive servlet, the need for more sophisticated
health checks on a service level arises when using a microservices
architecture.
However, there are different ways of approaching this requirement.
A simple approach is to select an API management solution that not
only deals with governance and load balancing but also handles the
SLA and implicit health monitoring of every service. Although this
is strongly recommended, there are plenty of other solutions start
ing from custom implementations up to more complex monitoring
approaches.
Integration Testing
While integration testing for Java EE applications has always been
important but complex, it is even harder for microservices-based,
distributed systems. As usual, testing begins with the so-called mod
ule or developer tests. Typically running on a single developer
machine, integration tests for distributed systems require the pres
ence of all downstream services. With everything completely auto
mated, this also includes controlling the relevant containers with the
dependent services. Although mocking and tests based on intelligent
assumptions were best practices a couple of years back, todays sys
tems have to be tested with all the involved services at the correct
version.
First and foremost, this requires a decent infrastructure, including
complete test and integration systems for the various teams. If youre
coming from a traditional enterprise development environment, it
might feel odd to exceed the existing five different test stages and
corresponding physical machines. But working with microservices
and being successful in enterprise settings will require having a PaaS
offering, which can spin up needed instances easily and still be costeffective.
Depending on the delivery model that has been chosen, this might
involve building container images and spinning up new instances as
Crosscutting Concerns
29
30
CHAPTER 4
31
JAX-RS 2.0
To execute asynchronous requests in JAX-RS, inject a reference to a
javax.ws.rs.container.AsyncResponse interface in the JAX-RS
resource method as a parameter. The resume method on the
AsyncResponse object needs to be called from within a separate
thread after the business logic execution is complete, as illustrated
here:
32
@Inject
private Executor executor;
@GET
public void asyncGet(@Suspended final AsyncResponse
asyncResponse) {
executor.execute(() -> {
String result = service.timeConsumingOperation();
asyncResponse.resume(result);
});
}
WebSocket 1.0
To send asynchronous messages with the WebSocket API, use the
getAsyncRemote method on the javax.websocket.Session inter
face. This is an instance of the nested interface of the javax.web
socket.RemoteEndpoint:
public void sendAsync(String message, Session session){
Future<Void> future = session.getAsyncRemote()
.sendText(message);
}
Servlet 3.1
The servlet specification also allows the use of asynchronous request
processing. The parts that need to be implemented are thread pools
(using the ExecutorService), AsyncContext, the runnable instance
of work, and a Filter to mark the complete processing chain as
asynchronous.
33
Service Registry
Multiple microservices are composed to create an application, and
each microservice can scale independently. The endpoint of the ser
vice may not be known until its deployed, especially if its deployed
in a PaaS. Service registration allows each microservice to register
itself with a registry using a logical name. This name is bound to a
physical URI and additional metainformation.
By using the logical name, a consumer can locate and invoke the
microservice after a simple registry query. If the microservice goes
34
Security
In a traditional multitiered server architecture, a server-side web tier
deals with authenticating the user by calling out to a relational data
base or a Lightweight Directory Access Protocol (LDAP) server. An
HTTP session is then created containing the required authentication
and user details. The security context is propagated between the
tiers within the application server so theres no need to reauthenti
cate the user.
This is different with microservices because you dont want to let
this expensive operation occur in every single microservices request
over and over again. Having a central component that authenticates
a user and propagates a token containing the relevant information
downstream is unavoidable. Enterprise access management (EAM)
systems mostly provide the needed features in an enterprise envi
ronment. In addition, some API management solutions also contain
security features on top of their government engine. And last but
not least, there are dedicated products, like JBoss Keycloak.
Migration Approaches
Putting the discussion in Chapter 3 about greenfield versus brown
field development into practice, there are three different approaches
to migrating existing applications to microservices.
Selective Improvements
The most risk-free approach is using selective improvements
(Figure 4-3). After the initial assessment, you know exactly which
parts of the existing application can take advantage of a microservi
ces architecture. By scraping out those parts into one or more serv
ices and adding the necessary glue to the original application, youre
able to scale out the microservices in multiple steps:
First, as a separate deployment in the same application server
cluster or instance
Migration Approaches
35
36
Migration Approaches
37
CHAPTER 5
Common Principles
Every microservice has some common basic principles that need to
be taken into account. They are derived from a quick recap of Ser
vice Characteristics on page 17 and Microservices Best Practices
on page 19.
39
Aggregator Pattern
The most simplistic pattern used with microservices is the aggrega
tor pattern (Figure 5-1). It is already well known from the Enterprise
Integration pattern catalog and has proven to be useful outside
microservices architecture. The primary goal of this pattern is to act
as a special filter that receives a stream of responses from service
calls and identifies or recognizes the responses that are correlated.
Once all the responses have been been collected, the aggregator cor
relates them and publishes a single response to the client for further
processing.
In its most basic form, aggregator is a simple, single-page applica
tion (e.g., JavaScript, AngularJS) that invokes multiple services to
achieve the functionality required by a certain use case. Assuming
40
all three services in this example are exposing a REST interface, the
application simply consumes the data and exposes it to the user. The
services in this example should be application services (compare
above) and do not require any additional business logic in the fron
tend. If they represent domain services, they should be called by an
application service first and brought into a representable state.
Proxy Pattern
The proxy pattern allows you to provide additional interfaces to
services by creating a wrapper service as the proxy (Figure 5-2). The
wrapper service can add additional functionality to the service of
interest without changing its code.
Proxy Pattern
41
Pipeline Pattern
In more complex scenarios, a single request triggers a complete ser
ies of steps to be executed. In this case, the number of services that
have to be called for a single response is larger than one. Using a
pipeline of services allows the execution of different operations on
the incoming request (Figure 5-3). A pipeline can be triggered syn
chronously or asynchronously, although the processing steps are
most likely synchronous and rely on each other. But if the services
are using synchronous requests, the client will have to wait for the
last step in the pipeline to be finished.
42
Shared Resources
One of the critical design principles of microservices is autonomy.
Especially in migration scenarios (see Migration Approaches on
page 43), it might be hard to correct design mistakes made a couple
of years ago. And instead of reaching for the big bang, there might
be a more reasonable way to handle those special cases.
Running into a situation where microservices have to share a com
mon data source isnt ideal. However, it can be worked around with
the shared resources pattern (Figure 5-4). The key here is to keep
the business domain closely related and not to treat this exception as
a rule; it may be considered an antipattern but business needs might
Shared Resources
43
Asynchronous Messaging
Typical RESTful design patterns are common in the microservices
world. Most likely, they are implemented in a synchronous and
therefore blocking manner. Even if this can be changed in Java EE,
and the implementations support asynchronous calls, it might still
be considered a second-class citizen in the enterprise systems you
are trying to build. Message-oriented middleware (MOM) is a more
reasonable solution to integration and messaging problems in this
field, especially when it comes to microservices that are exposed by
host systems and connected via MOMs. A combination of REST
request/response and pub/sub messaging may be used to accom
plish the business need (Figure 5-5).
44
Asynchronous Messaging
45
CHAPTER 6
Conclusion
48
Chapter 6: Conclusion
APPENDIX A
Architecture != Implementation
Approaches to architectural design do not contain an implicit
method for implementation. This is also true for microservices,
although the service contracts in a microservices-based architecture
allow for a flexible decision about the underlying implementation. It
doesnt even have to be on one platform or language.
If you are grounded in Java EE, youve already seen some recom
mendations and platform-specific thoughts for working with micro
49
services. The basic metric used to compile this short list was that
Java is the most commonly used programming language in todays
enterprises. To keep this a little more to the point, the following
products and technologies will give you an overview of Java run
times that arent Java EE application server-based for your microser
vices stack.
Vert.x
Vert.x is an asynchronous, nonblocking framework for development
of applications of all kinds. Although it has been mainly discussed in
the context of web applications, it has far broader appeal than purely
the Web.
Unlike traditional stacks, its been designed from day one to be scal
able and compatible with microservices architectures, so its almost
completely nonblocking when it comes to OS threads. This is the
most critical component for microservices-based applications,
which naturally have to handle a lot of concurrent processing of
messages or events while holding up a lot of connections. Vert.x also
supports the usage of a variety of different languages (e.g., Java
Script, Ruby, and Groovy).
This type of functionality can be achieved without being a container
or an invasive framework. You can use Vert.x inside your applica
tions and integrate with already existing frameworks such as Spring.
The nonblocking nature and reactive programing model speeds
along the adoption of basic microservices design principles and rec
ommendations, making this framework easier to use than other
platforms. Its also minimally invasive and can be integrated with
existing applications, in turn offering an interesting migration path
for brownfield developments.
WildFly Swarm
WildFly Swarm is a sidecar project of WildFly 9.x to enable decon
structing the WildFly Java EE application server to your needs.
WildFly Swarm allows developers to package just enough of its
modules back together with their application to create a selfcontained executable JAR.
The typical application development model for a Java EE application
is to create an EAR or WAR archive and deploy it to an application
server. All the required Java EE dependencies are already available to
50
51
Dropwizard
Dropwizard is a Java framework for developing ops-friendly, highperformance, RESTful web services. It pulls together well-known,
stable, mature libraries from the Java ecosystem (e.g., Jetty, Jersey,
and Jackson) into a fat JAR. Dropwizard has out-of-the-box sup
port for configuration, application metrics, logging, operational
tools, and more. The individual technologies are wired together
with the help of various interfaces and annotations that can be
viewed as the glue in between. This leaves the user with having to
know the individual technologies first, plus the wiring in between
them. So, there is a learning curve involved, but not a steep one.
53
54
APPENDIX B
Further Resources
55
56