Database Enabled Microservices with Tomee and MicroProfile

Creating database enabled Microservices with Tomee and MicroProfile is a fairly straightforward task. In this article, I'm going to highlight the key points to consider. So, we'll be looking at code, but this isn't a step-by-step tutorial.

Introduction

First off, what are Tomee and MicroProfile?

TomEE is a Java EE application server that is based on Apache Tomcat. So, if you are familiar with Apache Tomcat, you are automatically familiar with Tomee. MicroProfile is a reduced set of api's that are applicable to Microservices applications, and includes api's such as JAX-RS for web services, CDI for dependency injection amongst many others.

So why are we interested in TomEE and MicroProfile? Together, TomEE and MicroProfile give us a toolkit that allows us to rapidly build production level Microservice application (as well as full stack applications) that we can easily extend and test. Everything we can do in TomEE and MicroProfile can be done in other Java application Servers, but I personally like the tools for MicroServices and the simplicity of TomEE.

OK, that sounds cool. Where do we get started?

Creating a basic project

Maven is probably the most standard build tool in the Java ecosystem (unless you're a Gradle user!). There are many ways to create Maven projects, either manually, or via your favourite IDE. Since we're using MicroProfile though, we can use the MicroProfile Starter. This web page allows us to easily create basic Maven projects that contain everything we need for a MicroProfile application, including selecting an appropriate application server such as TomEE.

MicroProfile Starter

After creating a project with the MicroProfileStarter, we have the basic scaffolding for developing a microservice.

Database Support

To add database support into a microservice, we need to add a database persistence unit and a definition of the database. In Java EE, the persistence unit is standard across many application servers. For TomEE, the persistence unit needs to be placed in src\main\resources\META-INF\persistence.xml and needs to contain:

<persistence version="1.0"
             xmlns="http://java.sun.com/xml/ns/persistence"
             xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd">
  <persistence-unit name="customer-unit">
    <provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>

    <jta-data-source>customerDatasource</jta-data-source>
    <properties>
      <property name="openjpa.jdbc.DBDictionary" value="hsql" />

      <property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema(ForeignKeys=true)" />
    </properties>
  </persistence-unit>
</persistence>

In this code, we can see there is a persistence-unit called customer-unit which references a JTA datasource called customerDatasource. This datasource defines the database connections - we'll see this in a minute. The property openjpa.jdbc.SynchronizeMappings allows the database used for the application to be created and kept in sync with the application code. This is very useful whilst writing the application, but don't let your DBA see you doing this ;)

As well as the persistence unit, there needs to be a database definition called customerDatasource. In TomEE, this can be created within the src\main\resources\WEB-INF\resources.xml file which looks like:

<Resource id="customerDatasource" type="javax.sql.DataSource">
    defaultAutoCommit = true
    jdbcDriver = org.hsqldb.jdbcDriver
    jdbcUrl = jdbc:hsqldb:mem:hsqldb
    jtaManaged = true
    maxActive = 20
    password =
    passwordCipher = PlainText
    userName = sa
</Resource>

From this source, you can see that there is a javax.sql.DataSource called customerDatasource defined - remember I said we'd get to this :) This datasource defines the JDBC connection settings, in this case to an in-memory HSQLDB. There are many other settings that can be used to define a datasource in TomEE, all described here

So far, we've looked at all the plumbing necessary to get a database connection. To perform CRUD operations, we can use standard Java EE entities and JPA. For example, an @Entity could look like:

@Entity
public class Customer {

  @Id @GeneratedValue public Long id;

  @Column public String name;
...

and a persistence layer for this entity could look like:

public class CustomerRepository {

  @PersistenceContext(name = "customer-unit")
  EntityManager em;

  Customer find(Long customerId) {
    return em.find(Customer.class, customerId);
  }

  List<Customer> findAll() {
    return em.createQuery("SELECT c FROM Customer c").getResultList();
  }

  public void save(Customer customer) {
    em.persist(customer);
  }
}

REST Endpoint Support

So far, we've seen how to scaffold a new project with the MicroProfile Starter and how to add database support into a TomEE web service. Let's now take a look at what is involved in setting up some REST endpoints.

For the purpose of this example, let's consider 3 REST endpoints:

GET /data/customer/all - return all the customers

GET /data/customer/<customerId> - return a specific customer based on its id

POST /data/customer - add a new customer.

With Jax-RS, we need to have a class derived from javax.ws.rs.core.Application. This is automatically generated for us by the MicroProfile Starter:

@ApplicationPath("/data")
public class RestjpaRestApplication extends Application {}

The main point of interest here, is the @ApplicationPath parameter which defines the base URL for the endpoints in the microservice - in this case \data.

After defining the base url, resources can be defined and methods exposed for them.

@Path("/customer")
@Stateless
public class CustomerController {

  @Inject CustomerRepository customerRepository;

  @GET
  @Path("/all")
  @Produces("application/json")
  public Response customers() {
    return Response.ok(customerRepository.findAll()).build();
  }

  @GET
  @Path("/{id}")
  @Produces("application/json")
  public Response find(@PathParam("id") Long customerId) {
    Customer customer = customerRepository.find(customerId);

    if (customer != null) {
      return Response.ok(customer).build();
    } else return Response.status(Response.Status.NOT_FOUND).build();
  }

  @POST
  @Produces("application/json")
  public Response create(Customer customer) throws Exception {
    customerRepository.save(customer);

    URI location = new URI("http://localhost:8080/customer/" + customer.getId());
    return Response.created(location).build();
  }
}

In this example, we can see that the CustomerController class is defined as @Stateless which automatically gives us transactions wrapped around each method within the class. We could see this at the repository level, but for simplicity, its added at the resource controller level here.

Next, we see that there is an @Injected repository for accessing the database. Support for CDI is automatically provided via MicroProfile, with the necessary beans.xml file already created by the MicroProfile Starter tool.

Next, we can see 3 methods for returning all customers, returning a specific customer, and creating a new customer. Notice that each of these methods returns a Response object rather than the raw Customer object itself. This is so that we can add different HTTP response codes depending upon our requirements. For example, we can return a HTTP 200 for successfully finding objects, a HTTP 404 when searching for a non-existent customer, or HTTP 201 when successfully creating a customer.

Executing The Application

As we'd expect by now, running the application is very straightforward. The tool set that we've used allows a Uber Jar to be created and executed easily. We can generate a runnable Jar (created within the target folder of the project) simply by executing mvn package, or we can build and run the Jar by executing mvn package tomee:run. Incredibly straightforward.

Summary

In this article, we've seen how to easily create and run database centric Microservice applications using TomEE and MicroProfile. I hope you've found this useful. If you've any questions, please comment below or checkout the sample code on GitHub.

Credits

Photo by Caspar Camille Rubin.

No Comments Yet