Skip to content

Application platform

Shared backend conventions

Status: Locked for implementation Reference: Fordsworth fordsflex application package, adapted for Quarkus 3.38 and RFC 7807

Every business component (registry, strata, …) follows these platform rules. The platform component itself supplies cross-cutting infrastructure.

Layering (BCE)

Layer Package suffix Responsibility
boundary .boundary JAX-RS resources — thin implementations only
boundary.api .boundary.api OpenAPI-annotated API interfaces; resources implement these
control .control Procedures, repositories, transactions, and platform cross-cutting (error mapping, current user)
entity .entity JPA entities, embeddables, enums, DTO records

Do not add *Service classes. Name controls after the procedure they perform (PartyRegistration, LicenseImport, SubmissionCertification).

Boundaries delegate to control. Domain controls never assemble HTTP responses; they throw KoventException. Platform control hosts application-wide exception mappers and the current-user filter so resources stay clean.

REST paths

Global prefix is quarkus.rest.path=/api in application.properties. API interfaces declare relative paths only (@Path("/parties"), not @Path("/api/parties")).

Each business component exposes a boundary.api interface with OpenAPI annotations (@Operation, @APIResponse, @Tag). The resource in boundary implements the interface and contains no path or documentation annotations — only delegation to control.

// boundary.api — contract + OpenAPI
@Path("/parties")
@Tag(name = "Parties")
public interface PartiesApi {
  @GET
  @Path("/{partyId}")
  @Operation(operationId = "getParty", summary = "Party profile with licenses")
  PartyProfile getParty(@RestPath UUID partyId);
}

// boundary — implementation only
@ApplicationScoped
public class PartiesResource implements PartiesApi {
  public PartyProfile getParty(UUID partyId) {
    return loadPartyProfile.require(partyId);
  }
}

Dependency injection

Constructor injection only. No @Inject on fields. CDI resolves a single public constructor automatically.

Persistence — repository pattern

Entities are plain JPA @Entity / @MappedSuperclass types. They do not extend PanacheEntity (active record).

Persistence lives in control:

@ApplicationScoped
public class PartyRepository implements PanacheRepository<Party> {
  public Optional<Party> findByRegisteredNumber(String registeredNumber) {
    return find("organisation.registeredNumber", registeredNumber).firstResultOptional();
  }
}

Only repositories call persist, delete, and query methods. Controls call repositories.

All persisted entities extend AuditableEntity (UUID id, embedded Audit).

Audit and current user

Type Role
Audit created_by, created_on, updated_by, updated_on
AuditableEntity @PrePersist / @PreUpdate stamp audit from CurrentUserHolder
CurrentUser Request-scoped — reads SecurityIdentity for control-layer code
CurrentUserRequestFilter Binds principal to CurrentUserHolder per request

Domain status history (for example license_register_status_history) is separate from row audit.

Errors — RFC 7807 Problem Details

Application code throws KoventException with a KoventErrorCode. Boundaries do not build HTTP responses for business failures. Responses use ProblemDetailBody (RFC 7807 wire format). Jakarta WS RS 3.1 does not yet ship jakarta.ws.rs.core.ProblemDetail on the Quarkus classpath; the shape is identical and can be swapped later or replaced with quarkus-http-problem if desired.

Component Role
KoventErrorCode Enum mapping to HTTP status + title
KoventException Domain failure with optional parameters
ProblemDetailBody RFC 7807 response body (application/problem+json)
ProblemResponses Builds problem bodies from domain exceptions
KoventExceptionMapper Maps KoventExceptionapplication/problem+json
ConstraintViolationExceptionMapper Bean validation failures
FallbackExceptionMapper Unhandled throwables; logs server errors

Example response:

{
  "type": "about:blank",
  "title": "Not found",
  "status": 404,
  "detail": "Not found: party, p-1",
  "code": "NOT_FOUND",
  "parameters": ["party", "p-1"]
}

Control example:

return repository
    .findByIdOptional(id)
    .orElseThrow(() -> new KoventException(KoventErrorCode.NOT_FOUND, "party", id));

Transactions

@Transactional on control methods that mutate state. Boundaries stay non-transactional.

Diagrams

Entity-relationship diagrams for domain models are maintained in draw.io under docs/diagrams/ — not inline Mermaid ER blocks.

Architecture tests (ArchUnit)

BCE boundaries are enforced in CI via ArchitectureTest (src/test/java/.../architecture/). Violations fail mvn test. Rules include:

Rule Intent
Layer access Boundary → control/entity; control → entity (+ shared platform.control); entity → entity only
No BC cycles Slices (registry, platform, …) must not circularly depend
No dumping grounds No shared, common, util, application, exceptions packages
JAX-RS @Path Only in ..boundary.. (including boundary.api)
Exception mappers Only in platform.control
API pattern *Resource implements a boundary.api.*Api interface
No *Service Controls named after procedures, not generic services
Repositories *Repository classes live in control
No active record Entities do not extend PanacheEntity
Constructor injection No @Inject on fields

Reference: Quarkus + ArchUnit BCE tutorial.