Convenciones de la capa web

La capa web del arquetipo fija decisiones que todos los endpoints comparten: prefijo de versión, headers obligatorios, forma de la respuesta paginada, contrato de validación y forma de los errores. Esta página documenta cada una con el código exacto del arquetipo.

Path y versionado

El prefijo global es API_PATH_PREFIX, definido en utils/constants/ApiConstants:

public static final String API_PATH_PREFIX = "/{apiBasePath}";

{apiBasePath} es una propiedad del arquetipo definida al generar el proyecto.

Los controladores declaran solo la parte versionada del recurso:

@RequestMapping("/v1/tasks")

WebConfig.configurePathMatch antepone el prefijo a todos los controladores del paquete base:

configurer.addPathPrefix(API_PATH_PREFIX, HandlerTypePredicate.forBasePackage("..."));

URL resultante: /{apiBasePath}/v1/{recurso}. Las bumps mayores de contrato suben a /v2, /v3 y conviven con la versión previa hasta deprecación.

Headers globales

Cada operación REST exige los siguientes headers. Las constantes viven en utils/constants/HeaderConstants; OpenAPIConfig.globalHeaderCustomizer los inyecta en cada Operation del documento OpenAPI, así que los controladores no los declaran individualmente.

Header Obligatorio Pattern Propósito

X-Tenant-Id

UUID v4

Resuelve el tenant del request. TenantContextFilter lo lee, pregunta al TenantProvider, y deja el Tenant en el TenantContextHolder mientras dura el request.

X-Device-Id

No

[A-Za-z0-9-:_]{2,300}

Identifica el dispositivo origen.

X-Platform

No

(AND|IOS|WEB)

Plataforma cliente.

X-Application

No

(CUS|COM|EXP|APE|JKR|MRK|BACKOFFICE)

Aplicación cliente.

Sin X-Tenant-Id el filtro no setea el contexto. Cualquier código aguas abajo que llame a TenantContextHolder.get() recibe null y los datasources multitenant fallan al no encontrar tenant. Un UUID malformado en el header produce IllegalArgumentException desde UUID.fromString(…​), que GlobalExceptionHandler traduce a HTTP 400.

Paginación

Las queries paginadas reciben un Pageable como último parámetro y devuelven PageResponse<T> (commons/adapter/in/web/PageResponse):

@GetMapping
ResponseEntity<PageResponse<TaskResponse>> list(Pageable pageable) {
    Page<Task> page = taskRetrievalPort.findAll(pageable);
    return ResponseEntity.ok(PageResponse.of(page.map(TaskResponse::of)));
}

PageResponse.of(Page<T>) aplana metadata y contenido a una forma JSON estable:

{
  "page": {
    "number": 0,
    "size": 20,
    "totalElements": 137,
    "totalPages": 7,
    "empty": false
  },
  "items": [ /* ... */ ]
}

Los parámetros estándar de Spring (page, size, sort) se aceptan como query params sin configuración adicional.

Validación

El binding usa Bean Validation (jakarta.validation.constraints.*) sobre los DTOs de request, activado por @Valid en el handler:

@PostMapping
ResponseEntity<String> create(@RequestBody @Valid CreateTaskRequest request) { /* ... */ }

Una violación de constraints dispara MethodArgumentNotValidException. GlobalExceptionHandler.handleMethodArgumentNotValid la traduce a HTTP 400 con ProblemDetail y la lista violations:

{
  "type": "about:blank",
  "title": "Bad Request",
  "status": 400,
  "detail": "Request validation failed.",
  "violations": [
    { "field": "title", "message": "must not be blank" }
  ]
}

IllegalArgumentException lanzada manualmente (validación que no encaja en una anotación) también mapea a 400 — sin violations, con detail igual al mensaje de la excepción.

Errores

Todas las respuestas de error siguen RFC 7807 (ProblemDetail). El mapeo está en web/advice/GlobalExceptionHandler:

Origen Forma

ApplicationException(<Aggregate>Error, args…​)

status lo determina el enum. detail se resuelve por MessageSource (i18n). properties.code contiene el nombre del error.

IllegalArgumentException

HTTP 400. detail = mensaje crudo. Sin code.

MethodArgumentNotValidException

HTTP 400. detail = "Request validation failed.". properties.violations con field + message por cada constraint.

HttpMessageNotReadableException (JSON malformado, enum inválido)

HTTP 400. properties.violations con los valores permitidos del enum cuando aplica.

ConstraintViolationException

HTTP 400.

DataIntegrityViolationException

HTTP 409.

EntityNotFoundException

HTTP 404.

IllegalStateException

HTTP 500.

Para errores de negocio (invariante violada, recurso no encontrado) el patrón es lanzar ApplicationException con un ApplicationError enum específico del aggregate. No subclasear Exception.

Ver también