Aprende Spring Boot con un proyecto real

Hola developers, la pasada semana lanzamos en nuestro canal de Youtube un vídeo bajo el título «Aprendamos Spring Boot juntos», en el que realizamos un ejercicio completo. En resumen resolvimos el proyecto «ToDo API», una aplicación que implementa una API REST para gestionar tareas personales.

El objetivo es enseñar Spring Boot, la forma de desarrollar, arquitectura de una aplicación, dependencias, y otros temas «básicos» de Spring Boot que se pueden resumir en los siguientes items:

  • Spring MVC
  • Base de datos H2.
  • Spring Data JPA (Realizar consultas con Spring Data y SQL Nativas)
  • Manejar errores con el ControllerAdvice de Spring.
  • Configurar y usar Swagger.
  • Crear Mappers y el uso de DTOs.

Problemática del proyecto: ToDo API

El ejercicio de aprendizaje comienza con la problemática a resolver, que es en definitiva:

Crear una aplicación web (API REST) para la gestión de tareas personales, de cada tarea deberá guardarse la siguiente información:

  • Título.
  • Descripción.
  • Fecha de creación.
  • Fecha estimada de terminación.
  • Finalizada (Sí/No).
  • Estado (ON_TIME, LATE)

Funcionalidades deseadas para implementar:

  • Crear tareas.
  • Obtener todas las tareas.
  • Obtener todas las tareas filtradas por estado.
  • Marcar una tarea como cumplida.
  • Eliminar una tarea.

Requerimientos no funcionales a tener en cuenta:

  • Usar Spring Boot,
  • Usar la base de datos en memoria H2
  • Documentar la API con OpenAPI/Swagger.

La solución puedes verla completa en nuestro vídeo de Youtube, donde lo explicamos todo en detalles.

Implementación de ToDO API usando Spring Boot

Para resolver el problema anterior hemos implementado una aplicación Spring Boot cuyo código se muestra a continuación.

Dependencias del proyecto

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.5.8</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com.sacavix</groupId>
    <artifactId>todoapp</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>todoapp</name>
    <description>Demo project for Spring Boot</description>
    <properties>
        <java.version>11</java.version>
    </properties>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger2</artifactId>
            <version>2.9.2</version>
        </dependency>

        <dependency>
            <groupId>io.springfox</groupId>
            <artifactId>springfox-swagger-ui</artifactId>
            <version>2.9.2</version>
        </dependency>

    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
                <configuration>
                    <excludes>
                        <exclude>
                            <groupId>org.projectlombok</groupId>
                            <artifactId>lombok</artifactId>
                        </exclude>
                    </excludes>
                </configuration>
            </plugin>
        </plugins>
    </build>

</project>

Entidad principal TASK (encargada de representar una tarea).

import lombok.Data;

import javax.persistence.*;
import java.time.LocalDateTime;

@Data
@Entity
@Table(name = "task")
public class Task {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String title;
    private String description;
    private LocalDateTime createdDate;
    private LocalDateTime eta;
    private boolean finished;
    private TaskStatus taskStatus;
}

Capa de repositorio, donde se implementa el acceso a datos aplicando el patrón Repository.

import com.sacavix.todoapp.persistence.entity.Task;
import com.sacavix.todoapp.persistence.entity.TaskStatus;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface TaskRepository extends JpaRepository<Task, Long> {

    public List<Task> findAllByTaskStatus(TaskStatus status);

    @Modifying
    @Query(value = "UPDATE TASK SET FINISHED=true WHERE ID=:id", nativeQuery = true)
    public void markTaskAsFinished(@Param("id") Long id);
}

Capa de servicios, donde se implementa la lógica fundamental de la aplicación.

import javax.transaction.Transactional;
import java.util.List;
import java.util.Optional;

@Service
public class TaskService {

    private final TaskRepository repository;
    private final TaskInDTOToTask mapper;

    public TaskService(TaskRepository repository, TaskInDTOToTask mapper) {
        this.repository = repository;
        this.mapper = mapper;
    }

    public Task createTask(TaskInDTO taskInDTO) {
        Task task = mapper.map(taskInDTO);
        return this.repository.save(task);
    }

    public List<Task> findAll() {
        return this.repository.findAll();
    }

    public List<Task> findAllByTaskStatus(TaskStatus status) {
        return this.repository.findAllByTaskStatus(status);
    }

    @Transactional
    public void updateTaskAsFinished(Long id) {
        Optional<Task> optionalTask = this.repository.findById(id);
        if (optionalTask.isEmpty()) {
            throw new ToDoExceptions("Task not found", HttpStatus.NOT_FOUND);
        }

        this.repository.markTaskAsFinished(id);
    }

    public void deleteById(Long id) {
        Optional<Task> optionalTask = this.repository.findById(id);
        if (optionalTask.isEmpty()) {
            throw new ToDoExceptions("Task not found", HttpStatus.NOT_FOUND);
        }

        this.repository.deleteById(id);
    }

}

Controlador principal de la aplicación, encargado de ofrecer la API.

import java.net.http.HttpResponse;
import java.util.List;

@RestController
@RequestMapping("/tasks")
public class TaskController {

    private final TaskService taskService;

    public TaskController(TaskService taskService) {
        this.taskService = taskService;
    }

    @PostMapping
    public Task createTask(@RequestBody TaskInDTO taskInDTO) {
       return this.taskService.createTask(taskInDTO);
    }

    @GetMapping
    public List<Task> findAll(){
        return this.taskService.findAll();
    }

    @GetMapping("/status/{status}")
    public List<Task> findAllbyStatus(@PathVariable("status") TaskStatus status){
        return this.taskService.findAllByTaskStatus(status);
    }

    @PatchMapping("/mark_as_finished/{id}")
    public ResponseEntity<Void> markAsFiniched(@PathVariable("id") Long id) {
        this.taskService.updateTaskAsFinished(id);
        return ResponseEntity.noContent().build();
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> delete(@PathVariable("id") Long id) {
        this.taskService.deleteById(id);
        return ResponseEntity.noContent().build();
    }
}

Existen otras parte del proyecto que no han sido reflejadas en el artículo, pero puedes acceder al código fuente de la aplicación completo en nuestro repositorio de GitHub, si te fue útil regálame una estrella en GitHub.

Ver en GitHub todo el código: Código fuente de ToDo API

Hasta el próximo.

Deja una respuesta

Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *

SACAViX Tech Blog