Saltar a contenido

Listado simple - Spring Boot

A diferencia del tutorial básico de Spring Boot, donde construíamos una aplicación monolítica, ahora vamos a construir multiples servicios por lo que necesitamos crear proyectos separados.

Para la creación de proyecto nos remitimos a la guía de instalación donde se detalla el proceso de creación de nuevo proyecto Entorno de desarrollo

Todos los pasos son exactamente iguales, lo único que va a variar es el nombre de nuestro proyecto, que en este caso se va a llamar tutorial-category. El campo que debemos modificar es artifact en Spring Initilizr, el resto de campos se cambiaran automáticamente.

Estructurar el código y buenas prácticas

Esta parte de tutorial es una ampliación de la parte de backend con Spring Boot, por tanto, no se ve a enfocar en las partes básicas aprendidas previamente, sino que se va a explicar el funcionamiento de los micro servicios aplicados al mismo caso de uso.

Para cualquier duda sobre la estructura del código y buenas prácticas, consultar el apartado de Estructura y buenas prácticas, ya que aplican a este caso en el mismo modo.

Código

Dado de vamos a implementar el micro servicio Spring Boot de Categorías, vamos a respetar la misma estructura del Listado simple de la version monolítica.

Entity y Dto

En primer lugar, vamos a crear la entidad y el DTO dentro del package com.ccsw.tutorialcategory.category.model. Ojo al package que lo hemos renombrado con respecto al listado monolítico.

package com.ccsw.tutorialcategory.category.model;

import jakarta.persistence.*;

/**
 * @author ccsw
 *
 */
@Entity
@Table(name = "category")
public class Category {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "id", nullable = false)
    private Long id;

    @Column(name = "name", nullable = false)
    private String name;

    /**
     * @return id
     */
    public Long getId() {

        return this.id;
    }

    /**
     * @param id new value of {@link #getId}.
     */
    public void setId(Long id) {

        this.id = id;
    }

    /**
     * @return name
     */
    public String getName() {

        return this.name;
    }

    /**
     * @param name new value of {@link #getName}.
     */
    public void setName(String name) {

        this.name = name;
    }

}
package com.ccsw.tutorialcategory.category.model;

/**
 * @author ccsw
 *
 */
public class CategoryDto {

    private Long id;

    private String name;

    /**
     * @return id
     */
    public Long getId() {

        return this.id;
    }

    /**
     * @param id new value of {@link #getId}.
     */
    public void setId(Long id) {

        this.id = id;
    }

    /**
     * @return name
     */
    public String getName() {

        return this.name;
    }

    /**
     * @param name new value of {@link #getName}.
     */
    public void setName(String name) {

        this.name = name;
    }

}

Repository, Service y Controller

Posteriormente, emplazamos el resto de clases dentro del package com.ccsw.tutorialcategory.category.

package com.ccsw.tutorialcategory.category;

import com.ccsw.tutorialcategory.category.model.Category;
import org.springframework.data.repository.CrudRepository;

/**
 * @author ccsw
 *
 */
public interface CategoryRepository extends CrudRepository<Category, Long> {

}
package com.ccsw.tutorialcategory.category;


import com.ccsw.tutorialcategory.category.model.Category;
import com.ccsw.tutorialcategory.category.model.CategoryDto;

import java.util.List;

/**
 * @author ccsw
 *
 */
public interface CategoryService {

    /**
     * Recupera una {@link Category} a partir de su ID
     *
     * @param id PK de la entidad
     * @return {@link Category}
     */
    Category get(Long id);

    /**
     * Método para recuperar todas las {@link Category}
     *
     * @return {@link List} de {@link Category}
     */
    List<Category> findAll();

    /**
     * Método para crear o actualizar una {@link Category}
     *
     * @param id PK de la entidad
     * @param dto datos de la entidad
     */
    void save(Long id, CategoryDto dto);

    /**
     * Método para borrar una {@link Category}
     *
     * @param id PK de la entidad
     */
    void delete(Long id) throws Exception;

}
package com.ccsw.tutorialcategory.category;

import com.ccsw.tutorialcategory.category.model.Category;
import com.ccsw.tutorialcategory.category.model.CategoryDto;
import jakarta.transaction.Transactional;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

/**
 * @author ccsw
 *
 */
@Service
@Transactional
public class CategoryServiceImpl implements CategoryService {

    @Autowired
    CategoryRepository categoryRepository;

    /**
     * {@inheritDoc}
     */
    @Override
    public Category get(Long id) {

        return this.categoryRepository.findById(id).orElse(null);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public List<Category> findAll() {

        return (List<Category>) this.categoryRepository.findAll();
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void save(Long id, CategoryDto dto) {

        Category category;

        if (id == null) {
            category = new Category();
        } else {
            category = this.get(id);
        }

        category.setName(dto.getName());

        this.categoryRepository.save(category);
    }

    /**
     * {@inheritDoc}
     */
    @Override
    public void delete(Long id) throws Exception {

        if(this.get(id) == null){
            throw new Exception("Not exists");
        }

        this.categoryRepository.deleteById(id);
    }

}
package com.ccsw.tutorialcategory.category;

import com.ccsw.tutorialcategory.category.model.Category;
import com.ccsw.tutorialcategory.category.model.CategoryDto;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.modelmapper.ModelMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.stream.Collectors;

/**
 * @author ccsw
 *
 */
@Tag(name = "Category", description = "API of Category")
@RequestMapping(value = "/category")
@RestController
@CrossOrigin(origins = "*")
public class CategoryController {

    @Autowired
    CategoryService categoryService;

    @Autowired
    ModelMapper mapper;

    /**
     * Método para recuperar todas las {@link Category}
     *
     * @return {@link List} de {@link CategoryDto}
     */
    @Operation(summary = "Find", description = "Method that return a list of Categories"
    )
    @RequestMapping(path = "", method = RequestMethod.GET)
    public List<CategoryDto> findAll() {

        List<Category> categories = this.categoryService.findAll();

        return categories.stream().map(e -> mapper.map(e, CategoryDto.class)).collect(Collectors.toList());
    }

    /**
     * Método para crear o actualizar una {@link Category}
     *
     * @param id PK de la entidad
     * @param dto datos de la entidad
     */
    @Operation(summary = "Save or Update", description = "Method that saves or updates a Category"
    )
    @RequestMapping(path = { "", "/{id}" }, method = RequestMethod.PUT)
    public void save(@PathVariable(name = "id", required = false) Long id, @RequestBody CategoryDto dto) {

        this.categoryService.save(id, dto);
    }

    /**
     * Método para borrar una {@link Category}
     *
     * @param id PK de la entidad
     */
    @Operation(summary = "Delete", description = "Method that deletes a Category")
    @RequestMapping(path = "/{id}", method = RequestMethod.DELETE)
    public void delete(@PathVariable("id") Long id) throws Exception {

        this.categoryService.delete(id);
    }

}

SQL y Configuración

Finalmente, debemos crear el mismo fichero de inicialización de base de datos con solo los datos de categorías y modificar ligeramente la configuración inicial para añadir un puerto manualmente. Esto es necesario ya que vamos a levantar varios servicios simultáneamente y necesitaremos levantarlos en puertos diferentes para que no colisionen entre ellos.

INSERT INTO category(name) VALUES ('Eurogames');
INSERT INTO category(name) VALUES ('Ameritrash');
INSERT INTO category(name) VALUES ('Familiar');
server.port=8091

#Database
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=sa
spring.datasource.driver-class-name=org.h2.Driver

spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.defer-datasource-initialization=true
spring.jpa.show-sql=true

spring.h2.console.enabled=true

Pruebas

Ahora si arrancamos la aplicación server y abrimos el Postman podemos realizar las mismas pruebas del apartado de Listado simple pero esta vez apuntado al puerto 8091.

Siguientes pasos

Con esto ya tendríamos nuestro primer servicio separado. Podríamos conectar el frontend a este servicio, pero a medida que nuestra aplicación creciera en número de servicios sería un poco engorroso todo, así que todavía no lo vamos a conectar hasta que no tengamos toda la infraestructura.

Vamos a convertir en micro servicio el siguiente listado.