Divide and conquer—the most critical Roman strategy—works efficiently in several areas. The context to take any giant issue and break it down into small pieces to make the job easier to understand, making it scalable and agile, and allowing more people to work on it to deliver faster. The microservice was born from this principle. The microservices architecture is a variant of the service-oriented architecture style that structures an application as a collection of loosely coupled services. In a microservices architecture, services are fine-grained, and protocols are lightweight. In Java, there are several frameworks (including Spring Boot, which we explored in a previous post). Today, let’s walk through the Java/JakartaEE side and talk about the Eclipse MicroProfile.
The goal of the Eclipse MicroProfile initiative: to optimize Enterprise Java for the microservices architecture. It’s based on a subset of JakartaEE APIs, so we can build MicroProfile applications the same way we build JakartaEE ones.
MicroProfiles define standard APIs for building microservices and deliver portable applications across multiple MicroProfile runtimes.
Show me the code
Ok, let's start talking code. Not to digress (you’ll see my rationale in the next paragraph!), but in August, I'll be starting a fantastic trip around Latin America to talk about Java, cloud computing, NoSQL, and I’ll be joining the Oracle Groundbreakers Tour LATAM 2019:
- Argentina – August 6
- Brazil/SP – August 9
- Ecuador – August 13
- Colombia – August 15-16
- Panama – August 19
- Costa Rica – August 21
- Mexico – August 23
- Guatemala – August 26
To celebrate, let’s create a microservice with an Eclipse MicroProfile that manages countries for the Latin America trip. The first step in the Maven project is to set up the libraries needed for the project.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sh.platform.start</groupId>
<artifactId>thorntail-jpa</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>war</packaging>
<properties>
<version.thorntail>2.4.0.Final</version.thorntail>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<failOnMissingWebXml>false</failOnMissingWebXml>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>io.thorntail</groupId>
<artifactId>bom-all</artifactId>
<version>${version.thorntail}</version>
<scope>import</scope>
<type>pom</type>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>io.thorntail</groupId>
<artifactId>microprofile</artifactId>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>5.4.3.Final</version>
</dependency>
<dependency>
<groupId>sh.platform</groupId>
<artifactId>config</artifactId>
<version>2.2.1</version>
</dependency>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>9.1-901-1.jdbc4</version>
</dependency>
</dependencies>
<build>
<finalName>country</finalName>
<plugins>
<plugin>
<groupId>io.thorntail</groupId>
<artifactId>thorntail-maven-plugin</artifactId>
<version>${version.thorntail}</version>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>oss.sonatype.org-snapshot</id>
<url>http://oss.sonatype.org/content/repositories/snapshots</url>
<releases>
<enabled>false</enabled>
</releases>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
</repositories>
</project>
There’s no specification that covers the storage technology on the Eclipse MicroProfile, but once the MicroProfile has synergy with Jakarta EE, it will use a Java Persistence API (JPA).
JPA entity classes are user-defined classes whose instances can be stored in an annotation-driven database. There are annotations such as @Entity, @Table, @Id, and @Column that make the object-to-database transition smoother than doing it manually, helping to speed development.
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import javax.validation.constraints.NotBlank;
import java.util.Objects;
@Entity
@Table(name = "country")
public class Country {
@Id
@GeneratedValue
private Integer id;
@Column
@NotBlank
private String name;
@Column
private String city;
@Column
private String twitter;
@Column
private String link;
}
After defining the model, let's enable the JPA and CDI technologies. In the root of the code, we'll create two files in the folder src/main/resources/META-INF
Bean.xml. The bean.xml file will enable the CDI (Contexts and Dependency Injection), which is a standard dependency injection framework on Jakarta EE. It enables us to manage the lifecycle of stateful components via domain-specific lifecycle contexts and inject components (services) into client objects in a type-safe way.
<?xml version="1.0"?>
<beans xmlns="http://xmlns.jcp.org/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/beans_2_0.xsd"
bean-discovery-mode="all"
version="2.0">
</beans>
Persistence.xml. The persistence.xml is the central piece of configuration. That makes it one of the most important files of your persistence layer. It defines the JPA provider, the driver, user, password, and so on. For this sample code, we'll use PostgreSQL, but don't worry about the blank configuration; it will be defined in real time using Platform Config Reader.
<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_1.xsd"
version="2.1">
<persistence-unit name="jpa-example">
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
<class>sh.platform.template.Country</class>
<properties>
<property name="javax.persistence.jdbc.url" value="" />
<property name="javax.persistence.jdbc.driver" value="" />
<property name="javax.persistence.jdbc.user" value="" />
<property name="javax.persistence.jdbc.password" value="" />
<property name="hibernate.default_schema" value="public" />
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQL94Dialect"/>
<property name="hibernate.connection.autocommit" value="true" />
<property name="hibernate.hbm2ddl.auto" value="update"/>
<property name="show_sql" value="update"/>
</properties>
</persistence-unit>
</persistence>
Our next step is to combine the JPA and CDI. In brief, this combination will make the EntityManagerFactory eligible through all applications using the Inject annotation.
import sh.platform.config.Config;
import sh.platform.config.JPA;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
@ApplicationScoped
class EntityManagerConfiguration {
private EntityManagerFactory entityManagerFactory;
private EntityManager entityManager;
@PostConstruct
void setUp() {
Config config = new Config();
final JPA credential = config.getCredential("postgresql", JPA::new);
entityManagerFactory = credential.getPostgreSQL("jpa-example");
entityManager = entityManagerFactory.createEntityManager();
}
@Produces
@ApplicationScoped
EntityManagerFactory getEntityManagerFactory() {
return entityManagerFactory;
}
@Produces
@ApplicationScoped
EntityManager getEntityManager() {
return entityManager;
}
void close(@Disposes EntityManagerFactory entityManagerFactory) {
entityManagerFactory.close();
}
void close(@Disposes EntityManager entityManager) {
entityManager.close();
}
}
The CDI has impressive features, including the scope. Therefore, we can define an EntityManager to be used in the whole application, and when the application shuts down, it will close the containers one by one.
import javax.interceptor.InterceptorBinding;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import static java.lang.annotation.ElementType.METHOD;
import static java.lang.annotation.ElementType.TYPE;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
@Inherited
@InterceptorBinding
@Retention(RUNTIME)
@Target({METHOD, TYPE})
public @interface Transactional {
}
import sh.platform.config.Config;
import sh.platform.config.JPA;
import javax.annotation.PostConstruct;
import javax.enterprise.context.ApplicationScoped;
import javax.enterprise.inject.Disposes;
import javax.enterprise.inject.Produces;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
@ApplicationScoped
class EntityManagerConfiguration {
private EntityManagerFactory entityManagerFactory;
private EntityManager entityManager;
@PostConstruct
void setUp() {
Config config = new Config();
final JPA credential = config.getCredential("postgresql", JPA::new);
entityManagerFactory = credential.getPostgreSQL("jpa-example");
entityManager = entityManagerFactory.createEntityManager();
}
@Produces
@ApplicationScoped
EntityManagerFactory getEntityManagerFactory() {
return entityManagerFactory;
}
@Produces
@ApplicationScoped
EntityManager getEntityManager() {
return entityManager;
}
void close(@Disposes EntityManagerFactory entityManagerFactory) {
entityManagerFactory.close();
}
void close(@Disposes EntityManager entityManager) {
entityManager.close();
}
}
The Country entity class and the database integration are now ready. The next step: the service layer with the CountryService class, which is the layer that integrates the database with the business. The CountryService class will do the essential CRUD operations on PostgreSQL to handle the transaction we need for the transactional annotation. It’s easy!
import javax.enterprise.context.ApplicationScoped;
import javax.inject.Inject;
import javax.persistence.EntityManager;
import java.util.List;
import java.util.Optional;
@ApplicationScoped
public class CountryService {
@Inject
private EntityManager entityManager;
@Transactional
public Country insert(Country country) {
entityManager.persist(country);
return country;
}
@Transactional
public void update(Country country) {
entityManager.persist(country);
}
@Transactional
public void delete(Integer id) {
find(id).ifPresent(c -> entityManager.remove(c));
}
public Optional<Country> find(Integer id) {
return Optional.ofNullable(entityManager.find(Country.class, id));
}
public List<Country> findAll() {
String query = "select c from Country c";
return entityManager.createQuery(query).getResultList();
}
}
The last piece of Java code is the rest integration, where we’re going to use a JAX-RS that belongs to both Eclipse MicroProfile and Jakarta EE. On JAX-RS, all HTTP methods and paths are annotation-driven, using intuitive annotations such as @GET, @PUT, or @DELETE. We can now do the integration in a more relaxed way, saying “juguetón!” (“playful” in Spanish).
import javax.enterprise.context.RequestScoped;
import javax.inject.Inject;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.PUT;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.MediaType;
import javax.ws.rs.core.Response;
import java.util.List;
import java.util.Optional;
import static javax.ws.rs.core.Response.Status.NO_CONTENT;
import static javax.ws.rs.core.Response.status;
@Path("countries")
@RequestScoped
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_JSON)
public class CountryResource {
@Inject
private CountryService countryService;
@GET
public List<Country> doGet() {
return countryService.findAll();
}
@GET
@Path("{id}")
public Country findById(@PathParam("id") Integer id) {
final Optional<Country> conference = countryService.find(id);
return conference.orElseThrow(this::notFound);
}
@PUT
@Path("{id}")
public Country update(@PathParam("id") Integer id, Country countryUpdated) {
final Optional<Country> optional = countryService.find(id);
final Country country = optional.orElseThrow(() -> notFound());
country.update(countryUpdated);
countryService.update(country);
return country;
}
@DELETE
@Path("{id}")
public Response remove(@PathParam("id") Integer id) {
countryService.delete(id);
return status(NO_CONTENT).build();
}
@POST
public Country insert(Country country) {
return countryService.insert(country);
}
private WebApplicationException notFound() {
return new WebApplicationException(Response.Status.NOT_FOUND);
}
}
The last class enables the JAX-RS and defines the root URL for the services.
import javax.ws.rs.ApplicationPath;
import javax.ws.rs.core.Application;
@ApplicationPath("")
public class ApplicationConfig extends Application {
}
Platform.sh structure
The Java application is now ready to go! The next step is to set up the Platform.sh files required to manage and deploy the application. In our first Java post, we took a deep dive into these three files:
- One Router (.platform/routes.yaml). Platform.sh allows you to define the routes.
- Zero or more service containers (.platform/services.yaml). Platform.sh allows you to completely define and configure the topology and services you want to use on your project.
- One or more application containers (.platform.app.yaml). You control your application and the way it will be built and deployed on Platform.sh via a single configuration file.
The file that will change in this post is the service file, so you can define a database, search engine, cache, and so on. In this project, we'll set up PostgreSQL instead of MySQL.
postgresql:
type: postgresql:11
disk: 512
size: S
We'll keep the route file the same as in the first post, but we'll change the platform.app.yaml to run thorntail uberjar.
# This file describes an application. You can have multiple applications
# in the same project.
#
# See https://docs.platform.sh/user_guide/reference/platform-app-yaml.html
# The name of this app. Must be unique within a project.
name: app
# The runtime the application uses.
type: "java:8"
disk: 1024
# The hooks executed at various points in the lifecycle of the application.
hooks:
build: mvn -DskipTests clean package thorntail:package
# The relationships of the application with services or other applications.
#
# The left-hand side is the name of the relationship as it will be exposed
# to the application in the PLATFORM_RELATIONSHIPS variable. The right-hand
# side is in the form `<service name>:<endpoint name>`.
relationships:
postgresql: "postgresql:postgresql"
# The configuration of the app when it is exposed to the web.
web:
commands:
start: java -jar -Xmx512m -Dswarm.http.port=$PORT -Djava.net.preferIPv4Stack=true -Djava.net.preferIPv4Addresses=true target/country-thorntail.jar
The application is now ready, so it’s time to move it to the cloud with Platform.sh using the following sets:
- Create a free trial account.
- Sign up with a new username and password, or login using a current GitHub, Bitbucket, or Google account. If you use a third-party login, you’ll be able to set a password for your Platform.sh account later.
- Select the region of the world where you want your site to live.
- Select the blank template.
After this wizard, Platform.sh will provision the whole infrastructure to you, as well as provide a Git remote repository. Before access, remember to set the SSH keys. The Platform.sh Git-driven infrastructure will automatically manage everything your application needs to push it to the master remote repository. You only need to write your code—including a few YAML files that specify your desired infrastructure—and then commit it to Git and push.
git remote add platform <platform.sh@gitrepository>
git commit -m "Initial project"
git push -u platform master
The code pushed will create the Java application, a PostgreSQL instance, and, when it's done, will return an IP address to the service. Let's test the application. To test a REST application, any HTTP client is suitable.
curl -X POST -H "Content-Type: application/json" -d '{"name":"JConfColombia","city":"Medellín","twitter":"@MedellinJug","link":"http://www.jconfcolombia.org/2019/index.html"}' https://<service.ip>/countries
curl https://<service.ip>/countries
#result output here
And just like that, we’ve created our first Eclipse MicroProfile application at Platform.sh! But is there any way to make this process easier?
New Eclipse MicroProfile templates have arrived
We're proud to announce that four new Java templates have arrived, and these new templates are all Eclipse MicroProfile providers:
Thorntail offers an innovative approach to packaging and running Java EE applications by packaging them with just enough of the server runtime to "java -jar" your application. It’s MicroProfile compatible, too. And it’s all much, much cooler than that.
Payara Micro is the open-source, lightweight, middleware platform of choice for containerized Java EE (Jakarta EE) microservices deployments. Less than 70MB in size, Payara Micro requires no installation or configuration. And there’s no need for code rewrites, so you can build and deploy a fully working app within minutes.
https://www.payara.fish/software/payara-server/payara-micro/
Develop microservices with Java EE/Jakarta EE technologies and extend them with Node.js, Go and other languages. Migrate existing Java EE applications to microservices and cloud-native architecture.
Apache TomEE is the Eclipse MicroProfile flavor that uses several Apache Project flavors, including Apache Tomcat and Apache OpenWebBeans.
In this post, we took a dive into the fantastic world of Eclipse MicroProfiles. We defined the concept of microservices and how to integrate them with JPA. There are many more combinations coming to our Java stack to make Java developers’ lives easier using the latest generation of PaaS. If you’re in any of the cities I’ll be visiting in my upcoming tour, please feel free to contact me on Twitter; it'll be amazing to get together and talk about Java while drinking something tropical from coconuts. Hasta la vista!!!