Skip to content

DB Schema — GRegistry / Registrul Sistemelor Informaționale (RSI)

Schema derivată din GRegistry.jdl (JDL v2) pentru verificare manuală înainte de jhipster jdl GRegistry.jdl.

Tipuri de date: PostgreSQL (profil prod și dev — GRegistry folosește PostgreSQL pe ambele). Package md.gov.gregistry, port 8085, autentificare OAuth2 / OIDC (GSSO / Keycloak). Toate tabelele mutabile moștenesc 4 câmpuri de audit de la AbstractAuditingEntity (omise din unele DDL-uri pentru lizibilitate — vezi §6).

Fără JSONB, fără Elasticsearch, fără Kafka în acest sistem — spre deosebire de Registrul Interdicțiilor, GRegistry nu are coloane TextBlob/jsonb de convertit (vezi §7). Registrul e un catalog structurat, read-heavy pentru consumatori (GPortal, GLog, GStorage, GMonitor).

Documente conexe: platform_design.md (arhitectură, diagrame de flux), caiet_de_sarcini.md (cerințe funcționale), Home.md (wiki / glosar).

erDiagram
PROVIDER ||--o{ INFORMATION_SYSTEM : "deține"
INFORMATION_SYSTEM ||--o{ ENVIRONMENT : "rulează în"
INFORMATION_SYSTEM ||--o{ PUBLICATION_REQUEST : "publicat prin"
INFORMATION_SYSTEM ||--o{ API_ENDPOINT : "expune"
INFORMATION_SYSTEM ||--o{ REGISTRY_AUDIT : "audit append-only"
INFORMATION_SYSTEM ||--o{ SYSTEM_DEPENDENCY : "consumator (system)"
INFORMATION_SYSTEM ||--o{ SYSTEM_DEPENDENCY : "furnizor (dependsOn)"
PROVIDER {
bigint id PK
varchar code "60 UK"
varchar name "160"
varchar legal_name "240"
varchar contact_email "160"
varchar website "240"
boolean active
}
INFORMATION_SYSTEM {
bigint id PK
varchar code "80 UK - ^[a-z][a-z0-9-]*$"
varchar name "160"
varchar category "120"
varchar system_type "enum SystemType (4)"
varchar status "enum SystemStatus (5)"
varchar visibility "enum Visibility (3)"
varchar criticality "enum Criticality (4)"
varchar security_level "enum SecurityLevel (3)"
boolean handles_personal_data
varchar current_version "40"
varchar description_ro "2000"
varchar description_en "2000"
varchar tech_lead "160"
varchar tech_lead_email "160"
boolean published_to_portal
timestamptz created_at
timestamptz updated_at
bigint provider_id FK
}
ENVIRONMENT {
bigint id PK
varchar environment_type "enum EnvironmentType (4)"
varchar state "enum EnvironmentState (5)"
varchar endpoint_url "300"
varchar deployed_version "40"
boolean monitored
timestamptz last_checked_at
bigint system_id FK
}
SYSTEM_DEPENDENCY {
bigint id PK
varchar kind "enum DependencyKind (5)"
boolean required
varchar note "500"
bigint system_id FK "consumator"
bigint depends_on_id FK "furnizor"
}
PUBLICATION_REQUEST {
bigint id PK
varchar state "enum PublicationState (6)"
varchar requested_by "160"
timestamptz requested_at
varchar approved_by "160"
timestamptz approved_at
timestamptz published_at
varchar requested_visibility "enum Visibility (3)"
varchar comment "1000"
bigint system_id FK
}
API_ENDPOINT {
bigint id PK
varchar path "300"
varchar protocol "enum ApiProtocol (5)"
varchar method "16"
varchar description "500"
boolean public_api
bigint system_id FK
}
REGISTRY_CONSUMER {
bigint id PK
varchar name "160"
varchar description "500"
varchar filter_expression "200"
boolean active
}
REGISTRY_AUDIT {
bigint id PK
varchar change_type "enum ChangeType (8)"
varchar actor "160"
varchar summary_ro "500"
varchar summary_en "500"
varchar ip_address "64"
timestamptz occurred_at
bigint system_id FK "opțional"
}

REGISTRY_CONSUMER nu are relații în JDL — este un catalog de sine stătător al consumatorilor de registru. SYSTEM_DEPENDENCY este o entitate-join care materializează un graf de dependențe direcționat auto-referențial peste INFORMATION_SYSTEM (system → dependsOn).

2.1 provider — instituția/organizația care deține sisteme

Section titled “2.1 provider — instituția/organizația care deține sisteme”
CREATE TABLE provider (
id BIGINT NOT NULL PRIMARY KEY,
code VARCHAR(60) NOT NULL,
name VARCHAR(160) NOT NULL,
legal_name VARCHAR(240),
contact_email VARCHAR(160),
website VARCHAR(240),
active BOOLEAN NOT NULL,
-- audit (§6)
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT ux_provider_code UNIQUE (code)
-- code: minlength(2) maxlength(60) — min impus la nivel de aplicație (Bean Validation)
-- contact_email: pattern e-mail — impus la nivel de aplicație
);
CREATE INDEX ix_provider_active ON provider (active);

2.2 information_system — nucleul registrului (RSI)

Section titled “2.2 information_system — nucleul registrului (RSI)”
CREATE TABLE information_system (
id BIGINT NOT NULL PRIMARY KEY,
code VARCHAR(80) NOT NULL, -- ^[a-z][a-z0-9-]*$ (aplicație)
name VARCHAR(160) NOT NULL,
category VARCHAR(120),
system_type VARCHAR(32) NOT NULL,
status VARCHAR(32) NOT NULL,
visibility VARCHAR(32) NOT NULL,
criticality VARCHAR(32) NOT NULL,
security_level VARCHAR(32) NOT NULL,
handles_personal_data BOOLEAN NOT NULL,
current_version VARCHAR(40),
description_ro VARCHAR(2000),
description_en VARCHAR(2000),
tech_lead VARCHAR(160),
tech_lead_email VARCHAR(160),
published_to_portal BOOLEAN NOT NULL,
created_at TIMESTAMPTZ,
updated_at TIMESTAMPTZ,
provider_id BIGINT NOT NULL, -- ManyToOne required
-- audit (§6)
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT fk_is_provider FOREIGN KEY (provider_id) REFERENCES provider(id),
CONSTRAINT ux_is_code UNIQUE (code),
CONSTRAINT chk_is_system_type CHECK (system_type IN ('CORE','BUSINESS','PAAS','INFRASTRUCTURE')),
CONSTRAINT chk_is_status CHECK (status IN ('PLANNED','DEMO','ACTIVE','DEPRECATED','RETIRED')),
CONSTRAINT chk_is_visibility CHECK (visibility IN ('PUBLIC','INTERNAL','TEST')),
CONSTRAINT chk_is_criticality CHECK (criticality IN ('CRITICAL','IMPORTANT','NORMAL','LOW')),
CONSTRAINT chk_is_security_level CHECK (security_level IN ('HIGH','MEDIUM','LOW'))
);
CREATE INDEX ix_is_provider ON information_system (provider_id);
CREATE INDEX ix_is_system_type ON information_system (system_type);
CREATE INDEX ix_is_status ON information_system (status);
-- Calea de citire publică/consumator: GPortal cere sistemele PUBLIC publicate.
CREATE INDEX ix_is_visibility_published ON information_system (visibility, published_to_portal);

created_at/updated_at (câmpuri de domeniu din JDL) coexistă cu created_date/last_modified_date (audit AbstractAuditingEntity). Cele din JDL sunt setate de logica aplicativă a registrului; cele de audit, automat. Vezi §8 — de decis dacă se elimină duplicarea.

2.3 environment — mediu de deployment (prod/staging/test/dev)

Section titled “2.3 environment — mediu de deployment (prod/staging/test/dev)”
CREATE TABLE environment (
id BIGINT NOT NULL PRIMARY KEY,
environment_type VARCHAR(32) NOT NULL,
state VARCHAR(32) NOT NULL,
endpoint_url VARCHAR(300),
deployed_version VARCHAR(40),
monitored BOOLEAN NOT NULL,
last_checked_at TIMESTAMPTZ,
system_id BIGINT NOT NULL, -- OneToMany invers, required
-- audit (§6)
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT fk_env_system FOREIGN KEY (system_id) REFERENCES information_system(id),
CONSTRAINT chk_env_type CHECK (environment_type IN ('PRODUCTION','STAGING','TEST','DEV')),
CONSTRAINT chk_env_state CHECK (state IN ('UP','DEGRADED','DOWN','PLANNED','NONE'))
);
CREATE INDEX ix_env_system ON environment (system_id);
CREATE INDEX ix_env_state ON environment (state);

2.4 system_dependency — graf de dependențe direcționat (join auto-referențial)

Section titled “2.4 system_dependency — graf de dependențe direcționat (join auto-referențial)”
CREATE TABLE system_dependency (
id BIGINT NOT NULL PRIMARY KEY,
kind VARCHAR(32) NOT NULL,
required BOOLEAN NOT NULL,
note VARCHAR(500),
system_id BIGINT NOT NULL, -- consumatorul (system)
depends_on_id BIGINT NOT NULL, -- serviciul consumat (dependsOn)
-- audit (§6)
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT fk_dep_system FOREIGN KEY (system_id) REFERENCES information_system(id),
CONSTRAINT fk_dep_depends_on FOREIGN KEY (depends_on_id) REFERENCES information_system(id),
CONSTRAINT chk_dep_kind CHECK (kind IN ('API','EVENT','DATA','AUTH','STORAGE')),
-- un sistem nu depinde de sine
CONSTRAINT chk_dep_no_self CHECK (system_id <> depends_on_id)
);
-- o pereche (consumator, furnizor) apare o singură dată per tip de dependență
CREATE UNIQUE INDEX ux_dep_edge ON system_dependency (system_id, depends_on_id, kind);
CREATE INDEX ix_dep_system ON system_dependency (system_id, depends_on_id);
CREATE INDEX ix_dep_depends_on ON system_dependency (depends_on_id);

Coloana required este un cuvânt permis ca identificator în PostgreSQL, dar pentru siguranță în interogări scrise manual poate fi citată ("required"). JHipster o generează nescăpată. chk_dep_no_self și ux_dep_edge nu provin din JDL — recomandate; vezi §8.

2.5 publication_request — fluxul de aprobare pentru publicarea în GPortal

Section titled “2.5 publication_request — fluxul de aprobare pentru publicarea în GPortal”
CREATE TABLE publication_request (
id BIGINT NOT NULL PRIMARY KEY,
state VARCHAR(32) NOT NULL,
requested_by VARCHAR(160),
requested_at TIMESTAMPTZ,
approved_by VARCHAR(160),
approved_at TIMESTAMPTZ,
published_at TIMESTAMPTZ,
requested_visibility VARCHAR(32),
comment VARCHAR(1000),
system_id BIGINT NOT NULL, -- OneToMany invers, required
-- audit (§6)
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT fk_pubreq_system FOREIGN KEY (system_id) REFERENCES information_system(id),
CONSTRAINT chk_pubreq_state CHECK (state IN (
'DRAFT','PROPOSED','APPROVED','PUBLISHED','REJECTED','UNPUBLISHED'
)),
CONSTRAINT chk_pubreq_req_vis CHECK (requested_visibility IS NULL OR requested_visibility IN (
'PUBLIC','INTERNAL','TEST'
))
);
CREATE INDEX ix_pubreq_system ON publication_request (system_id);
CREATE INDEX ix_pubreq_state ON publication_request (state);

2.6 api_endpoint — suprafața API expusă de un sistem

Section titled “2.6 api_endpoint — suprafața API expusă de un sistem”
CREATE TABLE api_endpoint (
id BIGINT NOT NULL PRIMARY KEY,
path VARCHAR(300) NOT NULL,
protocol VARCHAR(32) NOT NULL,
method VARCHAR(16),
description VARCHAR(500),
public_api BOOLEAN NOT NULL,
system_id BIGINT NOT NULL, -- OneToMany invers, required
-- audit (§6)
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT fk_api_system FOREIGN KEY (system_id) REFERENCES information_system(id),
CONSTRAINT chk_api_protocol CHECK (protocol IN ('REST','GRAPHQL','SOAP','GRPC','EVENT'))
);
CREATE INDEX ix_api_system ON api_endpoint (system_id);
CREATE INDEX ix_api_public ON api_endpoint (public_api) WHERE public_api = TRUE;

2.7 registry_consumer — consumator înregistrat de date de registru

Section titled “2.7 registry_consumer — consumator înregistrat de date de registru”
CREATE TABLE registry_consumer (
id BIGINT NOT NULL PRIMARY KEY,
name VARCHAR(160) NOT NULL,
description VARCHAR(500),
filter_expression VARCHAR(200),
active BOOLEAN NOT NULL,
-- audit (§6)
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ
);
CREATE INDEX ix_consumer_active ON registry_consumer (active) WHERE active = TRUE;

2.8 registry_audit — jurnal de audit imutabil (append-only)

Section titled “2.8 registry_audit — jurnal de audit imutabil (append-only)”
CREATE TABLE registry_audit (
id BIGINT NOT NULL PRIMARY KEY,
change_type VARCHAR(32) NOT NULL,
actor VARCHAR(160) NOT NULL,
summary_ro VARCHAR(500),
summary_en VARCHAR(500),
ip_address VARCHAR(64),
occurred_at TIMESTAMPTZ NOT NULL,
system_id BIGINT, -- OneToMany invers, opțional
-- audit (§6) — redundant cu actor/occurred_at, păstrat pentru uniformitate
created_by VARCHAR(50) NOT NULL,
created_date TIMESTAMPTZ,
last_modified_by VARCHAR(50),
last_modified_date TIMESTAMPTZ,
CONSTRAINT fk_audit_system FOREIGN KEY (system_id) REFERENCES information_system(id),
CONSTRAINT chk_audit_change_type CHECK (change_type IN (
'CREATE','EDIT','PUBLISH','UNPUBLISH','ENV_CHANGE',
'DEPENDENCY_CHANGE','STATUS_CHANGE','DELETE'
))
);
CREATE INDEX ix_audit_system_time ON registry_audit (system_id, occurred_at DESC);
CREATE INDEX ix_audit_change_type ON registry_audit (change_type);
CREATE INDEX ix_audit_actor ON registry_audit (actor);

Append-only impus și la nivel de DB — vezi §5. system_id este opțional (relația RegistryAudit{system} din JDL nu are required): pot exista intrări de audit care nu vizează un sistem anume (ex. acțiuni la nivel de registru).

Autentificarea GRegistry este OAuth2 / OIDC (authenticationType oauth2), delegată către GSSO / Keycloak — nu există login/parolă local, deci nu se generează tabelele jhi_user / jhi_authority cu password_hash ca la un monolit JWT. Rămân totuși generate, pentru referință (nu se editează manual):

  • jhi_user — proiecție locală a utilizatorului OIDC (login, email, activated, lang_key), populată la primul login din claims-urile token-ului.
  • jhi_authority — name PK ∈ {ROLE_ADMIN, ROLE_USER} — mapate din rolurile Keycloak.
  • jhi_persistent_audit_event, jhi_persistent_audit_evt_data — audit Spring Boot.
  • databasechangelog, databasechangeloglock — Liquibase.

Rolurile funcționale (ADMIN de registru, editor de sistem, aprobator de publicare) provin din authorities Spring Security (ROLE_*), mapate din Keycloak — nu există o entitate de domeniu AppUser separată în acest sistem.

-- Calea de citire a consumatorilor (GPortal): "dă-mi sistemele publice publicate".
-- Deja acoperit de ix_is_visibility_published; index parțial mai strâns:
CREATE INDEX ix_is_portal_feed ON information_system (visibility)
WHERE visibility = 'PUBLIC' AND published_to_portal = TRUE;
-- Căutare frecventă după cod (join key folosit de consumatori) — acoperit de
-- ux_is_code (unique), dar util pentru prefix-search pe cod:
CREATE INDEX ix_is_code_pattern ON information_system (code varchar_pattern_ops);
-- Matricea de dependențe "consumat de X" / "depinde de Y":
-- ix_dep_system (system_id, depends_on_id) + ix_dep_depends_on acoperă ambele sensuri.
-- Raport "ce a schimbat actorul X în perioada Y":
CREATE INDEX ix_audit_actor_time ON registry_audit (actor, occurred_at DESC);
-- Medii nesănătoase pentru dashboard de monitorizare:
CREATE INDEX ix_env_unhealthy ON environment (system_id)
WHERE state IN ('DEGRADED','DOWN');
-- Cereri de publicare în lucru (coada de aprobare):
CREATE INDEX ix_pubreq_open ON publication_request (state)
WHERE state IN ('PROPOSED','APPROVED');
-- Full-text local pe nume/descriere sistem (fără Elasticsearch în GRegistry):
CREATE INDEX ix_is_name_fts ON information_system
USING gin (to_tsvector('simple', name || ' ' || coalesce(description_ro,'')));

5. Trigger append-only pentru registry_audit

Section titled “5. Trigger append-only pentru registry_audit”

RegistryAudit este imutabil din perspectiva aplicației (jurnal de audit, forwardat și către GLog). Se impune și la nivel de DB, nu doar în service layer:

CREATE OR REPLACE FUNCTION registry_audit_no_modify()
RETURNS TRIGGER AS $$
BEGIN
RAISE EXCEPTION 'registry_audit este append-only (% blocat)', TG_OP;
END;
$$ LANGUAGE plpgsql;
CREATE TRIGGER tr_audit_no_update BEFORE UPDATE ON registry_audit
FOR EACH ROW EXECUTE FUNCTION registry_audit_no_modify();
CREATE TRIGGER tr_audit_no_delete BEFORE DELETE ON registry_audit
FOR EACH ROW EXECUTE FUNCTION registry_audit_no_modify();

Notă: Liquibase rulează cu utilizator privilegiat; pentru migrări care chiar trebuie să atingă istoricul (caz extrem), trigger-ele se dezactivează temporar într-un changelog dedicat și se reactivează imediat după. La nivel de service layer, RegistryAuditRepository trebuie să interzică save pe entități existente și delete (append-only din JDL: „enforce no-update/no-delete in the service layer”).

Toate entitățile moștenesc (de la AbstractAuditingEntity + SpringSecurityAuditorAware):

ColoanăTipPopulare
created_byVARCHAR(50) NOT NULLlogin OIDC al utilizatorului la INSERT
created_dateTIMESTAMPTZInstant.now() la INSERT
last_modified_byVARCHAR(50)login OIDC al utilizatorului la UPDATE
last_modified_dateTIMESTAMPTZInstant.now() la UPDATE

Pe registry_audit aceste câmpuri coexistă cu actor/occurred_at de domeniu (vezi nota din §2.8); pe information_system coexistă cu created_at/updated_at de domeniu (vezi §2.2). Ambele suprapuneri sunt de revizuit în §8.

7. Conversie JSONB — N/A pentru GRegistry

Section titled “7. Conversie JSONB — N/A pentru GRegistry”

Spre deosebire de Registrul Interdicțiilor (unde Entitate.atribute, ActiuneInterzisa.scope, EvenimentInterdictie.payload, Notificare.payload sunt TextBlob → jsonb), GRegistry nu are niciun câmp TextBlob/JSON. Toate câmpurile din JDL sunt String (→ VARCHAR), Boolean (→ BOOLEAN), enum (→ VARCHAR + CHECK) sau Instant (→ TIMESTAMPTZ).

Prin urmare:

  • nu există changelog Liquibase de conversie TEXT → jsonb;
  • nu există adnotări @JdbcTypeCode(SqlTypes.JSON) / hypersistence-utils;
  • nu există indici GIN pe coloane jsonb (singurul GIN recomandat este cel full-text opțional din §4).

Dacă în viitor system_dependency.note sau api_endpoint.description evoluează spre metadate structurate (ex. spec OpenAPI inline), se va deschide un ADR și un changelog de conversie separat — momentan nu e cazul.

  • Dublă marcă temporală pe information_system — created_at/updated_at (domeniu, JDL) vs. created_date/last_modified_date (audit). Se păstrează ambele sau se elimină cele de domeniu și se folosesc doar cele de audit?
  • registry_audit audit-pe-audit — created_by/created_date sunt redundante cu actor/occurred_at. Se dezactivează AbstractAuditingEntity pe această entitate (nu extinde) sau se acceptă redundanța?
  • chk_dep_no_self și ux_dep_edge — nu provin din JDL. Confirmă dacă un sistem poate depinde de sine (probabil nu) și dacă pot exista muchii duplicate consumator→furnizor de același kind.
  • Detecție cicluri în graful de dependențe — SystemDependency permite cicluri la nivel de DB. Validarea aciclicității (dacă e cerută) se face în service layer, nu prin constrângere SQL.
  • ON DELETE pe FK-uri — la ștergerea unui information_system, ce se întâmplă cu environment/api_endpoint/publication_request (CASCADE?) și cu system_dependency (muchii orfane)? registry_audit nu trebuie să cascadeze (audit nu se șterge — de altfel e append-only).
  • Soft-delete pe information_system — pentru un registru autoritar, ștergerea fizică e probabil interzisă; se preferă status = RETIRED. Dacă e nevoie de tombstone, adaugă deleted_at TIMESTAMPTZ.
  • Consistență flux publicare — information_system.published_to_portal este denormalizat față de publication_request.state = PUBLISHED. Cine sincronizează flagul (service layer) și cum se previne divergența?
  • Unicitate publication_request — se permite o singură cerere activă (state ∈ {DRAFT,PROPOSED,APPROVED}) per sistem? Dacă da → unique parțial.
  • Lungimi VARCHAR(32) pentru enum-uri — cea mai lungă valoare este DEPENDENCY_CHANGE (17) / INFRASTRUCTURE (14); 32 e suficient cu marjă.
  • Validări impuse doar la aplicație — provider.code minlength(2), information_system.code pattern ^[a-z][a-z0-9-]*$, provider.contact_email pattern e-mail: sunt Bean Validation, nu constrângeri DB. De decis dacă se adaugă CHECK-uri de oglindire în DB.
  • Retenție registry_audit — crește nelimitat + e forwardat la GLog. Politică de arhivare/partitioning pe occurred_at?