Enmilocalfunciona

Thoughts, stories and ideas.

Micronaut: Implementando CRUD con H2

Publicado por Jhonatan Rojas Terrones el

Arquitectura de SolucionesMicronautMicroserviciosH2


Antes de empezar este artículo, recordar que Micronaut es un proyecto open-source con licencia de Apache (v2), el cual ha sido desarrollado por los creadores del Framework Grails con la idea de poder implementar y testear microservicios de una forma muy sencilla y rápida, para más información te recomiendo leer el post anterior Microservicios y Micronaut que tal?

Introducción

En el mundo del desarrollo nos encontramos a constantes innovaciones y cambios en todos los aspectos y cuando nos referimos a las aplicaciones van evolucionando cada día más, con la llegada de Cloud Computing y el nacimiento de nuevos modelos de servicios, hemos ido revolucionando tanto en la forma de desarrollar, este punto es clave donde aprendemos buenas prácticas en el desarrollo de aplicaciones y microservicios utilizando Spring Boot, Quarkus y en este caso Micronaut un potente framework el cual si aún no lo has llegado a implementar aquí te enseñaremos el paso a paso como Implementar CRUD con H2.

A por ello....

Pre-requisitos

Creación del Proyecto JAVA en Micronaut

  1. Para la creación del proyecto utilizaremos el Micronaut Launch
    El microservicio tendrá las siguientes características, como se muestra en la siguiente imagen:

2. Configuración del fichero application.properties

datasources.default.password=
datasources.default.dialect=H2
micronaut.application.name=demo-example-micronaut-crud
datasources.default.schema-generate=CREATE_DROP
datasources.default.url=jdbc\:h2\:mem\:devDb;LOCK_TIMEOUT\=10000;DB_CLOSE_ON_EXIT\=FALSE
datasources.default.username=sa
datasources.default.driver-class-name=org.h2.Driver

La configuración del H2 en memoria viene por defecto cuando lo generas por el Micronaut Launch

3. La estructura del proyecto debe ser la siguiente, como se muestra en la imagen:

Para este proyecto se ha creado los paquetes controller, model y repository.

Ahora que tenemos la configuración y estructura de paquetes completa, pasaremos al a implementación...

4. Creación de la clase Model (más conocido como dominio o entidad)

En esta clase utilizaremos Lombok para la generación de métodos getters/setters, constructores por anotaciones, esto nos facilita a que nuestro código evite escribir ciertos métodos, que van a ser repetitivos con el objetivo de tener un código más limpio.

src/main/java/com/micronaut/model/Client.java
package com.micronaut.model;

import io.micronaut.data.annotation.GeneratedValue;
import io.micronaut.data.annotation.Id;
import io.micronaut.data.annotation.MappedEntity;
import io.micronaut.serde.annotation.Serdeable;
import lombok.Getter;
import lombok.NonNull;
import lombok.RequiredArgsConstructor;
import lombok.Setter;

import java.io.Serializable;
import java.math.BigDecimal;

@Setter
@Getter
@RequiredArgsConstructor
@MappedEntity
@Serdeable
public class Client implements Serializable {

    @GeneratedValue
    @Id
    private Long id;
    private @NonNull String name;
    private @NonNull int age;
    private @NonNull String address;
    private @NonNull double salary;
}

@Setter: Genera métodos de tipo SET.

@Getter: Genera métodos de tipo GET.

@RequiredArgsConstructor: Genera un constructor con un parámetro para cada campo.

@MappedEntity: Define la entidad (el nombre de la tabla).

@Serdeable: Permite que la entidad serialice o deserialice desde o hacia un formato JSON.

@GeneratedValue: Genera valores de una llave primaria (primary key).

@Id: Define el identificador único de una entidad.

5. Creación de la interfaz Repository

Esta clase nos ayudará a definir las operaciones para acceder a la base de datos. Micronaut Data implementará una interfaz en el momento de compilación

src/main/java/com/micronaut/repository/ClientRepository.java
package com.micronaut.repository;

import com.micronaut.model.Client;
import io.micronaut.data.jdbc.annotation.JdbcRepository;
import io.micronaut.data.model.query.builder.sql.Dialect;
import io.micronaut.data.repository.CrudRepository;

import java.util.List;

@JdbcRepository(dialect = Dialect.H2)
public interface ClientRepository extends CrudRepository<Client, Long> {

    @Override
    public List<Client> findAll();

}

@JdbcRepository: Define el dialecto especifico de base de datos en este caso H2.

CrudRepository: Es una interfaz repositorio para realizar CRUD que proporciona métodos como findAll(), save(), update(), deleteById(Long) y findById(Long).

6. Creación de la clase Controlador

src/main/java/com/micronaut/controller/ClientController.java
package com.micronaut.controller;

import com.micronaut.model.Client;
import com.micronaut.repository.ClientRepository;
import io.micronaut.http.HttpResponse;
import io.micronaut.http.MediaType;
import io.micronaut.http.MutableHttpResponse;
import io.micronaut.http.annotation.*;
import io.micronaut.scheduling.TaskExecutors;
import io.micronaut.scheduling.annotation.ExecuteOn;
import jakarta.inject.Inject;

import java.util.List;
import java.util.Optional;

@Controller
@ExecuteOn(value = TaskExecutors.IO)
public class ClientController {

    @Inject
    private ClientRepository clientRepository;

    @Get(value = "/clients", produces = MediaType.APPLICATION_JSON)
    public HttpResponse<List<Client>> get() {
        try {
            List<Client> listClients = clientRepository.findAll();
            if (!listClients.isEmpty()) {
                return HttpResponse.ok().body(listClients);
            } else {
                return HttpResponse.noContent();
            }
        } catch (Exception e) {
            return HttpResponse.serverError();
        }
    }

    @Get(value = "/client/{idClient}", produces = MediaType.APPLICATION_JSON)
    public MutableHttpResponse<Optional<Client>> getById(@QueryValue(value = "idClient") Long id) {
        try {
            Optional<Client> client = clientRepository.findById(id);

            if (client.isEmpty())
                return HttpResponse.noContent();

            return HttpResponse.ok().body(client);
        } catch (Exception e) {
            return HttpResponse.serverError();
        }
    }

    @Post(value = "/client", consumes = MediaType.APPLICATION_JSON)
    public HttpResponse<?> save(@Body Client client) {
        try {
            if (client != null) {
                clientRepository.save(client);
                return HttpResponse.created(client);
            } else {
                return HttpResponse.badRequest();
            }
        } catch (Exception e) {
            return HttpResponse.serverError();
        }
    }

    @Delete(value = "/client/{id}", consumes = MediaType.APPLICATION_JSON)
    public HttpResponse<?> remove(Long id) {
        try {
            if (id != null) {
                clientRepository.deleteById(id);
                return HttpResponse.ok();
            } else {
                return HttpResponse.badRequest();
            }
        } catch (Exception e) {
            return HttpResponse.serverError();
        }
    }

    @Put(value = "/client/{id}", consumes = MediaType.APPLICATION_JSON)
    public HttpResponse<?> update(Long id, @Body Client client) {
        try {
            if (id != null) {
                client.setId(id);
                clientRepository.update(client);
                return HttpResponse.ok();
            } else {
                return HttpResponse.badRequest();
            }
        } catch (Exception e) {
            return HttpResponse.serverError();
        }
    }
}

@Controller: Define un controlador

@ExecuteOn: Sirve para cualquier operación de E/S de bloqueo (como recuperar datos en la base de datos) se descargue a un grupo de subprocesos separado que no bloquee el bucle de eventos.

@Inject: Inyectar un "bean" de tipo Repository

@Get: Asigna una solicitud HTTP de tipo GET, se utiliza para retornar información de uno o varios registros.

@Post: Asigna una solicitud HTTP de tipo POST, se utiliza para almacenar información de uno o varios registros.

@Delete: Asigna una solicitud HTTP de tipo DELETE, se utiliza para eliminar información de un registro en especifico por Id.

@Put: Asigna una solicitud HTTP de tipo PUT, se utiliza para actualizar información de un registro en especifico por Id.

7. Ejecutar la aplicación

- Botoncito verde y a correr!!!!

package com.micronaut;

import com.micronaut.model.Client;
import com.micronaut.repository.ClientRepository;
import io.micronaut.context.event.StartupEvent;
import io.micronaut.runtime.Micronaut;
import io.micronaut.runtime.event.annotation.EventListener;
import jakarta.inject.Singleton;
import jakarta.transaction.Transactional;

import java.util.Arrays;

@Singleton
public class Application {

    private final ClientRepository repository;

    public Application(ClientRepository repository){
        this.repository = repository;
    }

    public static void main(String[] args) {
        Micronaut.run(Application.class, args);
    }

    @EventListener
    @Transactional
    public void init(StartupEvent startupEvent) {
        repository.saveAll(Arrays.asList(
                new Client( "Jhonatan", 33, "Av. Diagonal 735", 1.500),
                new Client( "Milton", 50, "Av. Desierto 500", 3.500),
                new Client( "Charles", 52, "Av. Juan Carlos I 10", 7.000)));
    }
}

java -jar .\target\demo-example-micronaut-crud-0.1.jar

Probando el servicio vía Postman

Consulta de Clientes

Vamos a consultar todos los clientes (precargados anteriormente).

Consultar de Clientes por Id

Vamos a consultar un cliente en especifico con Identificador 3.

Registrar de Cliente

Vamos a registrar a Cristiano Ronaldo.

Actualizar de Cliente por Id

Vamos a actualizar el cliente 4 que es Cristiano Ronaldo para Cristiano JR.

Eliminar de Cliente por Id

Vamos a eliminar a Jhonatan que tiene Id = 1.

Volvemos a consultar todos los clientes, para ver el nuevo listado (actualizado).

Conclusión

Micronaut es un framework muy potente y que promete bastante, como hemos aprendido en este articulo ofrece una colección de funciones para el desarrollo de microservicios. Al mismo tiempo, el macro hace que el desarrollo más tradicional basado en API sea directo y sencillo.

Micronaut junto a Quarkus, DropWizard, entre otros macros JAVA es una gran alternativa refrescante a las soluciones existentes.

Espero que este articulo haya sido de utilidad para futuros desarrollos en vuestras aplicaciones. ¿Por qué no implementar una solución con Micronaut en la nube? (esto os dejo para un futuro articulo).

Tenéis el código del proyecto en el repositorio de GitHub aqui.