Skip to content

Technical architecture

Every gStack service is built from the same technology stack and the same layering, so the estate is consistent end to end. This page is the concrete “what it’s built on”; for the conceptual picture see How gStack works.

ConcernTechnologyNotes
ScaffoldingJHipster (8.11 monolith; 9.1 for microservices)Generates the whole vertical slice from a JDL model
BackendSpring Boot 3 (Java 17 / 21)REST, service layer, Spring Security
FrontendAngular (standalone components) on GDSOne design system across every SPA
Relational storePostgreSQL (prod) / H2 (dev)jsonb for polymorphic/flexible fields
SearchElasticsearchFull-text + filtered search on selected entities
Logs / observabilityELK (Elasticsearch · Logstash · Kibana) + GLogOperational logs via ELK; business audit via GLog
IdentityKeycloak (OIDC) via gSSOSSO, tokens, roles; no app mints its own tokens
MigrationsLiquibaseAppend-only changelogs
MappingMapStructEntity ↔ DTO (entities never leave the service layer)
Async / messagingRabbitMQNotifications, OCR/transcription, archive workers
Object storageMinIO / S3 (via GStorage)Bytes never in the DB
PackagingDocker · compose on hosts · KubernetesImages in the GitLab container registry

Each application follows the same request path, so the same file means the same thing in every repo:

Angular SPA
│ HTTPS + OIDC/JWT bearer
▼
web.rest.*Resource ← controllers; exchange DTOs only
▼
service.*Service (+ impl) ← business logic + lifecycle/state machine
service.*QueryService ← JPA Criteria filtering for list endpoints
service.mapper (MapStruct) ← entity ↔ DTO
▼
repository.*Repository → PostgreSQL
repository.search.* → Elasticsearch
  • Entities never leave the service layer — controllers exchange DTOs.
  • URL-driven filtering exists only on entities declared filterable in the JDL (via *QueryService + *Criteria).
  • Auditing columns (createdBy/createdDate/lastModified…) are auto-populated by a shared AbstractAuditingEntity.
  • Lifecycle state machines are enforced in the service layer; each transition appends one row to a write-only event log.
  • OIDC/SSO through Keycloak (realm managed by gSSO); services are OAuth2 resource servers validating JWTs.
  • Roles are platform-prefixed and carried in the token; endpoints authorize in the service/controller layer.
  • Machine-to-machine access uses client-credentials tokens or, where required, mTLS (e.g. GNotify, GLog ingest).
  • Append-only audit (Hibernate guard + DB trigger) backs every state change and is forwarded to GLog.
  • Model-first: the JDL is the source of truth; regenerate, then re-apply hand-written logic kept in separate classes.
  • Build: ./mvnw (backend), ./npmw (frontend); production images are built per app (backend + nginx-served frontend) and pushed to registry.esempla.systems.
  • Run: docker-compose on the shared hosts behind a single edge nginx (TLS + per-app vhosts on the shared gstack-web network); SI RDP runs on a Kubernetes cluster.
  • Integrations: the government building blocks (MPass/MSign/MConnect/MNotify/MLog) are reusable Spring Boot starters, each with a mock mode for local development.

Every service ships trilingual (RO / RU / EN). Translations live in two places and are kept in sync: webapp/i18n/{ro,ru,en}/*.json (frontend) and resources/i18n/messages_*.properties (backend). Romanian domain terms (legal names) are never translated.