AWS: Pushing Jakarta EE Full Platform Applications to the Cloud
In this article, readers will learn how to deploy more complex Jakarta EE applications as serverless services with AWS Fargate.
Join the DZone community and get the full member experience.
Join For FreeIn a previous blog post, I demonstrated how to deploy Jakarta EE applications as serverless services with AWS Fargate. The Jakarta EE application I used as an example in the previously mentioned post was a basic one. Now, it's time to go further and see what it takes to do the same thing with more complex Jakarta EE applications involving a wide variety of components like RESTful, CDI, Enterprise Beans, and Server Pages.
Jakarta EE Full Platform is the superset of what has been commonly called "enterprise Java." While not officially coined as a Jakarta EE profile, such as the Core Profile or the Web Profile, the Full Platform includes the whole bunch of the Jakarta EE specifications implementations, from the most essential ones, like Servlet, to the most uncharted ones, like RMI or SAAJ.
In this article, we'll be using Jakarta EE 10 and its implementation by WildFly 27.0.1.Final release.
The Project
The project used in order to illustrate this blog ticket may be found here. It consists of a Maven-based Jakarta EE 10 sample application for items management involving the following components:
- a RESTful 3.1 endpoint that aims at CRUD-ing items;
- a CDI 4.0 component on the domain layer;
- an Enterprise Bean Lite 4.0 component implementing the facade design pattern;
- a Faces 4.0 front-end providing an easy way to test the application.
I tried to carefully choose these components in order to have as much as a reasonable variety of Jakarta EE implementations, such that to justify the deployment of a Full Platform server like WildFly. And while some analysts still think that WildFly, like all the existent Jakarta EE implementations, is a heavy platform, I'll demonstrate how fast and easy it can be deployed on AWS in a Fargate serverless context.
But, before going into infrastructure and deployment considerations, let's first have a detailed analysis of each project's layer.
The Faces 4.0 Front End
Our front-end layer consists of an XHTML-based view using Facelets. As the reader might know, Jakarta EE Faces and JSF (Java Server Faces) were historically used to support two view technologies: JSP (Java Server Pages) and Facelets. The new 4.0 release is deprecating the JSP support, and it adds a new programmatic one based on the jakarta.faces.view.facelets.Facelet
interface. While this new API might be practical for developers who prefer having a pure Java view definition, it always has been considered that a clean software architecture has to separate the business logic from the visual presentation, including using a different declarative notation, like Facelets. That's for this reason that, in this example, I didn't give way to the temptation of using this new Faces API, but I kept using the old good Facelets notation and one of its most useful features: the templating.
The listing below shows the file template.xhtml and the way that it defines our Facelets template:
<html xmlns:ui="jakarta.faces.facelets" xmlns:h="jakarta.faces.html">
<h:head>
<h:outputStylesheet name="style.css"/>
</h:head>
<h:body>
<div class="page">
<div class="header">
<ui:include src="header.xhtml"/>
</div>
<div class="content">
<ui:insert name="content">Default Content</ui:insert>
</div>
<div class="footer">
<ui:include src="footer.xhtml"/>
</div>
</div>
</h:body>
</html>
As we can see, our HTML page is divided into three regions, a header, content, and a footer. Each region is defined in a separate .xhtml file. Don't hesitate to open the files named header.xhtml
and, respectively, footer.xhtml
located in the src/main/webapp/resources/templates
directory and to see how each one of these regions are defined.
The region named content
is defined in the index.xhtml
file, located in the src/main/webapp
directory.
<html
xmlns:ui="jakarta.faces.facelets"
xmlns:f="jakarta.faces.core"
xmlns:h="jakarta.faces.html"
xmlns:mc="jakarta.faces.composite/mycomponents">
<body>
<ui:composition template="/templates/template.xhtml">
<ui:define name="content">
<mc:item key = "#{item.key}" value ="#{item.value}" actionListener="#{itemManager.save()}"/>
<h:form id="items">
<h:dataTable value="#{itemManager.itemList}" var="it" styleClass="table" headerClass="table-header" rowClasses="table-odd-row,table-even-row">
<h:column>
<f:facet name="header">Key</f:facet>
<h:outputText value="#{it.key}"/>
</h:column>
<h:column>
<f:facet name="header">Value</f:facet>
<h:outputText value="#{it.value}"/>
</h:column>
<h:column>
<f:facet name="header">Delete</f:facet>
<h:commandButton actionListener="#{itemManager.delete(it)}" styleClass="buttons" value="Delete"/>
</h:column>
</h:dataTable>
</h:form>
</ui:define>
</ui:composition>
</body>
</html>
The ui:composition
and ui:define
tags in the listing above state that here we're defining the element named content
in the template.xhtml
file. The namespace jakarta.faces.composite
is a new feature of the JSF 2.3, included in Faces 4.0 as well, and aims at defining composite components. The tag mc:item
in the listing below references such a composite component, while its definition is shown in the file item.xhtml
located in the src/main/webapp/resources/mycomponents
directory.
<html xmlns:cc="jakarta.faces.composite" xmlns:h="jakarta.faces.html">
<cc:interface>
<cc:attribute name="key"/>
<cc:attribute name="value"/>
<cc:attribute name="actionListener" method-signature="void action(javax.faces.event.Event)" targets="form:saveButton"/>
</cc:interface>
<cc:implementation>
<h:form id="form">
<h:panelGrid columns="2">
<h:outputText value="Key:" for="inputTextKey"/>
<h:inputText value="#{cc.attrs.key}" id="inputTextKey"/>
<h:outputText value="Value:" for="inputTextValue"/>
<h:inputText value="#{cc.attrs.value}" id="inputTextValue"/>
</h:panelGrid>
<h:commandButton id="saveButton" value="Save"/>
<h:messages errorStyle="color: red" infoStyle="color: green" globalOnly="true" />
</h:form>
</cc:implementation>
</html>
Our composite component here above defines a form consisting of a grid with two columns and a command button. This simple interface allows us to CRUD items.
At this point of the presentation, I have to apologize to the readers for the poor styling of the visual part of the project. As a matter of fact, I have to admit that I completely lack any graphical or visual designing skills. However, I tried to style them as much as I could the presentation and the maximum I could come to is in the file style.css
located in the src/main/webapp/resource
directory. Please don't hesitate to adapt and customize this file such that the visual presentation of the front end becomes more attractive.
The CDI 4.0 Component Layer
The listing below shows the class ItemManager which instance is driving the items CRUD operations:
@Model
public class ItemManager
{
@Inject
private ItemFacade itemFacade;
@Produces
@Named
private Item item = new Item();
@Produces
@Named
public List<Item> getItemList()
{
return itemFacade.getItemList();
}
public void save()
{
itemFacade.addToList(item);
FacesMessage facesMsg = new FacesMessage(FacesMessage.SEVERITY_INFO, "Added item " + item.getKey(), null);
FacesContext.getCurrentInstance().addMessage(null, facesMsg);
item = new Item();
}
public void delete(Item item)
{
itemFacade.removeFromList(item);
}
public boolean isFull()
{
return (itemFacade.getItemList().size() > 0);
}
}
Notice how annotations like @Produces
and @Named
are used in the listing above in order to produce new bean instances and, respectively, to export them to EL (Expression Language), such that to be used in the view component in expressions like #{item.key}
and #{item.value}
or #{itemList}
.
The Enterprise Bean 4.0 Component Layer
The ItemManager
CDI bean is injecting an instance of the ItemFacade
, which is the effective service performing the items CRUD operations. This service is implemented as a stateless session Enterprise Bean, as shown by the listing below:
@Singleton
@Named
public class ItemFacade implements Serializable
{
private List<Item> itemList;
public ItemFacade()
{
}
@PostConstruct
public void postConstruct()
{
itemList = new ArrayList<Item>();
}
public List<Item> getItemList()
{
return itemList;
}
public void setItemList(List<Item> itemList)
{
this.itemList = itemList;
}
public int addToList(Item item)
{
itemList.add(item);
return itemList.size();
}
public int addToList(String key, String value)
{
itemList.add(new Item(key, value));
return itemList.size();
}
public int removeFromList(Item item)
{
itemList.remove(item);
return itemList.size();
}
public int removeFromList(int idx)
{
itemList.remove(idx);
return itemList.size();
}
public void removeAll()
{
itemList.clear();
}
}
We could imagine that the facade component would perform some persistence operations like storing the items in a database or sending JMS messages to a message broker. In our simple example, we only store them in a collection.
The RESTful 3.1 Endpoint
This endpoint exposes the operations required for CRUD items, as shown in the table below:
Resource | HTTP Request | Action | Java Method |
---|---|---|---|
/items/list | GET | Get the full list of the currently registered items | public List<Item> getItems() |
/items | POST | Create a new item | public Response createItem() |
/items/{id} | GET | Get the item identified by its key, passed as a path parameter | public Item getItemByPathParam() |
/items | GET | Get the item identified by its key, passed as a query parameter | public Item getItemByQueryId() |
/items/{id} | DELETE | Remove the item identified by its key | public Response removeItem() |
Nothing new here. This is just a very classical RESTful endpoint, as we use to see often.
Testing Locally
Before deploying to the cloud, we need to make sure first that our application works as expected. As it isn't worth deploying it as long as it doesn't work. Our pom.xml file defines several profiles, as follows:
arq-managed
: This profile defines an Arquillian integration test in order to deploy our WAR on a managed WildFly server;arq-remote
: This profile defines an Arquillian integration test in order to deploy our WAR on a managed WildFly server;arq-docker
: This profile defines an Arquillian integration test in order to deploy our WAR on a WildFly server running in a Docker container;docker
: This profile is useful to test the Faces front-end in an interactive way. It doesn't use Arquillian, but it only deploys the WAR on a WildFly application server running in a Docker container, giving you the possibility to use your preferred browser and to perform manual tests;aws
: This profile allows us to execute integration tests without Arquillian once that the application has been deployed on the cloud.
$ mvn -Parq-managed clean package verify
A WildFly server will be downloaded and executed locally, and Arquillian will deploy in it the WAR before executing the integration tests, which should succeed. In the same way, if you want to perform tests in a WildFly server running in a Docker container, you may execute the following command:
$ mvn -Parq-docker clean package verify
If you want to perform manual tests without Arquillian, you need to do the following:
$ mvn clean package docker:build docker:run
This will start a Docker container running a WildFly server, and you will be able to connect to this URL in order to check if your Faces front end works.
Pushing to the Cloud
In a previous blog ticket, I've already investigated the use of the AWS Fargate serverless service as an efficient way to deploy Jakarta EE applications on the cloud. It is this same service that we'll be using here to deploy the WildFly application server, together with our Jakarta EE 10 application. Assuming that copilot is already installed on your box, the only thing to do is to run the following command:
$ copilot init --app duke-app --name duke --type "Load Balanced Web Service" --dockerfile ./DockerfileWithWAR --port 8080 --deploy
The execution of this command might take some time, depending on your bandwidth. Once finished, you'll see a message similar to the following:
- You can access your service at http://duke-Publi-V5NKFZU77FWB-1356331722.eu-west-3.elb.amazonaws.com over the internet.
Now you can connect to this URL and check whether the Faces front-end works as was the case when tested locally. If you prefer to execute integration tests, you can use the aws
profile, as follows:
$ mvn -Paws clean package verify
Don't forget to clean up your environment as soon as you've finished playing:
$ copilot app delete
Again, the execution of this command might take some time.
Enjoy!
Opinions expressed by DZone contributors are their own.
Comments