Book Image

Java EE 8 High Performance

By : Romain Manni-Bucau
Book Image

Java EE 8 High Performance

By: Romain Manni-Bucau

Overview of this book

The ease with which we write applications has been increasing, but with this comes the need to address their performance. A balancing act between easily implementing complex applications and keeping their performance optimal is a present-day need. In this book, we explore how to achieve this crucial balance while developing and deploying applications with Java EE 8. The book starts by analyzing various Java EE specifications to identify those potentially affecting performance adversely. Then, we move on to monitoring techniques that enable us to identify performance bottlenecks and optimize performance metrics. Next, we look at techniques that help us achieve high performance: memory optimization, concurrency, multi-threading, scaling, and caching. We also look at fault tolerance solutions and the importance of logging. Lastly, you will learn to benchmark your application and also implement solutions for continuous performance evaluation. By the end of the book, you will have gained insights into various techniques and solutions that will help create high-performance applications in the Java EE 8 environment.
Table of Contents (12 chapters)

Application project highlights

To be able to create and run this application, we will need to set up a build tool. For this book, it will be Apache Maven; however, Gradle, Ant, or any other alternative will work perfectly as well. We will then identify some key parts of the application code and, finally, we will insert some data to ensure that our application is usable before investigating its performance.

The build

The only dependency Java EE requires is the Java EE API:

<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>${javaee-api.version}</version> <!-- 8.0 -->
<scope>provided</scope>
</dependency>
If you prefer, you can indeed register all the individual specifications, but it will require more work to maintain the list with Java EE upgrades. For this reason, the bundle is often preferred.

Here, the point is to ensure that the API is provided, which means it will not be packaged in the deliverable and will inherit from the server API. The server providing the services associated with the API also provides the API with the right supported version and the right defaults matching the built-in implementations.

Since Java EE 6, there are two main flavors of Java EE: the web profile and the full profile. The web profile is a light version, with only half the specifications compared with the full profile, more or less. The web profile supports only web applications and, therefore, war files. Most of this book will work with a web profile server, so we will package our application as war:

<packaging>war</packaging>

Since we need Java 8, don't forget to configure the Java source and target version in the build. It can be done in different ways, but configuring maven-compiler-plugin as follows is an efficient one:

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>

The persistence layer

Our data model will be simple: a quote will be linked to a customer. This means that a customer can see a set of quotes, and quotes can be seen by a set of customers. In terms of use cases, we want to be able to monetize our API and make the customers pay to access some quote prices. To do so, we will need a sort of whitelist of quotes per customer.

JPA uses a descriptor called persistence.xml, placed in the META-INF repository of resources (or WEB-INF), which defines how EntityManager, which is a class that allows the manipulation of our model, will be instantiated. Here is what it looks like for our application:

<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="
http://xmlns.jcp.org/xml/ns/persistence
http://xmlns.jcp.org/xml/ns/persistence/persistence_2_2.xsd"
version="2.2">
<persistence-unit name="quote">
<class>com.github.rmannibucau.quote.manager.model.Customer</class>
<class>com.github.rmannibucau.quote.manager.model.Quote</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<properties>
<property name="avax.persistence.schema
-generation.database.action"
value="create"/>
</properties>
</persistence-unit>
</persistence>

The link between the database and the Java code is done through entities. An entity is a plain old java object (POJO) that is decorated with the javax.persistence annotations. They mainly define the mapping between the database and the Java model. For instance, @Id marks a Java field that must match the database identifier.

Here is an example of our Quote entity:

@Entity
public class Quote {
@Id
@GeneratedValue
private long id;

private String name;

private double value;

@ManyToMany
private Set<Customer> customers;

// getters/setters
}

This simple model implicitly defines a QUOTE table with three columns, ID, NAME, and VALUE (the casing can depend on the database), and a table to manage the relationship with the CUSTOMER table, which is named QUOTE_CUSTOMER by default.

In the same spirit, our Customer entity just defines an identifier and name as columns and also the reverse relationship to the Quote entity:

@Entity
public class Customer {
@Id
@GeneratedValue
private long id;

private String name;

@ManyToMany(mappedBy = "customers")
private Set<Quote> quotes;

// getters/setters
}

What is important here is to notice the relationships in the model. We will deal with this later on.

The service layer

The goal of the book being to discuss the performance and not how to write a Java EE application, we will not detail the whole service layer here. However, to ensure a common knowledge of what we are dealing with, we will illustrate the code with one service.

We are using JTA 1.2 with JPA 2.2 to establish a link between our database and the Java model. The QuoteService bean, responsible for managing the Quote persistence, can therefore look like the following:

@Transactional
@ApplicationScoped
public class QuoteService {
@PersistenceContext
private EntityManager entityManager;

public Optional<Quote> findByName(final String name) {
return entityManager.createQuery("select q from Quote q where
q.name = :name"
, Quote.class)
.setParameter("name", name)
.getResultStream()
.findFirst();
}

public Optional<Quote> findById(final long id) {
return Optional.ofNullable(entityManager.find(Quote.class, id));
}

public long countAll() {
return entityManager.createQuery("select count(q) from Quote
q"
, Number.class)
.getSingleResult()
.longValue();
}

public Quote create(final Quote newQuote) {
entityManager.persist(newQuote);
entityManager.flush();
return newQuote;
}

// ... other methods based on the same model
}

JPA may or may not be used in a transactional context, depending on the kind of operation you do. When you read data, you can often do it without any transaction until you need some lazy loading. However, when you write data (insert/update/delete entities), JPA requires a running transaction to be able to execute the action. This is to ensure consistency of data but also has some implications on the code. To respect that requirement, and have an active transaction, we use @Transactional on methods instead of relying on Enterprise Java Bean 3.2 (EJB 3.2), so we can reuse the power of CDI (@ApplicationScoped, for instance, which will avoid creating a new instance per injection).

Our finders are very simple and directly use the EntityManager API. The only new thing Java 8 brings us in this code is the ability to wrap the result with Optional which offers a programmatic way to deal with the presence or absense of the entity instead of relying on a null check. Concretely, the caller can use our finder this way:

final int quoteCount = getCustomer().getCountFor("myquote");
final double quotesPrice = quoteService.findByName("myquote")
.map(quote -> quote.getValue() * quoteCount)
.orElse(0);

This kind of code hides the conditional branches behind a fluent API, which makes it more expressive and readable, while the lambdas stay small enough.

Finally, we used inline queries in this code, not static ones like in the @NamedQuery API.

The JAX-RS layer

If we step back one second and think about which stopover the application will execute, we can identify a few of them:

  • HTTP communication handling
  • Payload (un)marshalling
  • Routing
  • Service invocation

Because of the separation of concern principles, or simply for technical constraints between layers, it is very common to use a Data Transfer Object between the JAX-RS/front layer and the CDI/business layer. Of course, this statement can be applied to the business sub-layers as well, but in the case of this book, we will just do it in the JAX-RS layer. To make it obvious in the book, we will prefix the JAX-RS model with Json. Check out the following code snippet:

@JsonbPropertyOrder({"id", "name", "customerCount"})
public class JsonQuote {
private long id;
private String name;
private double value;

@JsonbProperty("customer_count")
private long customerCount;

// getters/setters
}

In this context, the front layer role is to delegate most of the logic to the service layer and convert the business model to the front model (it can almost be seen as a Java to JavaScript conversion for a lot of modern applications):

@Path("quote")
@RequestScoped
public class QuoteResource {
@Inject
private QuoteService quoteService;

@GET
@Path("{id}")
public JsonQuote findById(@PathParam("id") final long id) {
return quoteService.findById(id) // delegation to the business
layer
.map(quote -> { // the model conversion
final JsonQuote json = new JsonQuote();
json.setId(quote.getId());
json.setName(quote.getName());
json.setValue(quote.getValue());

json.setCustomerCount(ofNullable(quote.getCustomers())
.map(Collection::size).orElse(0));
return json;
})
.orElseThrow(() -> new
WebApplicationException(Response.Status.NO_CONTENT));
}

// other methods
}
We set the JAX-RS @ApplicationPath to /api to ensure that our endpoints are deployed under the /api subcontext.

The WebSocket layer

Why use JAX-RS and WebSocket? Don't they serve the same purpose? Not exactly, in fact, it is becoming more and more common to use both in the same application even if WebSocket is still a bit recent.

JAX-RS (and, more generally, HTTP/1 and the brand new HTTP/2) is generally web application oriented. Understand that it is often used for applications with a user interface (which needs to be compatible with all browsers). It is also commonly used in environments where you cannot assume much about the network setup. More particularly, in environments where you cannot assume the network setup, the proxies will let WebSocket connections work properly (either preventing them completely or disconnecting them too early). The last common case where HTTP-based solutions make a lot of sense is to try to target a market where clients can be developed in any language (Java, Python, Ruby, Go, Node.js, and so on). The fact that the technology is today spreading all over the world and works well with stateless connections, makes it easier to get started with, and it is therefore more accessible than WebSocket, which requires some care from client developers.

However, WebSocket will fit cases where you have higher performance or reactivity constraints, a state to maintain in order to handle the business use case, or you simply want to push the information from the server without requiring a client operation (such as polling).

When you start using a connected protocol such as WebSocket, the first thing to define is your own communication protocol: the format of the message you send/receive and the order of the messages (if needed).

Our WebSocket layer will be responsible for enabling a client to quickly access the quote prices. Therefore, we will react on a client's request (it will contain the name of the quote that we want to get the price for) and we will respond with two pieces of information: whether we found the quote and the current price, if existing.

Then, you need to pick a format to prepare the content sent through the WebSocket over the wire. Here, the choice is often guided by a trade-off between the client (consumers of the service), the requirements, the performances, and the ease of implementation. In our case, we will consider that our clients can be written in Java as well as in JavaScript. That is why we will use JSON.

To summarize the protocol, here is a full communication round-trip, as shown in the following diagram:

The communication protocol is based on a single message type in our case, so a full client/server communication looks like these steps:

  1. The client will connect to the server.
  2. The client will request the price of a quote N times, based on its symbol (name/identifier).
  3. Assuming there is no I/O error or timeout, the client will trigger a disconnect, which will end the communication.

In terms of code, we need multiple bricks of Java EE and we need the following to put them together:

  • The WebSocket API, obviously
  • JSON-B (we could use JSON-P, but it is less friendly) for the Java to JSON conversion
  • CDI, to link the WebSocket to the business layer

To start easy, we can modelize our payloads. Our request has only one name attribute, so JSON-B allows us to define it this way:

public class ValueRequest {
private String name;

// getter/setter
}

On the other side (that is, the response), we have to return a value attribute with the price of the quote and a found Boolean marking value as filled or not. Here again, JSON-B allows us to do a direct mapping of this model with a plain POJO:

public static class ValueResponse {
private double value;
private boolean found;

// getters/setters
}

Now, we need to ensure that the WebSocket will be able to deserialize and serialize these objects as required. The specification defines Encoder and Decoder APIs for this purpose. Since we will back our implementation by JSON-B, we can directly implement it using the (I/O) stream flavors of these APIs (called TextStream). Actually, before doing so, we need to get a Jsonb instance. Considering that we have already created one and made it available in CDI, we can then simply inject the instance in our coders:

@Dependent
public class JsonEncoder implements Encoder.TextStream<Object> {
@Inject
private Jsonb jsonb;

@Override
public void encode(final Object o, final Writer writer) throws EncodeException, IOException {
jsonb.toJson(o, writer);
}

// other methods are no-op methods
}

The decoding side is now fast to develop, thanks to the JSON-B API, which fits this usage very well with its fromJson() API. We will just note that this side is specific to ValueRequest, since we need to specify the type to instantiate it (compared with the encoding side, which can determine it dynamically):

@Dependent
public class RequestDecoder implements Decoder.TextStream<ValueRequest> {
@Inject
private Jsonb jsonb;

@Override
public ValueRequest decode(final Reader reader) throws DecodeException, IOException {
return jsonb.fromJson(reader, ValueRequest.class);
}

// other methods are no-op methods
}

Now that we have a way to handle our messages, we need to bind our WebSocket endpoint and implement the @OnMessage method to find the price and send it back to the client relying on our business layer. In terms of implementation, we will react to a ValueRequest message, try to find the corresponding quote, fill the response payload, and send it back to the client:

@Dependent
@ServerEndpoint(
value = "/quote",
decoders = RequestDecoder.class,
encoders = JsonEncoder.class)
public class DirectQuoteSocket {
@Inject
private QuoteService quoteService;

@OnMessage
public void onMessage(final Session session, final ValueRequest request) {
final Optional<Quote> quote = quoteService.findByName(request.getName());
final ValueResponse response = new ValueResponse();
if (quote.isPresent()) {
response.setFound(true);
response.setValue(quote.get().getValue()); // false
}

if (session.isOpen()) {
try {
session.getBasicRemote().sendObject(response);
}
catch (final EncodeException | IOException e) {
throw new IllegalArgumentException(e);
}
}
}
}

Provision some data

At this point, we have our application. Now, we need to ensure that it has some data and, then, move on to evaluating its performance.

Without delving too much into the business details, we will implement the provisioning in two passes:

  • Find all the symbols to update
  • For each symbol found, update the price in the database

To do so, we will use two public webservices:

The first one is a plain CSV file, which we will parse without any library to keep things simple and because the format does not require special escaping/parsing. The second one will return a JSON payload, which we can read directly using the JAX-RS 2.1 client API.

Here is how we can retrieve our data:

private String[] getSymbols(final Client client) {
try (final BufferedReader stream = new BufferedReader(
new InputStreamReader(
client.target(symbolIndex)
.request(APPLICATION_OCTET_STREAM_TYPE)
.get(InputStream.class),
StandardCharsets.UTF_8))) {

return stream.lines().skip(2/*comment+header*/)
.map(line -> line.split(","))
.filter(columns -> columns.length > 2 && !columns[1].isEmpty())
.map(columns -> columns[1])
.toArray(String[]::new);
} catch (final IOException e) {
throw new IllegalArgumentException("Can't connect to find symbols", e);
}
}

Note that we directly read a buffered reader backed by the HTTP response stream. Once the symbols are extracted, we can simply iterate over them and request the price of each quote:

try {
final Data data = client.target(financialData)
.resolveTemplate("symbol", symbol)
.request(APPLICATION_JSON_TYPE)
.get(Data.class);

if (!data.hasPrice()) {
LOGGER.warning("Can't retrieve '" + symbol + "'");
return;
}

final double value = data.getQuoteSummary().getResult().get(0)
.getFinancialData().getCurrentPrice().getRaw();

final Quote quote = quoteService.mutate(symbol, quoteOrEmpty ->
quoteOrEmpty.map(q -> {
q.setValue(value);
return q;
}).orElseGet(() -> {
final Quote newQuote = new Quote();
newQuote.setName(symbol);
newQuote.setValue(value);
quoteService.create(newQuote);
return newQuote;
}));

LOGGER.info("Updated quote '" + quote.getName() + "'");
} catch (final WebApplicationException error) {
LOGGER.info("Error getting '" + symbol + "': " + error.getMessage()
+ " (HTTP " + (error.getResponse() == null ? "-" :
error.getResponse().getStatus()) + ")");
}

This piece of code sends an HTTP request, thanks to the JAX-RS client API and JSON-B, which unmarshalls a data model. Then, we use the obtained data to update our database quote if it already exists; otherwise, we use the data to create the database quote.

The code now needs to be wired to be executed. We have multiple options here:

  • Execute it at startup
  • Execute it regularly
  • Execute it when an endpoint is called

In the context of this book, we will use the first two options. The startup is common for us, even if it is not as realistic, because once started, we will get some data. The second option will use an EJB 3.2 @Schedule, which will run hourly.

The startup implementation requires a simple CDI bean with a method calling the previous logic when @ApplicationScoped is created (at startup):

@ApplicationScoped
public class InitialProvisioning {
@Inject
private ProvisioningService provisioningService;

public void onStart(@Observes @Initialized(ApplicationScoped.class) final ServletContext context) {
provisioningService.refresh();
}
}

The scheduling is done, thanks to the Enterprise Java Bean @Schedule API, which allows us, in one annotation, to request the container to regularly execute a method:

@Singleton
@Lock(WRITE)
public class DataRefresher {
@Inject
private ProvisioningService provisioningService;

@Schedule(hour = "*", persistent = false, info = "refresh-quotes")
public void refresh() {
provisioningService.refresh();
}
}
In a real application, you will probably want to configure the refresh frequency and use the TimerService API to trigger the execution based on the application configuration. In the same spirit, the startup execution could be ignored based on the configuration in order to have a faster startup.