02 - Especificación de la tienda

Especificación funcional de la tienda, la aplicación de la práctica guiada. Es deliberadamente pequeña: los hitos se concentran en la organización y los mecanismos, no en la amplitud del dominio. Esta página describe el estado final (tras el hito 09); cada hito se acerca a él.

Una tienda mínima: los productos se organizan en catálogos; los clientes compran productos de un catálogo, cada pedido reserva las unidades que necesita y su cancelación las libera. Los usuarios se registran, inician sesión y ven sus pedidos; un rol de administración gestiona catálogos y productos.

shopcatalogordersusersShopApplicationREST: catálogos y productosdominio y persistencia propiosCatalogApiREST: pedidosPlaceOrder / CancelOrderdominio y persistencia propiosREST: registro y logintokens y persistencia propiosreserva y libera stock(colaboración síncrona)la identidad del compradorviaja en los claims del token

Los módulos no se conocen internamente: orders consume la API pública de catalog; la identidad del comprador llega en el token, así que orders no necesita nada de users.

Módulo Responsabilidad Desde
catalog CRUD de catálogos y productos con listado paginado; reservar y liberar stock partida (el hito 06 organiza el módulo)
orders crear, consultar y cancelar pedidos hito 06
users registro, login con JWT, roles USER y ADMIN hito 08
Tabla Campos
catalogs id, name (único), description
products id, catalog_id (FK), name, description, price, stock
orders id, user_id, state, created_at
order_lines id, order_id, product_id, quantity, unit_price
users id, name, email (único), password_hash, role
refresh_tokens id, token_hash, user_id, expires_at, revoked

Notas:

  • Todo producto pertenece a un catálogo; el borrado de un catálogo con productos se rechaza.
  • unit_price guarda el precio capturado al reservar el stock, no el precio actual del producto.
  • refresh_tokens guarda el hash del token, nunca el token en claro.
  • El estado del pedido se guarda como cadena: CREATED / CANCELLED.
Módulo Regla
catalog Todo producto pertenece a un catálogo existente (404 si no existe)
catalog El nombre del catálogo es único (409 si se repite al crear o modificar)
catalog No se puede borrar un catálogo con productos (409)
catalog El precio es mayor que cero y de dos decimales (value object Money)
catalog El stock no es negativo (value object Stock); reservar decrementa, liberar incrementa
orders Un pedido tiene al menos una línea; la cantidad de cada línea es 1 o más
orders Crear un pedido reserva el stock de todas sus líneas; si una falla, no queda nada reservado
orders El total es la suma de cantidad por precio de línea
orders Estado inicial CREATED; única transición: a CANCELLED
orders Cancelar libera el stock de todas sus líneas; cancelar un pedido ya cancelado es un error
users El email es único (error 409 si se repite al registrar)
users La contraseña se guarda como hash; longitud mínima de 8 en el contrato
users El registro crea siempre USER; el rol ADMIN existe solo por semilla

Provisional hasta el hito 08: la petición de creación de pedidos lleva el comprador en el cuerpo (userId), porque no hay usuarios todavía. En el hito 08 el identificador viene del token y ese campo desaparece del contrato.

Estado final, desde el hito 09:

Operación Público USER ADMIN
Lectura de catálogos y productos (GET) ✔ ✔ ✔
Escritura de catálogos y productos (POST, PUT, DELETE) ✖ ✖ ✔
Crear pedido ✖ ✔ ✔
Ver o cancelar pedido ✖ el propio cualquiera
Mis pedidos ✖ propios propios
Registro, login, refresh, logout ✔ ✔ ✔

Spring Security resuelve las filas por rutas; la pertenencia del pedido (propietario o ADMIN) se comprueba en el módulo de pedidos, y esa regla se expresa con autorización por método.

Endpoint Aparece Cambios posteriores
GET /api/products partida desaparece en el 04: lo sustituye el listado paginado por catálogo
GET /api/catalogs 04 —
GET /api/catalogs/{id} 04 —
POST /api/catalogs 04 protegido desde el 09
PUT /api/catalogs/{id} 04 protegido desde el 09
DELETE /api/catalogs/{id} 04 protegido desde el 09
GET /api/catalogs/{id}/products 04 paginado (page y size) desde que nace
GET /api/products/{id} 04 —
POST /api/products 04 protegido desde el 09
PUT /api/products/{id} 04 protegido desde el 09
DELETE /api/products/{id} 04 protegido desde el 09
POST /api/orders 06 pierde userId en el 08; protegido desde el 09
GET /api/orders/{id} 06 protegido desde el 09
POST /api/orders/{id}/cancel 06 protegido desde el 09
GET /api/me/orders 08 —
POST /api/users 08 —
POST /api/auth/login 08 —
POST /api/auth/refresh 08 —
POST /api/auth/logout 08 —

Los errores de la API usan el mismo esquema Problem (RFC 9457) en todas las operaciones: 400 (formato), 404 (recurso inexistente, incluido el catálogo de un producto), 409 (nombre de catálogo repetido, catálogo con productos, stock insuficiente, email repetido, pedido ya cancelado), 401 y 403 (seguridad).

Tema Flujo previsto en la tienda
08 — eventos Aviso de stock bajo al reservar y factura al cancelar: son operaciones auxiliares, fuera del flujo principal; encajan como eventos
09 — logs Correlación de peticiones con MDC, registro a fichero con rotación y Actuator
10 — Lambda Informe de ventas del mes (CSV) como función desplegada en local
11 — concurrencia Análisis del modelo de concurrencia de la tienda; sin cambios de código
Decisión Valor
Paquete base es.cesguiro.shop (cada quien adapta el suyo)
Identificadores del código en inglés
Títulos de los errores del contrato en inglés (Not Found, Invalid request…)
Base de datos MariaDB (imagen mariadb:12); puerto local 3306
Pruebas de persistencia Testcontainers con la misma imagen de MariaDB (desde el hito 07)
Tags del repositorio partida y hito-04 … hito-09
Versión del contrato la tabla de la práctica
  • clase/daw/dws/1eval/practicas/p2.txt
  • Última modificación: 2026/10/30 17:48
  • por cesguiro