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.

El objetivo es un punto de partida conocido y funcional, no una aplicación completa. Lo que falta se construye en cada hito, consultando la especificación. En todos los hitos, la página fija el alcance y los criterios; el diseño del código — trabajando con la IA — queda a decisión de cada quien.
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 Maven de Spring Boot 4 con el starter webmvc y el driver JDBC de MariaDB, sobre JDK 25.
  2. 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
  • domain contiene el modelo Product — sus campos son los de la tabla products de la semilla —, el servicio con su interfaz y su implementación y la interfaz del repositorio que necesita.
  • persistence implementa 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.
El listado plano es deliberadamente ingenuo: con veintiocho productos en una sola respuesta ya se queda corto, y no hay forma de navegar por catálogos. El hito 04 lo sustituye por catálogos navegables con listado paginado: será la primera decisión que tome el contrato sobre el código.

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

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.

El precio y el stock son números «sueltos»; las invariantes del dominio (precio positivo, stock no negativo) aún no existen. Llegan como value objects en el hito 05.

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».

  • docker compose up -d levanta MariaDB y la semilla se carga: 3 catálogos y 28 productos.
  • curl http://localhost:8080/api/products devuelve el JSON del catálogo con los veintiocho productos, cada uno con su catalogId.
  • El código queda organizado en las tres capas: domain define la interfaz del repositorio y no menciona JDBC; persistence la implementa sobre el DAO.
  • Commit y tag partida en el repositorio.

Siguiente hito: hito 04 — API First.

  • clase/daw/dws/1eval/practicas/p3.txt
  • Última modificación: 2026/10/30 17:23
  • por cesguiro