03 - Partida: la tienda por capas
El punto de arranque de la práctica se construye como se sabía hacer al terminar 1.º DAW — Spring Boot, arquitectura por capas, persistencia JDBC sobre MariaDB y un único endpoint — con lo que el tema 01 — Arquitecturas de software añade a esa arquitectura: la inversión de dependencias del repositorio, con la interfaz en domain y la implementación en persistence. El resto de la API no existe todavía: en el hito 04 se diseña antes de implementar. Que el arranque se haya hecho primero en código es lo que da sentido a ese cambio.
Alcance
| Pieza | Alcance |
|---|---|
| Dominio | Solo el catálogo: el modelo Product, que referencia su catálogo por catalogId |
| Capas | controller → domain → persistence; la interfaz del repositorio en domain |
| Persistencia | JDBC: repositorio sobre un DAO con entidad, como en 1.º DAW |
| API | Un único endpoint: GET /api/products |
| Pruebas | Manuales (curl); el primer criterio de pruebas llega en el hito 05 |
1. Proyecto
- Proyecto Maven de Spring Boot 4 con el starter
webmvcy el driver JDBC de MariaDB, sobre JDK 25. - Estructura inicial por capas (paquete base de la especificación):
es.cesguiro.shop
├── ShopApplication.java
├── controller
│ └── ProductController.java
├── domain
│ ├── Product.java
│ ├── ProductService.java
│ ├── ProductServiceImpl.java
│ └── ProductRepository.java
└── persistence
├── ProductRepositoryImpl.java
├── ProductDao.java
├── ProductDaoJdbc.java
└── ProductEntity.java
domaincontiene el modeloProduct— sus campos son los de la tablaproductsde la semilla —, el servicio con su interfaz y su implementación y la interfaz del repositorio que necesita.persistenceimplementa esa interfaz sobre el DAO: el DAO consulta la base con JDBC y trabaja con su entidad; la implementación del repositorio traduce la entidad al modelo.
2. MariaDB con Docker
El compose.yaml se reutilizará en todo el curso:
services: db: image: mariadb:12 environment: MARIADB_DATABASE: shop MARIADB_USER: shop MARIADB_PASSWORD: shop MARIADB_ROOT_PASSWORD: root ports: - "3306:3306" volumes: - ./db:/docker-entrypoint-initdb.d
El db/init.sql crea las tablas — los catálogos primero, porque los productos los referencian — y deja la semilla de la tienda, con suficientes productos para que la paginación del hito 04 tenga sentido:
CREATE TABLE catalogs ( id BIGINT AUTO_INCREMENT PRIMARY KEY, name VARCHAR(80) NOT NULL UNIQUE, description VARCHAR(240) NOT NULL ); CREATE TABLE products ( id BIGINT AUTO_INCREMENT PRIMARY KEY, catalog_id BIGINT NOT NULL, name VARCHAR(80) NOT NULL, description VARCHAR(240) NOT NULL, price DECIMAL(8,2) NOT NULL, stock INT NOT NULL, FOREIGN KEY (catalog_id) REFERENCES catalogs (id) ); INSERT INTO catalogs (name, description) VALUES ('Periféricos', 'Entrada y salida para el escritorio'), ('Pantallas', 'Monitores y proyección'), ('Accesorios', 'El resto del escritorio'); INSERT INTO products (catalog_id, name, description, price, stock) VALUES (1, 'Teclado mecánico', 'Mecánico, inalámbrico', 54.90, 20), (1, 'Ratón vertical', 'Ergonómico, 6 botones', 39.95, 35), (1, 'Ratón inalámbrico', 'Silencioso, 2,4 GHz', 24.90, 40), (1, 'Trackball', 'Cursor con pulgar', 44.50, 15), (1, 'Teclado numérico', 'Inalámbrico, 19 teclas', 29.90, 18), (1, 'Panel de control', '15 teclas programables', 149.00, 8), (1, 'Micrófono USB', 'Cardioide, con soporte', 69.00, 18), (1, 'Webcam 1080p', 'Full HD, enfoque automático', 49.90, 25), (1, 'Webcam 4K', 'UHD, HDR', 129.00, 10), (1, 'Auriculares con micro', 'Diadema, cancelación', 59.95, 30), (1, 'Altavoces 2.1', 'Subwoofer, 40 W', 79.00, 12), (1, 'Auriculares gaming', 'Inalámbricos, 7.1', 89.90, 14), (2, 'Monitor 27"', 'IPS, 75 Hz', 189.00, 12), (2, 'Monitor 24"', 'IPS, 100 Hz', 139.00, 20), (2, 'Monitor 32" 4K', 'UHD, 60 Hz', 329.00, 7), (2, 'Monitor 34" ultrawide', 'Curvo, 100 Hz', 449.00, 5), (2, 'Monitor 25" de oficina', 'IPS, pivotante', 175.00, 16), (2, 'Monitor curvo 27"', 'VA, 165 Hz', 259.00, 9), (2, 'Monitor portátil', '15,6", USB-C', 219.00, 11), (2, 'Proyector compacto', '1080p, 4000 lúmenes', 499.00, 4), (3, 'Base portátil', 'Aluminio, ventilación', 24.50, 50), (3, 'Alfombrilla XXL', '90 x 40 cm, bordes cosidos', 19.95, 60), (3, 'Hub USB-C', '7 en 1, HDMI y Ethernet', 45.00, 28), (3, 'Cable HDMI 2.1', '2 m, 8K', 14.90, 45), (3, 'Cable USB-C', '1 m, 100 W', 12.50, 55), (3, 'Soporte de monitor', 'Brazo doble, gas', 89.00, 10), (3, 'Lámpara de escritorio', 'LED, con cargador', 35.90, 22), (3, 'Organizador de cables', 'Felpa con velcro', 9.90, 80);
La conexión se declara en application.properties:
spring.datasource.url=jdbc:mariadb://localhost:3306/shop spring.datasource.username=shop spring.datasource.password=shop
3. Persistencia
La capa de persistence funciona como en 1.º DAW: el DAO consulta con PreparedStatement y mapea el ResultSet a su entidad, y la implementación del repositorio traduce la entidad al modelo. La relación con los catálogos, por ahora, es solo el catalogId del producto: la integridad la pone la clave ajena, no el código.
4. Endpoint
ProductController expone GET /api/products y devuelve el listado en JSON. Nada de status codes especiales para errores: el contrato de esta API es, por ahora, «lo que hace el código».
Comprobación
docker compose up -dlevanta MariaDB y la semilla se carga: 3 catálogos y 28 productos.curl http://localhost:8080/api/productsdevuelve el JSON del catálogo con los veintiocho productos, cada uno con sucatalogId.- El código queda organizado en las tres capas:
domaindefine la interfaz del repositorio y no menciona JDBC;persistencela implementa sobre el DAO. - Commit y tag
partidaen el repositorio.
Siguiente hito: hito 04 — API First.