chore: retirar GraphQL — REST como única superficie (implementa D-007) - #19
Merged
Conversation
La decisión NO es nueva. D-007, del 2026-07-14, estableció «API REST-only con CQRS en la capa de aplicación», revisando expresamente ADR-0055 y ADR-0059. La documentación de referencia lo recogió entonces y lo sigue diciendo: la arquitectura de solución afirma que «GraphQL fue retirado», los objetivos de calidad movieron su SLO de lectura de GraphQL a `GET`, el modelo de amenazas retiró el vector de DoS por GraphQL y el PRD declara la API REST-only. Lo que nunca ocurrió fue el cambio en el código. Un mes después seguían en pie el esquema, el endpoint `/graphql`, HotChocolate y 17 clases de consulta; y ADR-0055 y ADR-0059 seguían en estado Aceptado, contradiciendo a D-007 sin que nada lo señalara. Este commit implementa D-007 y ADR-0166 escribe el registro que le faltaba. ## Por qué se pudo pasar por alto: TD-010 El registro de decisiones vive en un `DECISIONS.md` que NO está en este repositorio, y nunca ha estado. Los 117 enlaces a `../../DECISIONS.md` y `../../GAPS.md` que hay en `docs/` apuntan a la nada. Quien pregunte «¿se decidió algo sobre GraphQL?» mirando los ADRs concluye que no, porque 0055 y 0059 dicen Aceptado. Queda registrado como TD-010: un registro de decisiones fuera del repositorio que gobierna vuelve a divergir, y esta es la evidencia. ## La retirada es borrado puro: REST ya cubría el 100% No se ha escrito ni un endpoint. Existe una carpeta `Endpoints/**/Queries/` paralela con 22 grupos REST de consulta frente a las 17 clases GraphQL, y cada consulta del esquema tiene gemela que envía la MISMA query de MediatR. El GET de identidad visual ya estaba en `Queries/BrandingQueryEndpoints`; el resolver y el endpoint enviaban ambos `GetBrandingByTenantIdQuery`. Esa carpeta paralela es también por qué el primer sondeo de este trabajo concluyó —mal— que había que añadir un GET: se miró el fichero de comandos y no el de consultas. ## Alcance Backend: las 17 clases de consulta, GraphQlServiceCollectionExtensions, SafeGraphQlErrorFilter, TenantContextGraphQlInterceptor, QueryResultExtensions, el paquete HotChocolate.AspNetCore, el endpoint en sus dos rutas, dos pruebas de integración y el bloque S2325 del .editorconfig que existía por la reflexión de HotChocolate. Front: graphqlClient y los nueve módulos `*.graphql.ts` con sus pruebas. Ocho ya eran código muerto; el noveno, `branding`, pasa a REST. `getBranding` colapsa a `null` tanto el 200 con cuerpo vacío (el inquilino existe sin identidad visual) como el 404 (el inquilino no existe), igual que hacía el camino GraphQL; cualquier otro error se propaga. Documentación: ADR-0166 en ambos idiomas, ADR-0055 marcado Reemplazado, ADR-0059 anotado —su decisión era co-ubicar dos superficies y queda una—, ADR-0066 con su cláusula de GraphQL marcada como no aplicable, TE-07 sin su ruta `/graphql`, la vista de arquitectura actualizada y TD-002 cerrado por desaparición del sujeto. El GET de identidad visual no tenía ninguna prueba: se le añaden tres, incluida la de leer lo que se acaba de escribir. Verificado: build 0 errores; Domain 930/930; Application 934/934; Contrato 38/38; Integración 314/316 (2 saltados); front typecheck 0, vitest 1626/1626, build OK, eslint 0. En vivo: `/graphql` y `/api/v1/graphql` responden 404, health 200 y el GET REST de identidad visual devuelve lo que el POST acaba de escribir. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…undía 133 en CI La prueba «leer lo que el POST acaba de escribir» que añadí escribía identidad visual sobre el primer inquilino sembrado. Las 31 clases de esta suite comparten `UmsApiWebApplicationFactory` y su store, así que esa escritura contaminaba a todas las demás: 133 de 316 en rojo en CI. En local pasaba, en Debug y en Release. El orden de ejecución lo escondía. Una escritura añadida, 133 fallos, y una corrida local verde: esa combinación es exactamente lo que hace caro este tipo de defecto. Se retira la prueba mutante y quedan las dos de lectura, que son deterministas. El camino de escritura ya está cubierto en Ums.Application.Test/Tenants/Branding, a nivel de handler y sin estado compartido. La razón queda escrita en el propio fichero para que nadie la reañada y repita los 133. Registrado como TD-011: no es la primera vez. Los comentarios de la propia factoría documentan un incidente anterior de ~80 fallos por un constructor estático que filtraba `Persistence__Provider` entre hosts. El mecanismo cambia, la forma no. Verificado: Integración 313/315 (2 saltados) en Release. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Opción 1 de las tres que planteé: retirar GraphQL. −3.485 líneas, 44 ficheros borrados, ningún endpoint nuevo.
La decisión ya existía: D-007
Preguntaste si no habíamos descartado GraphQL antes. Sí, y yo respondí mal. Miré los ADRs, vi 0055 y 0059 en Aceptado, y dije que no había decisión.
D-007, del 2026-07-14, estableció «API REST-only con CQRS en la capa de aplicación», revisando expresamente ADR-0055 y ADR-0059. La documentación de referencia lo recogió entonces y lo sigue diciendo:
solution-architecture.es.md— «GraphQL fue retirado»quality-objectives.es.md— SLO de lectura movido de GraphQL aGETthreat-model.es.md— vector de DoS por GraphQL retiradoprd-ums-001.md— API declarada REST-onlyLo que nunca ocurrió fue el cambio en el código. Un mes después seguían en pie el esquema,
/graphql, HotChocolate y las 17 clases de consulta. Y los dos ADRs seguían diciendo lo contrario, sin que nada lo señalara.Este PR implementa D-007, y ADR-0166 escribe el registro ADR que le faltaba.
Por qué se pudo pasar por alto: TD-010 (nuevo)
El registro de decisiones vive en un
DECISIONS.mdque no está en este repositorio y nunca ha estado (git log --allno encuentra nada). Los 117 enlaces a../../DECISIONS.mdy../../GAPS.mdque hay endocs/apuntan a la nada.Los registros se citan como autoridad —objetivos de calidad, modelo de amenazas y PRD delegan en
D-007/D-008para decir qué es la arquitectura—. Quien no puede abrirlos no puede comprobar si ADRs, prosa y código coinciden. Aquí no coincidieron durante un mes.Registrado como TD-010, con la recomendación de traer el registro al repositorio que gobierna.
La retirada es borrado puro: REST ya cubría el 100%
No se ha escrito ni un endpoint. Hay una carpeta
Endpoints/**/Queries/paralela con 22 grupos REST de consulta frente a las 17 clases GraphQL, y cada consulta del esquema tiene gemela que despacha la misma query de MediatR.Alcance
Backend — 17 clases de consulta,
GraphQlServiceCollectionExtensions,SafeGraphQlErrorFilter,TenantContextGraphQlInterceptor,QueryResultExtensions, el paqueteHotChocolate.AspNetCore, el endpoint en sus dos rutas, dos pruebas de integración y el bloqueS2325del.editorconfigque sólo existía por la reflexión de HotChocolate.Front —
graphqlClienty los nueve*.graphql.tscon sus pruebas. Ocho ya eran código muerto; el noveno,branding, pasa a REST.getBrandingcolapsa anulltanto el 200 con cuerpo vacío (inquilino sin identidad visual) como el 404 (inquilino inexistente), igual que hacía GraphQL; cualquier otro error se propaga.Documentación — ADR-0166 bilingüe; ADR-0055 marcado Reemplazado; ADR-0059 anotado (su decisión era co-ubicar dos superficies y queda una, pero el rechazo a separarlas en tiers sigue vigente); ADR-0066 con su cláusula de GraphQL marcada como no aplicable y conservada como registro; TE-07 sin su ruta
/graphql; vista de arquitectura actualizada; TD-002 cerrado.TD-002 se cierra por desaparición del sujeto, no por arreglo. Nunca se encontró la causa raíz de por qué
appConfigurationsdevolvía vacío. Si algún día se reintroduce GraphQL, esa entrada merece releerse antes.Cobertura nueva
El GET de identidad visual —ahora el único camino de lectura— no tenía ninguna prueba. Se le añaden tres, incluida leer lo que el POST acaba de escribir.
Verificación
dotnet buildUms.Domain.TestUms.Application.TestUms.ContractTestUms.Presentation.IntegrationTestEn vivo, contra la API arrancada:
POST /graphql→ 404,POST /api/v1/graphql→ 404,GET /health/live→ 200, y elGETREST de identidad visual devuelve lo que elPOSTacaba de escribir.🤖 Generated with Claude Code