Developer Guide
1. 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.
2. Controllers – API Entry
Controllers are thin REST adapters and delegate all logic to Services and Assemblers.
-
Extend
BaseControllerwith generics for: Input DTO, Output DTO, Entity, Specification, ID (usuallyUUID). -
Base CRUD endpoints are provided:
GET /resource– list + filters/paginationGET /resource/{id}– detailPOST /resource– createPUT /resource/{id}– updatePATCH /resource/{id}– partial updateDELETE /resource/{id}– delete
Most controllers only define type parameters and base path. Custom endpoints are added only when required.
3. Assemblers – Response Construction
Assemblers build Output DTOs from entities.
-
Implement
BaseAssemblerwith a template flow:- Optional pre-processing (e.g. load lazy relations)
- Base field mapping via Mapper
- Optional enrichment (computed fields, summaries)
- Optional post-processing (links, formatting)
-
Responsibilities:
- Use Mappers for base field mapping
- Convert relations to Summary DTOs
- Optionally expose
toInputfor PATCH workflows
Assemblers isolate all response shaping logic from controllers and services.
4. 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:
preProcessCreateInputpreProcessUpdateInput
Mapping:
postConvertToEntity
Persistence:
preSavepostSave
Queries:
preProcessQuerypostProcessQueryResult
Load/Delete:
preProcessLoad,postLoadpreProcessDelete,postDelete
Relation handling always belongs in the service layer, never in mappers.
5. Mappers – DTO ↔ Entity Conversion
Mappers perform field-level transformations.
-
Implement
DtoMapper:toEntitytoOutputtoInputupdateEntity
-
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.
6. 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
EntityGraphqueries for eager loading. -
Specifications:
- Shared base specs for
id,createdAt,modifiedAt - Named-entity specs add
name,description, and aqsearch field - Entity-specific specs extend these as needed
- Shared base specs for
Controllers receive specification instances built automatically from query parameters.
7. Domain Model – Entities and DTOs
The domain model defines core structures:
-
Entities:
BaseEntity– id, audit fields, validation hookNamedEntity– addsname,descriptionBaseDataEntity– adds project-level fields for data entitiesAssignableEntity– 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.
8. 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 Kafka messages. There are two patterns:
8.1 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 Keycloak 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 syncConfigEventPublisherService– sends the Kafka messagesKafkaCloudEventPublisher– handles sending and waiting for responsesKafkaConfigResultListener– receives responses from Config-Adapter
To add IDM sync to a new entity, extend EventPublishingService instead of BaseService.
8.2 Dataset Saga (DataSets)
When a dataset is created, updated, or deleted, multiple external systems need to be configured (FROST for IoT data, APISIX for API routing, Redpanda for data pipelines). This happens asynchronously — the dataset is saved immediately with a PENDING status and updated to ACTIVE or FAILED once the Config-Adapter finishes.
Key classes:
DataSetSagaPublisher– sends the saga triggerDataSetSagaResultListener– receives the result and updates the datasetSagaTrigger– defines the trigger types:DatasetCreate,DatasetUpdate,DatasetDelete
8.3 Running Without Kafka
Kafka is disabled by default (kafka.enabled=false). This affects the two patterns differently:
| Kafka enabled? | User Management Sync (Users, Groups, Roles) | Dataset Saga |
|---|---|---|
| Yes | Changes are synced to Keycloak | Datasets are provisioned in external systems |
| No | Changes are saved locally only (no Keycloak sync) | Not available |
For local development without Kafka, user/group/role operations still work but are not synced to Keycloak. Set kafka.enabled=true in your profile to enable full sync.
9. Implementation Checklist for a New Entity
- Model
- Create entity (extend appropriate base)
- Define relations and constraints
- Add Input, Output, Summary DTOs
- Data Access
- Create repository
- Create Specification interface
- Mapping
- Implement MapStruct mapper
- Ignore relation fields; define summary mapping
- Business Logic
- Extend service
- Override hooks for relations/validation only as needed
- Response
- Implement assembler with summary generation
- API
- Create controller extending base controller
This sequence ensures all entities integrate consistently with the platform’s architecture.