Saltar a contenido

Spring Boot y los Building Blocks

Con Spring Boot se vuelve bastante simple implementar los patrones tácticos requeridos en este proyecto.

1. Entity

Las Entities (Entidades) son representaciones de objetos del dominio que poseen una identidad única y cuyo estado puede cambiar con el tiempo. En Spring Boot, las entidades a menudo se anotan con @Entity.

Ejemplo:

import javax.persistence.Entity;
import javax.persistence.Id;

@Entity
public class User {
    @Id
    private Long id;
    private String name;
    private String email;

    // Constructores, getters y setters

    public User() {}

    public User(Long id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    // Getters y Setters
}

2. Repository

El Repository (Repositorio) actúa como un intermediario entre la aplicación y la capa de persistencia de datos. En Spring Boot, esto se hace frecuentemente usando Spring Data JPA, que simplifica la implementación de repositorios.

Ejemplo:

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

import java.util.List;

public interface UserRepository extends JpaRepository<User, Long> {

    // Método para encontrar un usuario por su e-mail
    User findByEmail(String email);

    // Método para encontrar todos los usuarios con una edad mayor a un valor específico
    @Query("SELECT u FROM User u WHERE u.age > :age")
    List<User> findAllOlderThan(@Param("age") int age);
}

3. Service

El Service (Servicio) es responsable de implementar la lógica de negocio de la aplicación. Orquesta las interacciones entre las entidades y los repositorios.

Ejemplo:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class UserService {
    private final UserRepository userRepository;

    @Autowired
    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public User createUser(User user) {
        return userRepository.save(user);
    }

    public User getUser(Long id) {
        return userRepository.findById(id).orElse(null);
    }

    public List<User> getAllUsers() {
        return userRepository.findAll();
    }

    public void deleteUser(Long id) {
        userRepository.deleteById(id);
    }
}

4. Controller

El Controller (Controlador) maneja las solicitudes del usuario y coordina la interacción entre la interfaz de usuario y los servicios. En Spring Boot, los controladores se anotan con @RestController.

Ejemplo:

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequestMapping("/users")
public class UserController {
    private final UserService userService;

    @Autowired
    public UserController(UserService userService) {
        this.userService = userService;
    }

    @PostMapping
    public ResponseEntity<User> createUser(@RequestBody User user) {
        User createdUser = userService.createUser(user);
        return ResponseEntity.ok(createdUser);
    }

    @GetMapping("/{id}")
    public ResponseEntity<User> getUser(@PathVariable Long id) {
        User user = userService.getUser(id);
        return user != null ? ResponseEntity.ok(user) : ResponseEntity.notFound().build();
    }

    @GetMapping
    public List<User> getAllUsers() {
        return userService.getAllUsers();
    }

    @DeleteMapping("/{id}")
    public ResponseEntity<Void> deleteUser(@PathVariable Long id) {
        userService.deleteUser(id);
        return ResponseEntity.noContent().build();
    }
}

5. TDD

Probar utilizando @SpringBootTest con MockMvc es una práctica eficaz que se alinea con los principios del Desarrollo Guiado por Pruebas (TDD). Este enfoque permite validar las interacciones del controlador con el servicio de forma aislada, garantizando que la lógica de manipulación de usuarios funcione correctamente, sin depender de componentes externos como una base de datos real. Al crear pruebas antes de la implementación, TDD ayuda a definir claramente los requisitos y comportamientos esperados del sistema, promoviendo la escritura de código más robusto. Esta combinación resulta en un ciclo de desarrollo más eficiente, donde la funcionalidad del sistema puede ser validada y refinada constantemente, asegurando que permanezca confiable y adaptable a medida que evoluciona.

@SpringBootTest
@AutoConfigureMockMvc
public class UserControllerTest {

    @Autowired
    private MockMvc mockMvc;

    private ObjectMapper objectMapper;

    @BeforeEach
    void setUp() {
        objectMapper = new ObjectMapper();
    }

    @Test
    void testCreateUser() throws Exception {
        User user = new User(1L, "John Doe", "john@example.com");

        mockMvc.perform(post("/users")
            .contentType(MediaType.APPLICATION_JSON)
            .content(objectMapper.writeValueAsString(user)))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("John Doe"))
            .andExpect(jsonPath("$.email").value("john@example.com"));
    }

    @Test
    void testGetUser() throws Exception {
        User user = new User(1L, "John Doe", "john@example.com");

        mockMvc.perform(get("/users/1"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$.name").value("John Doe"))
            .andExpect(jsonPath("$.email").value("john@example.com"));
    }

    @Test
    void testGetUserNotFound() throws Exception {
        mockMvc.perform(get("/users/999"))
            .andExpect(status().isNotFound());
    }

    @Test
    void testGetAllUsers() throws Exception {
        mockMvc.perform(get("/users"))
            .andExpect(status().isOk())
            .andExpect(jsonPath("$", hasSize(2)))
            .andExpect(jsonPath("$[0].name").value("John Doe"))
            .andExpect(jsonPath("$[1].name").value("Jane Doe"));
    }

    @Test
    void testDeleteUser() throws Exception {
        mockMvc.perform(delete("/users/1"))
            .andExpect(status().isNoContent());
    }
}