Points Clés
- Comprendre la couche de persistance
- Qu'est-ce que Microstream ?
- Comment intégrer Microstream avec Open Liberty ?
Les microservices sont devenus un mot à la mode lorsque nous parlons de créer une application évolutive. Mais est-ce suffisant ? La réponse simple est non. Comme pour toute décision d'architecture logicielle, il y a un compromis et plusieurs défis. Heureusement pour nous développeurs Java, il existe une combinaison de deux outils pour nous faciliter la vie : Microstream et MicroProfile. Cet article couvrira la combinaison de Microstream et Open Liberty pour créer facilement une application de type microservices stable et ultra-rapide.
Microservices avec Open Liberty
Les microservices posent plusieurs défis aux ingénieurs logiciels, en particulier comme première étape pour faire face aux systèmes distribués. Mais cela ne veut pas dire que nous sommes seuls. En effet, il existe plusieurs outils pour nous faciliter la vie dans le monde Java, notamment MicroProfile.
MicroProfile a pour objectif d'optimiser Enterprise Java pour une architecture de microservices. Il est basé sur la norme Java EE/Jakarta EE plus une API spécifiquement pour les microservices tels que Rest Client, Configuration, Open API, etc.
Open Liberty est l'une de ces implémentations, et IBM est le principal contributeur. Open Liberty est une infrastructure légère et ouverte permettant de créer des microservices Java rapides et efficaces dans le cloud. Il fournit juste ce qu’il faut pour exécuter des applications et des microservices natifs du cloud.
Persistance des données très rapide avec Microstream
Dès qu'on parle de microservices, on parle de système distribué et de ses enjeux, et cette invocation sera la même dans la couche de persistance.
Malheureusement, nous n'avons pas assez d'articles qui en parlent. Nous devrions avoir un modèle, même des bases de données sans schéma lorsque vous avez des informations plus incertaines sur le métier. Pourtant, la couche de persistance a plus de problèmes, principalement parce qu'elle est plus difficile à changer.
L'un des secrets pour créer une application évolutive est de s'assurer qu'elle est sans état, mais nous ne pouvons pas nous le permettre dans la couche de persistance. Principalement, la base de données vise à conserver les informations et leur état.
Microstream réalise un traitement de données en mémoire ultra-rapide en Java pur. Il fournit un temps de requête de l'ordre de la microseconde, un accès aux données à faible latence, un débit de données et des charges de travail gigantesque. Ainsi, il économise beaucoup de puissance CPU, d'émissions de CO2 et de coûts dans le centre de données.
Montrez-moi le code
Combinons les deux pour créer un microservice ultrarapide. Une fois que l'objectif principal est de montrer comment les deux se combinent, nous choisirons une démo simple. Dans cet exemple, nous allons créer une simple API CRUD avec un produit, son nom et sa note et l'exposer en tant qu'API Rest.
La première étape consiste à créer le squelette MicroProfile : il est facile et fluide, principalement parce que nous pouvons le créer visuellement avec le MicroProfile starter. Définissez Microprofile version 4.1 avec Java 11 et Open Liberty, comme le montre l'image ci-dessous.
Oui, nous avons le squelette de notre application. L'étape suivante consiste à ajouter Microstream et à faire fonctionner les deux ensemble. Heureusement, il existe une bibliothèque pour intégrer les deux via l'extension CDI. Ainsi, toute application avec CDI et MicroProfile Config peut fonctionner grâce à cette API.
Veuillez consulter la dernière version et l'ajouter à votre application.
<dependency>
<groupid>one.microstream</groupid>
<artifactid>microstream-integrations-cdi</artifactid>
<version>DERNIERE_VERSION_ICI</version>
</dependency>
Le squelette est défini, alors commençons par le code. Le modèle est la partie centrale. Comme c’est un exemple simple, nous allons créer une entité Product avec quelques champs. La principale recommandation en utilisant Microstream est d'utiliser des entités immuables. Par conséquent, nous allons créer un produit en tant qu'entité immuable.
public class Product {
private final long id;
private final String name;
private final String description;
private final int rating;
@JsonbCreator
public Product(
@JsonbProperty("id") final long id,
@JsonbProperty("name") final String name,
@JsonbProperty("description") final String description,
@JsonbProperty("rating") final String rating){
//le code du constructeur
}
//…
Les annotations JSON indiquent uniquement à MicroProfile comment sérialiser l'entité au format JSON.
L'étape suivante consiste à définir une collection de produits, que nous appellerons Inventory . La classe Inventory est un ensemble de produits avec plusieurs méthodes de fonctionnement.
Cette classe est le lien entre votre entité et le moteur Microstream. La connexion avec Microstream utilise l'annotation Storage.
import java.util.Collections;
import java.util.HashSet;
import java.util.Objects;
import java.util.Optional;
import java.util.Set;
import java.util.function.Predicate;
import one.microstream.integrations.cdi.types.Storage;
@Storage
public class Inventory {
private final Set<Product> products = new HashSet<>();
public void add(final Product product) {
Objects.requireNonNull(product, "product is required");
this.products.add(product);
}
public Set<Product> getProducts() {
return Collections.unmodifiableSet(this.products);
}
public Optional<Product> findById(final long id) {
return this.products.stream().filter(this.isIdEquals(id)).limit(1).findFirst();
}
public void deleteById(final long id) {
this.products.removeIf(this.isIdEquals(id));
}
private Predicate<Product> isIdEquals(final long id) {
return p -> p.getId() == id;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Inventory inventory = (Inventory) o;
return Objects.equals(products, inventory.products);
}
@Override
public int hashCode() {
return Objects.hash(products);
}
@Override
public String toString() {
return "Inventory{" +
"products=" + products +
'}';
}
}
public interface ProductRepository
{
Collection<Product> getAll();
Product save(Product item);
Optional<Product> findById(long id);
void deleteById(long id);
}
@ApplicationScoped
public class ProductRepositoryStorage implements ProductRepository {
private static final Logger LOGGER = Logger.getLogger(ProductRepositoryStorage.class.getName());
@Inject
private Inventory inventory;
@Override
public Collection<Product> getAll() {
return this.inventory.getProducts();
}
@Override
@Store
public Product save(final Product item) {
this.inventory.add(item);
return item;
}
@Override
public Optional<Product> findById(final long id) {
LOGGER.info("Finding the item by id: " + id);
return this.inventory.findById(id);
}
@Override
@Store
public void deleteById(final long id) {
this.inventory.deleteById(id);
}
}
La dernière étape consiste à exposer ce produit en tant qu'API Rest. Nous reviendrons avec MicroProfile en utilisant l'API Jakarta EE : JAX-RS. Puis, nous allons créer une documentation Open API à l'aide de MicroProfile.
@RequestScoped
@Path("products")
@Consumes(MediaType.APPLICATION_JSON)
@Produces(MediaType.APPLICATION_JSON)
public class ProductController
{
@Inject
private ProductRepository repository;
// TODO don't worried about pagination
@GET
@Operation(summary = "Get all products", description = "Returns all available items at the restaurant")
@APIResponse(responseCode = "500", description = "Server unavailable")
@APIResponse(responseCode = "200", description = "The products")
@Tag(name = "BETA", description = "This API is currently in beta state")
@APIResponse(description = "The products", responseCode = "200", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = Collection.class, readOnly = true, description = "the products", required = true, name = "products")))
public Collection<Product> getAll()
{
return this.repository.getAll();
}
@GET
@Path("{id}")
@Operation(summary = "Find a product by id", description = "Find a product by id")
@APIResponse(responseCode = "200", description = "The product")
@APIResponse(responseCode = "404", description = "When the id does not exist")
@APIResponse(responseCode = "500", description = "Server unavailable")
@Tag(name = "BETA", description = "This API is currently in beta state")
@APIResponse(description = "The product", content = @Content(mediaType = MediaType.APPLICATION_JSON, schema = @Schema(implementation = Product.class)))
public Product findById(
@Parameter(description = "The item ID", required = true, example = "1", schema = @Schema(type = SchemaType.INTEGER)) @PathParam("id") final long id)
{
return this.repository.findById(id).orElseThrow(
() -> new WebApplicationException("There is no product with the id " + id, Response.Status.NOT_FOUND));
}
@POST
@Operation(summary = "Insert a product", description = "Insert a product")
@APIResponse(responseCode = "201", description = "When creates an product")
@APIResponse(responseCode = "500", description = "Server unavailable")
@Tag(name = "BETA", description = "This API is currently in beta state")
public Response insert(
@RequestBody(description = "Create a new product.", content = @Content(mediaType = "application/json", schema = @Schema(implementation = Product.class))) final Product product)
{
return Response.status(Response.Status.CREATED).entity(this.repository.save(product)).build();
}
@DELETE
@Path("{id}")
@Operation(summary = "Delete a product by ID", description = "Delete a product by ID")
@APIResponse(responseCode = "200", description = "When deletes the product")
@APIResponse(responseCode = "500", description = "Server unavailable")
@Tag(name = "BETA", description = "This API is currently in beta state")
public Response delete(
@Parameter(description = "The item ID", required = true, example = "1", schema = @Schema(type = SchemaType.INTEGER)) @PathParam("id") final long id)
{
this.repository.deleteById(id);
return Response.status(Response.Status.NO_CONTENT).build();
}
}
C'est tout ! Nous pouvons tester l'application en cours d'exécution et vérifier le résultat. L'intégration fonctionne comme un charme.
mvn clean package
java -jar target/openliberty-example.jar
curl --location --request POST 'http://localhost:8080/products/' \
--header 'Content-Type: application/json' \
--data-raw '{"id": 1, "name": "banana", "description": "a fruit", "rating": 5}'
curl --location --request POST 'http://localhost:8080/products/' \
--header 'Content-Type: application/json' \
--data-raw '{"id": 2, "name": "watermelon", "description": "watermelon sugar ahh", "rating": 4}'
curl --location --request GET 'http://localhost:8080/products/'
curl --location --request GET 'http://localhost:8080/products/1'
</code>
Notre intégration entre Open Liberty et Microstream fonctionne enfin. Ce tutoriel montre comment les deux fonctionnent ensemble et vous donne un nouvel outil pour faire face aux problèmes de persistance : Microstream. En effet Microstream et Open Liberty sont de formidables alliés lorsque l'on souhaite créer des microservices pour le faire fonctionner ultra-rapidement.
References
-
La documentation de Microstream
-
La documentation d’Open Liberty