Creazione di microservizi con Eclipse MicroProfile

1. Panoramica

In questo articolo, ci concentreremo sulla creazione di un microservizio basato su Eclipse MicroProfile.

Vedremo come scrivere un'applicazione web RESTful utilizzando le API JAX-RS, CDI e JSON-P.

2. Un'architettura di microservizi

In poche parole, i microservizi sono uno stile di architettura software che forma un sistema completo come una raccolta di diversi servizi indipendenti.

Ognuno si concentra su un perimetro funzionale e comunica agli altri con un protocollo indipendente dal linguaggio, come REST.

3. Eclipse MicroProfile

Eclipse MicroProfile è un'iniziativa che mira a ottimizzare Enterprise Java per l'architettura Microservices. Si basa su un sottoinsieme di API Jakarta EE WebProfile, quindi possiamo creare applicazioni MicroProfile come costruiamo quelle Jakarta EE.

L'obiettivo di MicroProfile è definire API standard per la creazione di microservizi e fornire applicazioni portatili su più runtime MicroProfile.

4. Dipendenze di Maven

Tutte le dipendenze richieste per creare un'applicazione Eclipse MicroProfile sono fornite da questa dipendenza BOM (Bill Of Materials):

 org.eclipse.microprofile microprofile 1.2 pom provided  

L'ambito è impostato come previsto perché il runtime di MicroProfile include già l'API e l'implementazione.

5. Modello di rappresentazione

Iniziamo creando una rapida classe di risorse:

public class Book { private String id; private String name; private String author; private Integer pages; // ... }

Come possiamo vedere, non ci sono annotazioni in questa classe Book .

6. Utilizzo di CDI

In poche parole, CDI è un'API che fornisce l'inserimento delle dipendenze e la gestione del ciclo di vita. Semplifica l'uso dei bean Enterprise nelle applicazioni Web.

Creiamo ora un bean gestito da CDI come archivio per la rappresentazione del libro:

@ApplicationScoped public class BookManager { private ConcurrentMap inMemoryStore = new ConcurrentHashMap(); public String add(Book book) { // ... } public Book get(String id) { // ... } public List getAll() { // ... } } 

Annotiamo questa classe con @ApplicationScoped perché abbiamo bisogno di una sola istanza il cui stato è condiviso da tutti i client. Per questo, abbiamo utilizzato ConcurrentMap come archivio dati in memoria indipendente dai tipi. Quindi abbiamo aggiunto metodi per le operazioni CRUD .

Ora il nostro bean è pronto per CDI e può essere iniettato nel bean BookEndpoint utilizzando l' annotazione @Inject .

7. API JAX-RS

Per creare un'applicazione REST con JAX-RS, è necessario creare una classe Application annotata con @ApplicationPath e una risorsa annotata con @Path.

7.1. Applicazione JAX RS

L'applicazione JAX-RS identifica l'URI di base sotto il quale esponiamo la risorsa in un'applicazione Web.

Creiamo la seguente applicazione JAX-RS:

@ApplicationPath("/library") public class LibraryApplication extends Application { }

In questo esempio, tutte le classi di risorse JAX-RS nell'applicazione Web sono associate a LibraryApplication rendendole sotto lo stesso percorso di libreria , che è il valore dell'annotazione ApplicationPath.

Questa classe annotata dice al runtime JAX RS che dovrebbe trovare le risorse automaticamente e le espone.

7.2. Endpoint JAX RS

Una classe Endpoint , chiamata anche classe Resource , dovrebbe definire una risorsa sebbene molti degli stessi tipi siano tecnicamente possibili.

Ogni classe Java annotata con @Path o con almeno un metodo annotato con @Path o @HttpMethod è un endpoint.

Now, we'll create a JAX-RS Endpoint that exposes that representation:

@Path("books") @RequestScoped public class BookEndpoint { @Inject private BookManager bookManager; @GET @Path("{id}") @Produces(MediaType.APPLICATION_JSON) public Response getBook(@PathParam("id") String id) { return Response.ok(bookManager.get(id)).build(); } @GET @Produces(MediaType.APPLICATION_JSON) public Response getAllBooks() { return Response.ok(bookManager.getAll()).build(); } @POST @Consumes(MediaType.APPLICATION_JSON) public Response add(Book book) { String bookId = bookManager.add(book); return Response.created( UriBuilder.fromResource(this.getClass()) .path(bookId).build()) .build(); } } 

At this point, we can access the BookEndpoint Resource under the /library/books path in the web application.

7.3. JAX RS JSON Media Type

JAX RS supports many media types for communicating with REST clients, but Eclipse MicroProfile restricts the use of JSON as it specifies the use of the JSOP-P API. As such, we need to annotate our methods with @Consumes(MediaType.APPLICATION_JSON) and @Produces(MediaType.APPLICATION_JSON).

The @Consumes annotation restricts the accepted formats – in this example, only JSON data format is accepted. The HTTP request header Content-Type should be application/json.

The same idea lies behind the @Produces annotation. The JAX RS Runtime should marshal the response to JSON format. The request HTTP header Accept should be application/json.

8. JSON-P

JAX RS Runtime supports JSON-P out of the box so that we can use JsonObject as a method input parameter or return type.

But in the real world, we often work with POJO classes. So we need a way to do the mapping between JsonObject and POJO. Here's where the JAX RS entity provider goes to play.

For marshaling JSON input stream to the Book POJO, that's invoking a resource method with a parameter of type Book, we need to create a class BookMessageBodyReader:

@Provider @Consumes(MediaType.APPLICATION_JSON) public class BookMessageBodyReader implements MessageBodyReader { @Override public boolean isReadable( Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return type.equals(Book.class); } @Override public Book readFrom( Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, InputStream entityStream) throws IOException, WebApplicationException { return BookMapper.map(entityStream); } } 

We do the same process to unmarshal a Book to JSON output stream, that's invoking a resource method whose return type is Book, by creating a BookMessageBodyWriter:

@Provider @Produces(MediaType.APPLICATION_JSON) public class BookMessageBodyWriter implements MessageBodyWriter { @Override public boolean isWriteable( Class type, Type genericType, Annotation[] annotations, MediaType mediaType) { return type.equals(Book.class); } // ... @Override public void writeTo( Book book, Class type, Type genericType, Annotation[] annotations, MediaType mediaType, MultivaluedMap httpHeaders, OutputStream entityStream) throws IOException, WebApplicationException { JsonWriter jsonWriter = Json.createWriter(entityStream); JsonObject jsonObject = BookMapper.map(book); jsonWriter.writeObject(jsonObject); jsonWriter.close(); } } 

As BookMessageBodyReader and BookMessageBodyWriter are annotated with @Provider, they're registered automatically by the JAX RS runtime.

9. Building and Running the Application

A MicroProfile application is portable and should run in any compliant MicroProfile runtime. We'll explain how to build and run our application in Open Liberty, but we can use any compliant Eclipse MicroProfile.

We configure Open Liberty runtime through a config file server.xml:

  jaxrs-2.0 cdi-1.2 jsonp-1.0     

Let's add the plugin liberty-maven-plugin to our pom.xml:

  net.wasdev.wlp.maven.plugins liberty-maven-plugin 2.1.2   io.openliberty openliberty-runtime 17.0.0.4 zip  ${basedir}/src/main/liberty/config/server.xml ${package.file} ${packaging.type} false project  / ${project.artifactId}-${project.version}.war 9080 9443     install-server prepare-package  install-server create-server install-feature    package-server-with-apps package  install-apps package-server    

This plugin is configurable throw a set of properties:

  library ${project.build.directory}/${app.name}-service.jar runnable 

The exec goal above produces an executable jar file so that our application will be an independent microservice which can be deployed and run in isolation. We can also deploy it as Docker image.

To create an executable jar, run the following command:

mvn package 

And to run our microservice, we use this command:

java -jar target/library-service.jar

This will start the Open Liberty runtime and deploy our service. We can access to our Endpoint and getting all books at this URL:

curl //localhost:9080/library/books

The result is a JSON:

[ { "id": "0001-201802", "isbn": "1", "name": "Building Microservice With Eclipse MicroProfile", "author": "baeldung", "pages": 420 } ] 

To get a single book, we request this URL:

curl //localhost:9080/library/books/0001-201802

And the result is JSON:

{ "id": "0001-201802", "isbn": "1", "name": "Building Microservice With Eclipse MicroProfile", "author": "baeldung", "pages": 420 }

Now we'll add a new Book by interacting with the API:

curl -H "Content-Type: application/json" -X POST -d '{"isbn": "22", "name": "Gradle in Action","author": "baeldung","pages": 420}' //localhost:9080/library/books 

As we can see, the status of the response is 201, indicating that the book was successfully created, and the Location is the URI by which we can access it:

< HTTP/1.1 201 Created < Location: //localhost:9080/library/books/0009-201802

10. Conclusion

Questo articolo ha dimostrato come creare un semplice microservizio basato su Eclipse MicroProfile, discutendo di JAX RS, JSON-P e CDI.

Il codice è disponibile su Github; questo è un progetto basato su Maven, quindi dovrebbe essere semplice da importare ed eseguire così com'è.