Skip to main content
Version: 2.0.0

Portal Backend


Purpose​

This guide explains how to implement and extend the Portal Backend at a code-level. It complements the Architectural Overview and describes how Controllers, Services, Assemblers, Mappers, Repositories, and the Domain Model work together.


Controllers – API Entry​

Controllers are thin REST adapters and delegate all logic to Services and Assemblers.

  • Extend BaseController with generics for: Input DTO, Output DTO, Entity, Specification, ID (usually UUID).

  • Base CRUD endpoints are provided:

    • GET /resource – list + filters/pagination
    • GET /resource/{id} – detail
    • POST /resource – create
    • PUT /resource/{id} – update
    • PATCH /resource/{id} – partial update
    • DELETE /resource/{id} – delete

Most controllers only define type parameters and base path. Custom endpoints are added only when required.


Assemblers – Response Construction​

Assemblers build Output DTOs from entities.

  • Implement BaseAssembler with a template flow:

    1. Optional pre-processing (e.g. load lazy relations)
    2. Base field mapping via Mapper
    3. Optional enrichment (computed fields, summaries)
    4. Optional post-processing (links, formatting)
  • Responsibilities:

    • Use Mappers for base field mapping
    • Convert relations to Summary DTOs
    • Optionally expose toInput for PATCH workflows

Assemblers isolate all response shaping logic from controllers and services.


Services – Business Logic and Hooks​

Services orchestrate repositories, transactions, and domain rules.

  • Extend BaseService, which provides all standard operations.
  • Override lifecycle hooks only when needed:

Input:

  • preProcessCreateInput
  • preProcessUpdateInput

Mapping:

  • postConvertToEntity

Persistence:

  • preSave
  • postSave

Queries:

  • preProcessQuery
  • postProcessQueryResult

Load/Delete:

  • preProcessLoad, postLoad
  • preProcessDelete, postDelete

Relation handling always belongs in the service layer, never in mappers.


Mappers – DTO ↔ Entity Conversion​

Mappers perform field-level transformations.

  • Implement DtoMapper:

    • toEntity
    • toOutput
    • toInput
    • updateEntity
  • MapStruct generates implementations:

    • Relation fields are ignored
    • Create ignores nulls for optional fields
    • Update/PATCH applies explicit nulls

Mappers focus purely on type-safe field copying.


Repositories and Specifications – Data Access​

Repositories encapsulate all DB operations.

  • Base repositories build on Spring Data JPA and support CRUD + Specifications.

  • Entity-specific repositories extend the base and may define EntityGraph queries for eager loading.

  • Specifications:

    • Shared base specs for id, createdAt, modifiedAt
    • Named-entity specs add name, description, and a q search field
    • Entity-specific specs extend these as needed

Controllers receive specification instances built automatically from query parameters.


Domain Model – Entities and DTOs​

The domain model defines core structures:

  • Entities:

    • BaseEntity – id, audit fields, validation hook
    • NamedEntity – adds name, description
    • BaseDataEntity – adds project-level fields for data entities
    • AssignableEntity – adds role/group assignment support
    • Concrete entities extend these and define relations and additional fields
  • DTOs:

    • Input DTOs – client data, relations as IDs
    • Output DTOs – full responses with nested summaries
    • Summary DTOs – minimal representations for nested relations

Validation occurs at the DTO, service, and optional entity level.


Event Communication with Config-Adapter​

When certain entities are created, updated, or deleted, the Portal Backend needs to tell external systems about these changes. This happens via KafkaApache KafkaA distributed event streaming platform. In CIVITAS/CORE it is used as the message bus to transport events, models and data in data flows. messages. There are two patterns:

User Management Sync (Users, Groups, Roles)​

When a user, group, or role is saved, the backend sends a message to the Config-Adapter and waits for a response before saving to the database. The Config-Adapter applies the change in KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs. and replies with success or failure.

If the Config-Adapter does not respond within 10 seconds, the operation is rolled back.

Key classes:

  • EventPublishingService – base service for entities that need external sync
  • ConfigEventPublisherService – sends the KafkaApache KafkaA distributed event streaming platform. In CIVITAS/CORE it is used as the message bus to transport events, models and data in data flows. messages
  • KafkaCloudEventPublisher – handles sending and waiting for responses
  • KafkaConfigResultListener – receives responses from Config-Adapter

To add IDM sync to a new entity, extend EventPublishingService instead of BaseService.

Dataset Saga (DataSets)​

When a datasetDatasetA data-related element that contains processed data and makes it available for consumption. A Dataset is populated via Pipelines and carries Metadata and access permissions. is created, updated, or deleted, multiple external systems need to be configured (FROST for IoT data, APISIXApache APISIXAn open-source API gateway for traffic management, security and observability. In CIVITAS/CORE it is used as the centralized entrypoint to route and protect externally exposed APIs. for API routing, Redpanda for data pipelines). This happens asynchronously — the datasetDatasetA data-related element that contains processed data and makes it available for consumption. A Dataset is populated via Pipelines and carries Metadata and access permissions. is saved immediately with a PENDING status and updated to ACTIVE or FAILED once the Config-Adapter finishes.

Key classes:

  • DataSetSagaPublisher – sends the saga trigger
  • DataSetSagaResultListener – receives the result and updates the datasetDatasetA data-related element that contains processed data and makes it available for consumption. A Dataset is populated via Pipelines and carries Metadata and access permissions.
  • SagaTrigger – defines the trigger types: DatasetCreate, DatasetUpdate, DatasetDelete

Running Without Kafka​

KafkaApache KafkaA distributed event streaming platform. In CIVITAS/CORE it is used as the message bus to transport events, models and data in data flows. is disabled by default (kafka.enabled=false). This affects the two patterns differently:

KafkaApache KafkaA distributed event streaming platform. In CIVITAS/CORE it is used as the message bus to transport events, models and data in data flows. enabled?User Management Sync (Users, Groups, Roles)DatasetDatasetA data-related element that contains processed data and makes it available for consumption. A Dataset is populated via Pipelines and carries Metadata and access permissions. Saga
YesChanges are synced to KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs.DatasetsDatasetA data-related element that contains processed data and makes it available for consumption. A Dataset is populated via Pipelines and carries Metadata and access permissions. are provisioned in external systems
NoChanges are saved locally only (no KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs. sync)Not available

For local development without KafkaApache KafkaA distributed event streaming platform. In CIVITAS/CORE it is used as the message bus to transport events, models and data in data flows., user/group/role operations still work but are not synced to KeycloakKeycloakAn open-source Identity and Access Management (IAM) solution providing SSO and OAuth2/OpenID Connect flows. In CIVITAS/CORE it is used to authenticate users and issue JWTs.. Set kafka.enabled=true in your profile to enable full sync.


Implementation Checklist for a New Entity​

  1. Model
  • Create entity (extend appropriate base)
  • Define relations and constraints
  • Add Input, Output, Summary DTOs
  1. Data Access
  • Create repository
  • Create Specification interface
  1. Mapping
  • Implement MapStruct mapper
  • Ignore relation fields; define summary mapping
  1. Business Logic
  • Extend service
  • Override hooks for relations/validation only as needed
  1. Response
  • Implement assembler with summary generation
  1. API
  • Create controller extending base controller

This sequence ensures all entities integrate consistently with the platform’s architecture.