Este arquivo é um registro histórico de arquitetura. As fases 1 a 6
descritas aqui já foram implementadas no
praxis-metadata-starter.
Leia este plano como:
Não leia este arquivo como backlog ativo de onboarding. Para a
narrativa pública atual, use README.md,
docs/architecture-overview.md e
docs/guides/**.
Nao ha compromisso com compatibilidade retroativa. A estrategia correta e substituir o nucleo conceitualmente errado, e nao empilhar uma camada “V2” sobre o legado.
As Fases 1 a 6 deste plano ja foram implementadas no
praxis-metadata-starter.
Estado consolidado atual:
resource-oriented novo consolidadosurfaces consolidadoactions consolidadocapabilities unificadas consolidadassrc/test consolidadoResiduos conhecidos, nao bloqueantes para abrir o primeiro consumidor externo:
e2e-pg com PostgreSQL/Testcontainers ainda
pendenteCapabilitySnapshot.group continua significando o grupo
OpenAPI canonico resolvido por resourcePathAntes de migrar um host real, o baseline esperado do starter e:
src/test verdeO proximo passo canonico, portanto, nao e mais evolucao interna do starter por fase. E a migracao controlada do primeiro consumidor externo sobre este baseline.
O ponto que permanece canonico no repo e:
@ApiResourceGET /schemas/filtered como resolvedor estrutural
canonicoGET /schemas/catalog como catalogo resumido de
operacoesoption-sources,
stats e capabilities derivadas do OpenAPIO ponto que deve ser substituido e o core baseado em:
AbstractCrudControllerBaseCrudServiceAbstractBaseCrudServiceAbstractReadOnlyControllerO starter deve passar a ter tres eixos distintos:
resource-oriented Contrato estrutural canonico, query,
create, update, partial update por intencao e schemas canonicamente
resolvidos.surface-oriented Discovery semantico de formularios,
views parciais, projecoes e UX contextual. Nao define payload; apenas
referencia operacao real, schemaId e URL de
/schemas/filtered.action-oriented Workflows e comandos de negocio
explicitos, com endpoints tipados e DTOs proprios. Tambem nao define
payload inline no catalogo.resource continua sendo a fonte canonica de
contrato.surface nunca define campos, validacoes ou payloads
inline.action nunca substitui endpoint real tipado.operationId,
schemaId e schemaUrl.schemaId e schemaUrl devem ser sempre
derivados de /schemas/filtered.surface organiza experiencia contextual; nao substitui
resource intent.PATCH /{id} foi tratado neste plano como opcao
arquitetural, mas nao faz parte do baseline atual do starter. Hoje o
core canonico publica PUT /{id} como update base e usa
PATCH apenas por intencao explicita.resourcePath nao deve ser a identidade canonica do
recurso. O modelo interno deve usar um identificador estavel, como
resourceKey, e tratar a URL como dado derivado.PATCH /{resource}/{id}/intents/{intentId} ou
POST /{resource}/{id}/actions/{actionId} sem controllers
tipados reais por operacao.Hoje o starter ainda concentra leitura e escrita no mesmo DTO central
D, via
AbstractCrudController<E, D, ID, FD> e
BaseCrudService<E, D, ID, FD>.
Isso aperta o modelo para:
ResponseDTO, CreateDTO e
UpdateDTO diferentesapprove, reject,
resubmitGET /{id} retorna ResponseDTOPOST / recebe CreateDTOPUT /{id} recebe UpdateDTO se a semantica
for substituicao completaPATCH /{id} permanece fora do baseline atual do
starter; se um dia entrar como update base, isso deve ser tratado como
decisao canonica nova de plataformaPATCH /{id}/profile,
PATCH /{id}/bank-details etc. recebem DTOs parciais
nomeados por intencaoAs secoes abaixo preservam a sequencia historica que levou ao estado atual do starter. Elas nao devem ser lidas como backlog ativo; o backlog ativo para a proxima etapa esta nos guias e checklists pre-piloto.
Criar a fundacao reutilizavel para:
ApiDocsControllerDomainCatalogControllerorg.praxisplatform.uischema.openapi
org.praxisplatform.uischema.schema
public record CanonicalOperationRef(
String group,
String operationId,
String path,
String method
) {}public record CanonicalSchemaRef(
String schemaId,
String schemaType,
String url
) {}public interface CanonicalOperationResolver {
CanonicalOperationRef resolve(HandlerMethod handlerMethod, RequestMappingInfo mappingInfo);
CanonicalOperationRef resolve(String path, String method);
Optional<CanonicalOperationRef> resolveByOperationId(String operationId);
}public interface SchemaReferenceResolver {
CanonicalSchemaRef resolve(
String path,
String method,
String schemaType,
boolean includeInternalSchemas,
String tenant,
Locale locale,
String idField,
Boolean readOnly
);
}public interface OpenApiDocumentService {
String resolveGroupFromPath(String path);
JsonNode getDocumentForGroup(String group);
String getOrComputeSchemaHash(String schemaId, Supplier<JsonNode> payloadSupplier);
void clearCaches();
}ApiDocsController passa a usar
OpenApiDocumentService,
CanonicalOperationResolver e
SchemaReferenceResolverDomainCatalogController passa a usar os mesmos
servicosDynamicSwaggerConfig permanece como base para scanning
e resolucao de gruposApiDocsController calcula schemaId e
ETag pela variante real do payload, incluindo
includeInternalSchemas, resolvedIdField e
computedReadOnlyDomainCatalogController monta links de
/schemas/filtered a partir do resolver canonicoOpenApiDocumentService passou a ser o dono de resolucao
de grupo, fetch/cache de OpenAPI e cache de hash estruturalSchemaReferenceResolver passou a devolver uma
schemaUrl que reproduz a mesma variante canonica do
schemaO repo ganha uma API interna unica para resolver operacao e schema canonicos, sem acoplamento ao controller documental.
ResourceMapperBaseResourceQueryServiceBaseResourceCommandServiceBaseResourceServiceAbstractBaseQueryResourceServiceAbstractBaseResourceServiceAbstractReadOnlyResourceServiceEste corte troca o boundary de service e mapeamento, separando
ResponseDTO, CreateDTO e
UpdateDTO, garante findAll() e preservacao de
ordem em findAllById(), e move read-only para uma
hierarquia query-only real. Os controllers legados ainda nao foram
removidos nesta rodada.
AbstractResourceQueryControllerAbstractResourceControllerAbstractReadOnlyResourceControllerEste corte sobe o core HTTP novo sobre o boundary resource-oriented,
preserva a superficie canonica de query/options/stats/schema, remove a
semantica de escrita herdada da variante read-only e adapta o scanning
de grupos OpenAPI para reconhecer a nova hierarquia. O legado
AbstractCrudController /
AbstractReadOnlyController permanece apenas como superficie
transitoria enquanto consumidores como o
praxis-api-quickstart ainda nao foram migrados.
No encerramento original da Fase 2, os proximos passos previstos eram:
AbstractReadOnlyControllerAbstractCrudControllerTrocar o boundary legado baseado em DTO unico por um core com separacao explicita entre leitura, criacao e atualizacao.
org.praxisplatform.uischema.mapper.base
org.praxisplatform.uischema.service.base
org.praxisplatform.uischema.controller.base
public interface ResourceMapper<E, ResponseDTO, CreateDTO, UpdateDTO, ID> {
ResponseDTO toResponse(E entity);
E newEntity(CreateDTO dto);
void applyUpdate(E entity, UpdateDTO dto);
ID extractId(E entity);
}public interface BaseResourceQueryService<ResponseDTO, ID, FilterDTO extends GenericFilterDTO> {
ResponseDTO findById(ID id);
Page<ResponseDTO> filter(FilterDTO filter, Pageable pageable, Collection<ID> includeIds);
CursorPage<ResponseDTO> filterByCursor(FilterDTO filter, Sort sort, String after, String before, int size);
OptionalLong locate(FilterDTO filter, Sort sort, ID id);
Page<OptionDTO<ID>> filterOptions(FilterDTO filter, Pageable pageable);
List<OptionDTO<ID>> byIdsOptions(Collection<ID> ids);
GroupByStatsResponse groupByStats(GroupByStatsRequest<FilterDTO> request);
TimeSeriesStatsResponse timeSeriesStats(TimeSeriesStatsRequest<FilterDTO> request);
DistributionStatsResponse distributionStats(DistributionStatsRequest<FilterDTO> request);
}public interface BaseResourceCommandService<ResponseDTO, ID, CreateDTO, UpdateDTO> {
SavedResult<ID, ResponseDTO> create(CreateDTO dto);
ResponseDTO update(ID id, UpdateDTO dto);
void deleteById(ID id);
void deleteAllById(Collection<ID> ids);
}public interface BaseResourceService<
ResponseDTO,
ID,
FilterDTO extends GenericFilterDTO,
CreateDTO,
UpdateDTO
> extends BaseResourceQueryService<ResponseDTO, ID, FilterDTO>,
BaseResourceCommandService<ResponseDTO, ID, CreateDTO, UpdateDTO> {
}AbstractResourceQueryControllerAbstractResourceControllerAbstractReadOnlyResourceControllerAbstractReadOnlyResourceController herda apenas da base
de query405 deve ser removidofindAll() permanece obrigatorio enquanto a superficie
canonica do starter continuar expondo GET /allO starter passa a suportar ResponseDTO,
CreateDTO, UpdateDTO e FilterDTO
como fronteiras distintas.
Modelar multiplos formularios da mesma entidade sem transformar
surface em contrato de escrita.
org.praxisplatform.uischema.annotation
org.praxisplatform.uischema.resource.intent
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface ResourceIntent {
String id();
String title();
String description() default "";
int order() default 0;
}Endpoints reais e tipados, por exemplo:
@PatchMapping("/{id}/profile")
@ResourceIntent(id = "employee-profile", title = "Editar perfil")
public ResponseEntity<RestApiResponse<EmployeeResponseDTO>> updateProfile(
@PathVariable Long id,
@Valid @RequestBody UpdateEmployeeProfileDTO dto
) { ... }Formularios parciais viram operacoes canonicamente resource-oriented, com DTO nomeado por intencao.
org.praxisplatform.uischema.annotation.ResourceIntent foi
introduzida como vocabulario canonico minimo da fasesrc/test/java/org/praxisplatform/uischema/controller/base/AbstractResourceControllerJpaWriteIntegrationTest.java
agora prova um
PATCH /integration-employees/{id}/profileUpdateEmployeeProfileDto, o OpenAPI do grupo individual e a
resolucao canonica de /schemas/filtered para o
PATCHAdicionar discovery semantico de formularios, views e projecoes, sempre por referencia a operacao canonica.
@ApiResource agora exige resourceKey como
identidade semantica estavel do recurso@UiSurface foi introduzida para surfaces explicitas
sobre operacoes HTTP reais e agora pode declarar
requiredAuthorities e allowedStatessurface/* agora publica
SurfaceDefinition, SurfaceCatalogItem,
SurfaceCatalogResponse,
SurfaceDefinitionRegistry,
SurfaceCatalogService,
SurfaceAvailabilityEvaluator e
AnnotationDrivenSurfaceDefinitionRegistryGET /schemas/surfaces?resource={resourceKey} e
GET /schemas/surfaces?group={openApiGroup} ja estao
publicadoscreate,
list, detail, edit e surfaces
explicitas anotadas, como profileGET /{resource}/{id}/surfaces
ao AbstractResourceQueryController, com
resourceId real no payloadSurfaceScope.ITEM
e usa SurfaceAvailabilityContext com
resourceKey, resourcePath,
resourceId, locale, principal,
authorities e snapshot opcional de estado do recursoSurfaceAvailabilityContextResolver, passou a usar
X-Tenant como sinal contextual canonico e tornou surfaces
ITEM globalmente indisponiveis sem resourceId,
com reason=resource-context-requiredResourceStateSnapshotProvider plugavel,
SurfaceAvailabilityRule componivel por beans Spring e
contexto/snapshot compartilhados por catalogo para evitar custo N+1 por
surface/schemas/surfaces
com 404 para resourceKey ou group
desconhecidos, adicionou cache lazily built no registry
annotation-driven e passou a ignorar mappings workflow-like
(/actions/ e :approve) no catalogo de
surfacesorg.praxisplatform.uischema.annotation
org.praxisplatform.uischema.surface
org.praxisplatform.uischema.controller.docs
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface UiSurface {
String id();
SurfaceKind kind();
String title();
String description() default "";
String intent() default "";
SurfaceScope scope() default SurfaceScope.ITEM;
int order() default 0;
String[] tags() default {};
}SurfaceDefinitionSurfaceCatalogItemSurfaceCatalogResponseSurfaceDefinitionRegistrySurfaceCatalogServiceSurfaceAvailabilityEvaluatorSurfaceAvailabilityContextResourceStateSnapshotProviderAnnotationDrivenSurfaceDefinitionRegistryGET /schemas/surfaces?resource={resourceKey}GET /schemas/surfaces?group={openApiGroup}GET /{resource}/{id}/surfacesoperationId, path,
method, schemaId, schemaUrlFORM e PARTIAL_FORM apontam para schema
requestVIEW e READ_PROJECTION apontam para schema
responsedelete, filter,
cursor, locate, options,
stats nem workflow actionsITEM sao discovery
semantico e devem refletir ausencia de contexto concreto em
availabilityresourceKey ou group desconhecidos devem
falhar explicitamente com 404, e nao retornar catalogo
vazio indistinguivel de sucessoreason explicito, short-circuit no primeiro deny sensivel e
nunca por condicionais monoliticos ou lookup N+1 por surfaceA Fase 4 esta formalmente encerrada no estado atual do starter.
Criterios de saida atingidos:
availability contextual endurecida por composicao404 explicito para resourceKey e
group desconhecidosO proximo passo canonico deixa de ser hardening adicional de
surfaces e passa a ser a Fase 5 de
WorkflowAction.
Catalogar e avaliar workflows de negocio explicitos.
@WorkflowAction foi introduzida como anotacao canonica
para comandos de negocio explicitos sobre endpoints reaisaction/* agora publica
ActionDefinition, ActionCatalogItem,
ActionCatalogResponse,
ActionDefinitionRegistry,
ActionCatalogService,
ActionAvailabilityEvaluator,
ActionAvailabilityContext,
DefaultActionAvailabilityContextResolver e
AnnotationDrivenActionDefinitionRegistryGET /schemas/actions?resource={resourceKey} e
GET /schemas/actions?group={openApiGroup} ja estao
publicadosGET /{resource}/{id}/actions foi acoplado ao
AbstractResourceQueryController como discovery contextual
item-levelGET /{resource}/actions passa a ser o endpoint
contextual canonico para ActionScope.COLLECTION quando
houver actions reais de colecao@WorkflowActionoperationId,
path, method, requestSchemaId,
requestSchemaUrl, responseSchemaId e
responseSchemaUrl, sem fields/schema inlinePOST /employees/{id}/actions/approve, catalogo
global/contextual e separacao rigida em relacao a
surfacesActionScope.COLLECTION com
GET /{resource}/actions e
POST /employees/actions/bulk-approve@UiSurface +
@WorkflowAction passou a ser governado por
praxis.metadata.validation.surface-workflow-conflict=FAIL|WARN|IGNORE,
com default WARNResourceStateSnapshot e
ResourceStateSnapshotProvider foram promovidos para o
pacote neutro capability, removendo o acoplamento
action -> surface no estado compartilhado de
availabilityAnnotationDrivenActionDefinitionRegistry agora resolve
requestSchema e responseSchema com o mesmo
contexto canonico de idField usado por
surfaces e pelos links do core HTTP@WorkflowAction passou a ser validado por
praxis.metadata.validation.workflow-action-shape=FAIL|WARN|IGNORE,
aceitando apenas mappings canonicos de comando
(POST/PATCH sobre /actions/... ou
alias :action)surfaces: ActionAvailabilityRule componivel
por beans Spring, DefaultActionAvailabilityEvaluator com
short-circuit no primeiro deny e metadados incrementais, e reasons
explicitos para contexto, RBAC e estado do recurso sem N+1 por
actionorg.praxisplatform.uischema.annotation
org.praxisplatform.uischema.action
org.praxisplatform.uischema.controller.docs
@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface WorkflowAction {
String id();
String title();
String description() default "";
ActionScope scope() default ActionScope.ITEM;
int order() default 0;
String successMessage() default "";
}ActionDefinitionActionCatalogItemActionCatalogResponseActionDefinitionRegistryActionAvailabilityEvaluatorActionAvailabilityContextAnnotationDrivenActionDefinitionRegistryGET /schemas/actions?resource={resourceKey}GET /schemas/actions?group={openApiGroup}GET /{resource}/actionsGET /{resource}/{id}/actionsSempre por endpoint tipado real, por exemplo:
@PostMapping("/{id}/actions/approve")
@WorkflowAction(id = "approve", title = "Aprovar")
public ResponseEntity<RestApiResponse<EmployeeResponseDTO>> approve(
@PathVariable Long id,
@Valid @RequestBody ApproveEmployeeDTO dto
) { ... }(resourceKey, actionId)ActionScope suporta ITEM e
COLLECTION, e actions de colecao reais devem usar
GET /{resource}/actions como discovery contextual
canonicodelete, filter, stats,
options e PATCH resource-oriented por intencao
nao entram no catalogo de actions@WorkflowAction exclui entrada no catalogo de
surfacesreason explicito e sem N+1 por actionA Fase 5 esta formalmente encerrada no estado atual do starter.
Criterios de saida atingidos:
ITEM e
COLLECTIONsurfacesO proximo passo canonico deixa de ser hardening adicional de
actions e passa a ser a Fase 6 de capabilities
unificadas.
Expor um snapshot unico do que pode ser feito agora em uma colecao ou instancia.
org.praxisplatform.uischema.capability
org.praxisplatform.uischema.controller.docs
AvailabilityDecisionCapabilitySnapshotCanonicalCapabilityResolverCapabilityServiceGET /{resource}/capabilitiesGET /{resource}/{id}/capabilitiesCapabilities agregam:
Sem redefinir payload ou contrato.
CanonicalCapabilityResolver foi extraido do calculo
canonico antes embutido em ApiDocsControllerCapabilitySnapshot e CapabilityService ja
agregam operacoes canonicas, surfaces e
actionsAbstractResourceQueryController ja publica
GET /capabilities e
GET /{id}/capabilitiessurfaces de
COLLECTION e actions de
COLLECTIONsurfaces de ITEM e
actions de ITEMsurfaces ou
actions para um recurso valido resulta em lista vazia, e
nao em shadow contract ou falha artificialCapabilitySnapshot.group segue o grupo OpenAPI canonico
resolvido por resourcePath; no estado atual isso
normalmente significa o grupo individual do recursoupdate ignora mappings
workflow-like (/actions/... e alias :action),
preservando a semantica resource-oriented mesmo quando
actions usam PATCHA Fase 6 esta concluida no starter. O proximo passo canonicamente
correto deixa de ser evolucao interna do
praxis-metadata-starter e passa a ser a migracao do
primeiro consumidor externo sobre o baseline completo
resource + surfaces + actions + capabilities.
org.praxisplatform.uischema
|-- annotation
|-- mapper.base
|-- service.base
|-- controller.base
|-- openapi
|-- schema
|-- resource.intent
|-- surface
|-- action
|-- capability
|-- controller.docs
`-- configuration
OpenApiDocumentServiceCanonicalOperationResolverSchemaReferenceResolverApiDocsControllerDomainCatalogControllerEsse corte tem o menor risco arquitetural e estabelece a fundacao canonica para:
ResourceMapperBaseResourceQueryServiceBaseResourceCommandServiceBaseResourceServiceAbstractResourceQueryControllerAbstractResourceControllerAbstractReadOnlyResourceControllerAbstractReadOnlyControllerEsse corte remove a principal limitacao estrutural do starter: o DTO central compartilhado entre leitura e escrita.
docs/agent-status/YYYY-MM-DD.md e encerrado ao final da
laneEscopo:
openapi/*schema/*ApiDocsControllerDomainCatalogControllerResponsabilidade:
OpenApiDocumentServiceCanonicalOperationResolverSchemaReferenceResolver/schemas/filtered e /schemas/catalogEscopo:
mapper.base/*service.base/*controller.base/*Responsabilidade:
ResourceMapperBaseResourceQueryServiceBaseResourceCommandServiceBaseResourceServiceAbstractResourceQueryControllerAbstractResourceControllerAbstractReadOnlyResourceControllerEscopo:
Responsabilidade:
Estado atual no starter:
src/test/java/org/praxisplatform/uischema/controller/base/AbstractResourceControllerJpaWriteIntegrationTest.javaby-ids, exclusao, datasetVersion, OpenAPI do
grupo individual e resolucao canonica de
/schemas/filteredPATCH /integration-employees/{id}/profile e
UpdateEmployeeProfileDtoEscopo:
annotation/UiSurfacesurface/*controller.docs/SurfaceCatalogControllerResponsabilidade:
operationId, schemaId e
schemaUrlEscopo:
annotation/WorkflowActionaction/*controller.docs/ActionCatalogControllerResponsabilidade:
Escopo:
capability/*controller.docs/*Responsabilidade:
As lanes 4, 5 e 6 so devem comecar depois que as lanes 1 e 2 estabilizarem a fundacao canonica.
Cada agente de implementacao deve receber:
Template operacional minimo:
Responsabilidade desta lane:
- arquivos permitidos: ...
- arquivos proibidos: ...
- objetivo: ...
- validacao minima: ...
- nao criar contratos paralelos
- nao redefinir schema fora de /schemas/filtered
- nao reverter mudancas de outras lanes
Toda rodada relevante deve terminar com um agente de QA separado da implementacao.
Perfil esperado:
Checklist obrigatorio do agente de QA:
resourcesurface e action nao
introduziram shadow APIschemaId e schemaUrl sao
derivados canonicamente/schemas/filtered/schemas/catalogPrompt minimo recomendado para o agente de QA:
Revise esta rodada como Staff Engineer especialista em Spring Boot.
Priorize:
1. integridade do codigo
2. integridade da logica de negocio
3. inconsistencias entre o plano e a implementacao
4. qualidade e sufiencia da documentacao
5. aderencia a uso corporativo
Procure principalmente:
- shadow API
- regressao de contrato canonico
- duplicacao estrutural
- endpoint generico onde deveria haver operacao tipada
- schema nao derivado de /schemas/filtered
- leitura/escrita ainda acopladas no mesmo DTO sem necessidade
- lacunas de teste e de migracao
Uma rodada so pode ser dada como pronta quando:
Ao final da migracao do core:
AbstractCrudControllerBaseCrudServiceAbstractBaseCrudServiceAbstractReadOnlyControllerAo final do plano:
resource define a verdade estruturalsurface organiza discovery semantico contextualaction explicita workflows de negocio/schemas/filtered continua sendo a unica fonte
estrutural canonicaDepois do fechamento das Fases 1 a 6, a trilha correta e:
resource + surfaces + actions + capabilitiese2e-pg ou mais
hardening transversalresource define o contrato; surface
organiza a experiencia; action expressa a intencao;
/schemas/filtered continua sendo a verdade estrutural.