Estructura per al RestController

Podem estructurar el nostre RestController de moltes maneres segons el nivell d’abstracció i modularitat que vullgam aplicar. Els més comuns són:

1️ Directament amb el Repository (@RestController + @Repository)

  • El RestController crida directament els JpaRepository, sense cap capa de servei.
  • Avantatges:
    • Codi senzill i ràpid de desenvolupar.
    • Menys classes i menys codi a mantenir.
  • Inconvenients:
    • Difícil d’ampliar si cal modificar la lògica de negoci.
    • No permet reutilitzar la lògica de negoci en altres llocs.
    • No separa clarament responsabilitats.

2️ Amb una Capa de Serveis (@Service)

  • El RestController crida un Service, que conté la lògica de negoci i després crida al Repository.
  • Avantatges:
    • Separa les responsabilitats: el Controller només rep les peticions, el Service s’encarrega de la lògica i el Repository accedeix a la BD.
    • Fàcil de modificar o afegir nova lògica sense tocar el Controller.
  • Inconvenients:
    • Més classes, pot semblar sobreenginyeria si l’API és molt simple.

3️ Amb DTOs (Data Transfer Objects)

  • Com funciona? Utilitza DTOs per encapsular les dades que s’envien o reben a l’API, en lloc d’exposar directament les entitats JPA.
  • Avantatges:
    • Protegeix l’estructura interna de la BD (no exposem directament les entitats JPA).
    • Evita problemes de Lazy Loading amb entitats relacionades.
    • Permet personalitzar les respostes i no dependre de la BD.
  • Inconvenients:
    • Necessita classes addicionals (DTOs).
    • Es requereix mapeig entre Entitats <-> DTOs (per exemple, amb MapStruct o manualment).

Esta és la millor opció per a projectes escalables:

  • Utilitzar Capa de Servei + DTOs, perquè separa bé les responsabilitats i protegeix l’estructura de la BD.

Estructura Recomanada

  • 1.- Repositoris (DAO)
    • CiutatRepository.java
    • ProvinciaRepository.java
    • PaisRepository.java
    • FranquiciaRepository.java
    • CiutatFranquiciaRepository.java
  • 2️ Serveis (Business Logic)
    • CiutatService.java
    • ProvinciaService.java
    • PaisService.java
    • FranquiciaService.java
    • CiutatFranquiciaService.java
  • 3️ DTOs
    • CiutatDTO.java
    • ProvinciaDTO.java
    • PaisDTO.java
    • FranquiciaDTO.java
    • CiutatFranquiciaDTO.java
  • 4️ Controllers
    • CiutatController.java
    • ProvinciaController.java
    • PaisController.java
    • FranquiciaController.java
    • CiutatFranquiciaController.java

Exemple d’implementació

🔹 DTO per a Ciutat

public class CiutatDTO {
    private Long id;
    private String nom;
    private int poblacio;
    private String descripcio;
    private String imatge;
    private String nomProvincia; // Evitem retornar l’objecte complet

    public CiutatDTO(Ciutat ciutat) {
        this.id = ciutat.getId();
        this.nom = ciutat.getNom();
        this.poblacio = ciutat.getPoblacio();
        this.descripcio = ciutat.getDescripcio();
        this.imatge = ciutat.getImatge();
        this.nomProvincia = ciutat.getProvincia().getNom();
    }

    // Getters i Setters...
}
  • CiutatRepository (DAO)
    @Repository
    public interface CiutatRepository extends JpaRepository<Ciutat, Long> {
      List<Ciutat> findByPoblacioGreaterThan(int poblacio);
    }
    
  • CiutatService
    @Service
    public class CiutatService {
      @Autowired
      private CiutatRepository ciutatRepository;
    
      public List<CiutatDTO> obtenirTotesLesCiutats() {
          List<Ciutat> ciutats = ciutatRepository.findAll();
          return ciutats.stream().map(CiutatDTO::new).collect(Collectors.toList());
      }
    
      public CiutatDTO obtenirCiutatPerId(Long id) {
          return ciutatRepository.findById(id)
                  .map(CiutatDTO::new)
                  .orElseThrow(() -> new RuntimeException("Ciutat no trobada"));
      }
    }
    
  • CiutatController
    @RestController
    @RequestMapping("/api/ciutats")
    public class CiutatController {
      @Autowired
      private CiutatService ciutatService;
    
      @GetMapping
      public List<CiutatDTO> obtenirTotes() {
          return ciutatService.obtenirTotesLesCiutats();
      }
    
      @GetMapping("/{id}")
      public CiutatDTO obtenirPerId(@PathVariable Long id) {
          return ciutatService.obtenirCiutatPerId(id);
      }
    }
    

En definitiva:

  • Si volem una API simple: Farem el Controller que cride directament el Repository (opció 1).
  • Si volem modularitat i escalabilitat: Separa en Service + Repository (opció 2).
  • Si vols protegir la BD i evitar problemes: Usa DTOs per encapsular les dades (opció 3).

Recomanació: Per a un projecte seriós i escalable, Capa de Servei + DTOs és la millor opció.