Con la popularidad de la arquitectura de microservicios y el desarrollo de aplicaciones nativas de la nube, existe una creciente necesidad de un servidor de aplicaciones rápido y ligero.
En este tutorial introductorio, exploraremos el framework Open Liberty para crear y consumir un servicio web RESTful. También examinaremos algunas de las características esenciales que proporciona.
Open Liberty
Open Liberty es un framework de código abierto para el ecosistema Java que permite desarrollar microservicios utilizando características de las plataformas Eclipse MicroProfile y Jakarta EE.
Es un runtime de Java flexible, rápido y ligero que parece prometedor para el desarrollo de microservicios nativos de la nube.
El framework nos permite configurar solo las funciones que nuestra aplicación necesita, lo que resulta en una menor huella de memoria durante el arranque de la aplicación. Además, se puede implementar en cualquier plataforma en la nube utilizando contenedores como Docker y Kubernetes.
Es compatible con el desarrollo rápido mediante la recarga en vivo del código para una iteración rápida.
Construir y ejecutar
Primero, crearemos un proyecto simple basado en Maven llamado open-liberty y luego agregaremos el último plugin liberty-maven-plugin al pom.xml:
<plugin>
<groupId>io.openliberty.tools</groupId>
<artifactId>liberty-maven-plugin</artifactId>
<version>3.3-M3</version>
</plugin>
O podemos agregar la última dependencia de Maven de openliberty-runtime como alternativa al plugin liberty-maven:
<dependency>
<groupId>io.openliberty</groupId>
<artifactId>openliberty-runtime</artifactId>
<version>20.0.0.1</version>
<type>zip</type>
</dependency>
De manera similar, podemos agregar la última dependencia de Gradle a build.gradle:
dependencies {
libertyRuntime group: 'io.openliberty', name: 'openliberty-runtime', version: '20.0.0.1'
}
Luego, agregaremos las últimas dependencias jakarta.jakartaee-web-api y microperfil de Maven:
<dependency>
<groupId>jakarta.platform</groupId>
<artifactId>jakarta.jakartaee-web-api</artifactId>
<version>8.0.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.eclipse.microprofile</groupId>
<artifactId>microprofile</artifactId>
<version>3.2</version>
<type>pom</type>
<scope>provided</scope>
</dependency>
Luego, agreguemos las propiedades predeterminadas del puerto HTTP al pom.xml:
<properties>
<liberty.var.default.http.port>9080</liberty.var.default.http.port>
<liberty.var.default.https.port>9443</liberty.var.default.https.port>
</properties>
A continuación, crearemos el archivo server.xml en el directorio src/main/liberty /config
:
<server description="servidor Open Liberty de ricardogeek">
<featureManager>
<feature>mpHealth-2.0</feature>
</featureManager>
<webApplication location="open-liberty-app.war" contextRoot="/" />
<httpEndpoint host="*" httpPort="${default.http.port}"
httpsPort="${default.https.port}" id="defaultHttpEndpoint" />
</server>
Aquí, hemos agregado la función mpHealth-2.0 para verificar el estado de la aplicación.
Eso es todo con toda la configuración básica. Ejecutemos el comando Maven para compilar los archivos por primera vez:
mvn clean package
Por último, ejecutemos el servidor utilizando un comando de Maven proporcionado por Liberty:
mvn liberty:dev
¡Voila! Nuestra aplicación se inicia y estará accesible en localhost: 9080:
Además, podemos acceder al estado de la aplicación en localhost:9080/health
:
{"checks":[],"status":"UP"}
El comando liberty: dev inicia el servidor Open Liberty en modalidad de desarrollo, que vuelve a cargar en caliente cualquier cambio realizado en el código o la configuración sin reiniciar el servidor.
De manera similar, el comando liberty: run está disponible para iniciar el servidor en modo de producción.
Además, podemos usar liberty: start-server y liberty: stop-server para iniciar / detener el servidor en segundo plano.
Servlet
Para usar servlets en la aplicación, agregaremos la función servlet-4.0 al server.xml:
<featureManager>
...
<feature>servlet-4.0</feature>
</featureManager>
Agregue la última dependencia de Maven de servlet-4.0 si usa la dependencia de Maven de openliberty-runtime en el pom.xml:
<dependency>
<groupId>io.openliberty.features</groupId>
<artifactId>servlet-4.0</artifactId>
<version>20.0.0.1</version>
<type>esa</type>
</dependency>
Sin embargo, si usamos el complemento liberty-maven-plugin, esto no es necesario.
Luego, crearemos la clase AppServlet ampliando la clase HttpServlet:
@WebServlet(urlPatterns="/app")
public class AppServlet extends HttpServlet {
private static final long serialVersionUID = 1L;
@Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
String htmlOutput = "<html><h2>Hola! Bienvenidos a Open Liberty</h2></html>";
response.getWriter().append(htmlOutput);
}
}
Aquí, hemos agregado la anotación @WebServlet que hará que AppServlet esté disponible en el patrón de URL especificado.
Accedemos al servlet en localhost:9080/app
Crear un servicio web RESTful
Primero, agreguemos la característica jaxrs-2.1 al server.xml:
<featureManager>
<feature>jaxrs-2.1</feature>
</featureManager>
Luego, crearemos la clase ApiApplication, que proporciona puntos finales al servicio web RESTful:
@ApplicationPath("/api")
public class ApiApplication extends Application {
}
Aquí, hemos utilizado la anotación @ApplicationPath para la ruta de la URL.
Luego, creemos la clase Persona que sirve al modelo:
public class Person {
private String usuario;
private String email;
// getters and setters
// constructores
}
A continuación, crearemos la clase PersonaResource para definir las asignaciones HTTP:
@RequestScoped
@Path("personas")
public class PersonaResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<Persona> getPersonas() {
return Arrays.asList(new Person(1, "juanperez", "[email protected]"));
}
}
Aquí, hemos agregado el método getPersonas para el mapeo GET al punto final/api/personas. Entonces, estamos listos con un servicio web RESTful, y el comando liberty: dev cargará los cambios sobre la marcha.
Accedamos al servicio web RESTful/api/persons mediante una solicitud curl GET:
curl --request GET --url http://localhost:9080/api/personas
Luego, obtendremos una matriz JSON en respuesta:
[{"id":1, "usuario":"juanperez", "email":"[email protected]"}]
De manera similar, podemos agregar el mapeo POST creando el método addPersona:
@POST
@Consumes(MediaType.APPLICATION_JSON)
public Response addPersona(Persona persona) {
String respuesta = "Persona " + persona.getUsuario() + " obtenida exitosamente.";
return Response.status(Response.Status.CREATED).entity(respuesta).build();
}
Ahora, podemos invocar el punto final con una solicitud POST curl:
curl --request POST --url http://localhost:9080/api/personas \
--header 'content-type: application/json' \
--data '{"usuario": "pedrogonzales", "email": "[email protected]"}'
La respuesta se verá así:
Persona pedrogonzales obtenida exitosamente.
Persistencia
Configuración
Agreguemos soporte de persistencia a nuestros servicios web RESTful.
Primero, agregaremos la dependencia de derby Maven al pom.xml:
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.14.2.0</version>
</dependency>
Luego, agregaremos algunas características como jpa-2.2, jsonp-1.1 y cdi-2.0 al server.xml:
<featureManager>
<feature>jpa-2.2</feature>
<feature>jsonp-1.1</feature>
<feature>cdi-2.0</feature>
</featureManager>
Aquí, la función jsonp-1.1 proporciona la API de Java para el procesamiento JSON y la función cdi-2.0 maneja los alcances y la inyección de dependencias.
A continuación, crearemos persistence.xml en el directorio src/main/resources/META-INF:
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.2"
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">
<persistence-unit name="jpa-unit" transaction-type="JTA">
<jta-data-source>jdbc/jpadatasource</jta-data-source>
<properties>
<property name="eclipselink.ddl-generation" value="create-tables"/>
<property name="eclipselink.ddl-generation.output-mode" value="both" />
</properties>
</persistence-unit>
</persistence>
Aquí, hemos usado la generación DDL de EclipseLink para crear nuestro esquema de base de datos automáticamente. También podemos utilizar otras alternativas como Hibernate.
Luego, agreguemos la configuración de la fuente de datos al server.xml:
<library id="derbyJDBCLib">
<fileset dir="${shared.resource.dir}" includes="derby*.jar"/>
</library>
<dataSource id="jpadatasource" jndiName="jdbc/jpadatasource">
<jdbcDriver libraryRef="derbyJDBCLib" />
<properties.derby.embedded databaseName="libertyDB" createDatabase="create" />
</dataSource>
Tenga en cuenta que jndiName tiene la misma referencia a la etiqueta jta-data-source en persistence.xml.
Entidad y DAO
Luego, agregaremos la anotación @Entity y un identificador a nuestra clase Person:
@Entity
public class Person {
@GeneratedValue(strategy = GenerationType.AUTO)
@Id
private int id;
private String usuario;
private String email;
// getters y setters
}
A continuación, creemos la clase PersonDao que interactuará con la base de datos utilizando la instancia EntityManager:
@RequestScoped
public class PersonaDao {
@PersistenceContext(name = "jpa-unit")
private EntityManager em;
public Persona createPersona(Persona persona) {
em.persist(persona);
return persona;
}
public Persona readPersona(int personaId) {
return em.find(Persona.class, personaId);
}
}
Tenga en cuenta que @PersistenceContext define la misma referencia a la etiqueta persistence-unit en persistence.xml.
Ahora, inyectaremos la dependencia PersonaDao en la clase PersonaResource:
@RequestScoped
@Path("persona")
public class PersonaResource {
@Inject
private PersonaDao personaDao;
// ...
}
Aquí, hemos utilizado la anotación @Inject proporcionada por la función CDI.
Por último, actualizaremos el método addPersona de la clase PersonaResource para conservar el objeto Persona:
@POST
@Consumes(MediaType.APPLICATION_JSON)
@Transactional
public Response addPersona(Persona persona) {
personaDao.createPersona(persona);
String respuesta = "Persona #" + persona.getId() + " creada exitosamente.";
return Response.status(Response.Status.CREATED).entity(respuesta).build();
}
Aquí, el método addPerson está anotado con la anotación @Transactional para controlar las transacciones en beans administrados por CDI.
Invoquemos el punto final con la solicitud curl POST ya discutida:
curl --request POST --url http://localhost:9080/api/persons \
--header 'content-type: application/json' \
--data '{"usuario": "juanperez", "email": "[email protected]"}'
lo cual responderá
Persona #1 creada exitosamente.
De manera similar, agreguemos el método getPersona con el mapeo GET para buscar un objeto Persona:
@GET
@Path("{id}")
@Produces(MediaType.APPLICATION_JSON)
@Transactional
public Person getPersona(@PathParam("id") int id) {
Persona persona = personaDao.readPersona(id);
return persona;
}
Invoquemos el endpoint usando una solicitud curl GET:
curl --request GET --url http://localhost:9080/api/personas/1
Luego, obtendremos el objeto Persona como respuesta JSON:
{"email":"[email protected]","id":1,"usuario":"juanperez"}
Consume el servicio web RESTful con JSON-B
Primero, habilitaremos la capacidad de serializar y deserializar modelos directamente agregando la función jsonb-1.0 al server.xml:
<featureManager>
<feature>jsonb-1.0</feature>
</featureManager>
Luego, creemos la clase RestConsumer con el método consumeWithJsonb:
public class RestConsumer {
public static String consumeWithJsonb(String targetUrl) {
Client cliente = ClientBuilder.newClient();
Response respuesta = cliente.target(targetUrl).request().get();
String resultado = respuesta.readEntity(String.class);
respuesta.close();
cliente.close();
return resultado;
}
}
Aquí, hemos utilizado la clase ClientBuilder para solicitar los puntos finales del servicio web RESTful.
Por último, escribamos una prueba unitaria para consumir el servicio web RESTful/api/person y verifiquemos la respuesta:
@Test
public void whenConsumeWithJsonb_thenGetPersona() {
String url = "http://localhost:9080/api/personas/1";
String resultado = RestConsumer.consumeWithJsonb(url);
Persona persona = JsonbBuilder.create().fromJson(result, Persona.class);
assertEquals(1, persona.getId());
assertEquals("juanperez", persona.getUsuario());
assertEquals("[email protected]", persona.getEmail());
}
Aquí, hemos utilizado la clase JsonbBuilder para analizar la respuesta String en el objeto Persona.
Además, podemos usar MicroProfile Rest Client agregando la función mpRestClient-1.3 para consumir los servicios web RESTful. Proporciona la interfaz RestClientBuilder para solicitar los puntos finales del servicio web RESTful.