====== 05 - Arquitectura por capas ======
La arquitectura de capas es un patrón de diseño de software que organiza el sistema en diferentes niveles o capas. Cada capa tiene una responsabilidad clara y definida, y las capas interactúan entre sí de manera jerárquica. Las capas superiores utilizan los servicios ofrecidos por las capas inferiores, pero las capas inferiores no dependen de las superiores.
Se caracteriza por la separación de responsabilidades, donde cada capa está encargada de una funcionalidad específica del sistema. Esto permite una mejor organización del código y facilita la mantenibilidad y escalabilidad del mismo.
Además, presenta ventajas como la reutilización de código y la posibilidad de realizar pruebas de manera más efectiva. Sin embargo, también tiene desventajas, como una posible sobrecarga en la comunicación entre capas y una complejidad adicional en la gestión de las interacciones.
===== Capas =====
Las capas básicas de la arquitectura de la aplicación web son la capa de **presentación**, la capa de **dominio** y la capa de **persistencia**.
La capa de **presentación** (en nuestro caso le llamaremos **controller**, ya que es dónde estarán nuestros controladores) es responsable de interactuar con el usuario. Aquí es donde se manejan las solicitudes y respuestas, y se presenta la información de una manera comprensible. En esta capa, se implementan los controladores que gestionan la lógica de la interfaz y coordinan la interacción entre el usuario y el sistema.
La capa de **dominio** contiene la lógica de negocio de la aplicación. Está compuesta por los servicios que procesan la información y las entidades del modelo que representan los datos fundamentales. Esta capa se encarga de las reglas de negocio y la validación de los datos antes de que sean enviados a la capa de persistencia.
La capa de **persistencia** se encarga de la gestión del almacenamiento de datos. Aquí se implementan los repositorios que facilitan la interacción con los datos, permitiendo realizar operaciones de creación, lectura, actualización y eliminación (CRUD) sobre las entidades del modelo.
@startuml
package "controller" #A5D6A7{
}
package "domain" #EF5350{
}
package "persistence" #90CAF9{
}
database datos
controller -down-> domain
domain -down-> persistence
persistence -> datos
@enduml
Además de estas capas básicas, es posible incorporar más capas según las necesidades de la aplicación. Por ejemplo, se pueden incluir capas de servicio adicional para la integración con servicios externos, capas de presentación para distintos tipos de interfaces (como API REST y aplicaciones web), o capas de configuración y seguridad. Esta flexibilidad permite adaptar la arquitectura a los requisitos específicos de cada proyecto.
En el tema anterior, cuando hablamos de las arquitecturas limpias vimos que el núcleo del sistema debería estar compuesto por los modelos y las reglas de negocio y que debería ser independiente de los detalles tecnológicos, como bases de datos, frameworks... \\ \\
En el esquema anterior, ésto no se cumple, ya que, en realidad, todo depende de la última capa, la de persistencia. A lo largo de curso, ya iremos viendo mecanismos para solucionar esta situación
===== Capa de presentación =====
La capa de presentación es responsable de interactuar con el cliente y gestionar las solicitudes y respuestas de la aplicación.
Los controladores reciben las solicitudes HTTP, procesan los datos y devuelven respuestas adecuadas. Utilizan los servicios de la capa de dominio para llevar a cabo la lógica de negocio necesaria.
En //Spring// es habitual tener en esta capa nuestros controladores y el sistema de enrutamiento. Es común utilizar anotaciones en esta capa, como:
* **@RestController**: Esta anotación indica que la clase es un controlador y que las respuestas de los métodos se serializan automáticamente en formato JSON o XML.
* **@RequestMapping**: Se utiliza para mapear las solicitudes HTTP a los métodos del controlador.
* **@GetMapping**, **@PostMapping**, **@PutMapping**, **@DeleteMapping**: Estas anotaciones específicas permiten definir los métodos que manejarán las solicitudes HTTP correspondientes a las operaciones de lectura, creación, actualización y eliminación, respectivamente.
@RestController
@RequestMapping(BookController.URL)
@RequiredArgsConstructor
public class BookController {
public static final String URL = "/books";
private final BookService bookService;
@GetMapping
public List getAll() {
return bookService.getAll();
}
@GetMapping("/{isbn}")
public Book findByIsbn(@PathVariable String isbn) {
return bookService.findByIsbn(isbn);
}
}
Por ejemplo, los métodos //getAll()// y //findByIsbn()// manejan las solicitudes para obtener todos los libros y para encontrar un libro específico por su ISBN, respectivamente. Ambos métodos utilizan el servicio //BookService// para realizar la lógica de negocio, manteniendo así una clara separación entre la presentación y la lógica del dominio.
===== Capa de dominio =====
Es el componente principal de nuestra aplicación, donde estarán nuestras modelos y reglas de negocio.
Esta capa no debería tener ninguna dependencia con componentes externos, con lo que deberemos buscar mecanismos para evitar introducir referencias a Spring, bbdd, JSON...
public class Book {
package es.cesguiro.model;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.time.LocalDate;
import java.util.List;
public class Book {
private String isbn;
private String titleEs;
private String titleEn;
private String synopsisEs;
private String synopsisEn;
private BigDecimal basePrice;
private double discountPercentage;
private BigDecimal price;
private String cover;
private LocalDate publicationDate;
private Publisher publisher;
private List authors;
// Constructores, getters, setters, lógica de negocio, validaciones...
}
package es.cesguiro.service;
import java.util.List;
import java.util.Optional;
public interface BookService {
List getAll(int page, int size);
//...
}
package es.cesguiro.service.impl;
import es.cesguiro.repository.BookRepository;
import es.cesguiro.service.BookService;
import java.util.List;
import java.util.Optional;
public class BookServiceImpl implements BookService {
private final BookRepository bookRepository;
public BookServiceImpl(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
@Override
public List getAll(int page, int size) {
return bookRepository.findAll(page, size);
}
// ...
}
===== Capa de persistencia =====
La capa de persistencia es responsable de gestionar la interacción con el origen de datos (base de datos, ficheros, APIs...), permitiendo el almacenamiento y recuperación de los mismos.
public interface BookRepository {
List getAll();
}
public BookRepositoryImpl {
List getAll() {
// Conectar con el origen de datos de nuestra aplicación
// Recuperar y devolver listado de libros
}
}
En realidad, como veremos más adelante, los repositorios (concretamente las interfaces de los repositorios) pertenecen a la capa de dominio, ya que utilizaremos **inversión de dependencias** para que sea la capa de persistencia la que dependa de dominio