Integrare multi-protocol — Registrul Interdicțiilor
Acest conținut nu este încă disponibil în limba selectată.
Specificație tehnică pentru cerința de interoperabilitate (caiet_de_sarcini.md §6, CF-11): aplicația trebuie consumabilă dinamic, din orice limbaj de
programare (Java, .NET, Python, Go și altele), prin trei protocoale
paralele: REST/OpenAPI, gRPC și TCP raw.
Acest document e scris pentru dezvoltatori — atât echipa internă, cât și
integratorii externi (bănci, ANAF, alte sisteme govtech) care vor consuma
registrul. Pentru cerințele de business, vezi caiet_de_sarcini.md. Pentru
arhitectura de bază a aplicației, vezi platform_design.md.
1. Scop și principiu de bază
Section titled “1. Scop și principiu de bază”Principiu nenegociabil: toate cele trei protocoale apelează același
strat de servicii de business (service.*Service din arhitectura existentă,
vezi CLAUDE.md). Niciun protocol nu reimplementează validări, tranziții de
stare sau reguli de scope — fiecare e doar un front de transport diferit peste
aceeași logică.
┌─────────────────────────────────────┐ │ service.*Service (+ impl/) │ │ — logica de business unică — │ │ validări, tranziții de stare, │ │ scope ActiuneInterzisa, audit │ └───────────────┬───────────────────────┘ ┌─────────────────────┼─────────────────────┐ │ │ │ ┌──────────▼─────────┐ ┌─────────▼──────────┐ ┌────────▼───────────┐ │ REST Controller │ │ gRPC Service impl │ │ TCP Frame Handler │ │ web.rest.*Resource │ │ grpc.*ServiceImpl │ │ tcp.*FrameHandler │ │ port 8080 │ │ port 9090 │ │ port 9091 │ └──────────┬─────────┘ └─────────┬──────────┘ └────────┬───────────┘ │ │ │ HTTP/JSON gRPC/protobuf TCP/length-prefixed JSON │ │ │ ┌───────▼───────┐ ┌────────▼────────┐ ┌───────▼────────┐ │ Client Java/ │ │ Client Java/ │ │ Client Java/ │ │ .NET/Python/Go │ │ .NET/Python/Go │ │ .NET/Python/Go │ │ (SDK generat) │ │ (stub generat) │ │ (socket manual) │ └───────────────┘ └─────────────────┘ └─────────────────┘Toate trei front-urile trăiesc într-un modul nou integration (sau pachete
web.rest existent + grpc + tcp noi), fără duplicare de DTO-uri de
business — reutilizează service.dto existent unde e posibil.
2. Arhitectura gateway-ului de integrare
Section titled “2. Arhitectura gateway-ului de integrare”flowchart TB subgraph Clients["Clienți externi (orice limbaj)"] JavaC["Java"] DotnetC[".NET"] PyC["Python"] GoC["Go"] end
subgraph Frontends["Fronturi de protocol (md.gov.interdictii)"] REST["REST Controllers<br/>:8080 /api/v1/*"] GRPC["gRPC Services<br/>:9090"] TCP["TCP Frame Handler<br/>:9091"] end
subgraph Core["Strat comun"] Auth["AuthN/AuthZ adapter<br/>(JWT / API key / mTLS / token)"] RateLimit["Rate limiter comun"] ErrMap["Mapare erori unitară"] Svc["service.*Service<br/>(business logic unică)"] AuditLog["Audit log consultări externe"] end
JavaC & DotnetC & PyC & GoC -->|HTTPS+JSON| REST JavaC & DotnetC & PyC & GoC -->|HTTP/2+protobuf| GRPC JavaC & DotnetC & PyC & GoC -->|TCP socket| TCP
REST --> Auth GRPC --> Auth TCP --> Auth Auth --> RateLimit --> Svc Svc --> AuditLog REST --> ErrMap GRPC --> ErrMap TCP --> ErrMap3. Protocol 1 — REST + OpenAPI
Section titled “3. Protocol 1 — REST + OpenAPI”3.1 Versionare și expunere
Section titled “3.1 Versionare și expunere”- Toate rutele sub
/api/v1/...(versionare explicită în path, pentru compatibilitate pe termen lung). - Specificația OpenAPI 3.x generată automat din controllere via
springdoc-openapi(compatibil nativ cu stack-ul Spring Boot 3 existent), publicată la/v3/api-docsși UI la/swagger-ui.html. - Endpoint-urile interne (portal operator) și cele publice (consultare
externă) sunt documentate în aceeași spec, dar grupate sub tag-uri
separate (
internal,public) pentru claritate la generarea SDK-urilor.
3.2 Autentificare
Section titled “3.2 Autentificare”| Consumator | Mecanism |
|---|---|
| Portal Angular (intern) | JWT (HS512), conform fluxului existent /api/authenticate |
| Sisteme externe (bănci, ANAF, etc.) | API key (header X-Api-Key) sau OAuth2 client-credentials (token bearer), configurabil per consumator |
3.3 Generare SDK client
Section titled “3.3 Generare SDK client”Comenzi de referință (openapi-generator-cli, rulate din CI/CD după fiecare
release de API):
openapi-generator-cli generate -i openapi.yaml -g java -o sdk/java --additional-properties=library=resttemplateopenapi-generator-cli generate -i openapi.yaml -g csharp -o sdk/dotnet --additional-properties=targetFramework=net8.0openapi-generator-cli generate -i openapi.yaml -g python -o sdk/pythonopenapi-generator-cli generate -i openapi.yaml -g go -o sdk/goPublicare: pachetele generate se publică în registry-ul intern (GitLab
Package Registry, pe git.esempla.systems/govtech/gstack/* — repo-ul SDK-
urilor poate fi propriu, ex. saas_interdictii-sdk), nu pe registry-uri
publice (Maven Central, NuGet, PyPI, Go proxy public), din motive de
suveranitate a datelor (CNF-9 din caiet de sarcini).
3.4 Exemplu request/response
Section titled “3.4 Exemplu request/response”GET /api/v1/public/interdictii?identificator=2002004001234 HTTP/1.1Host: interdictii.gov.mdX-Api-Key: <api-key-consumator>{ "rezultate": [ { "interdictieId": 4821, "statut": "ACTIV", "categorieDomeniu": "PENAL", "startDate": "2026-01-15", "endDate": null, "autoritate": "Judecătoria Chișinău", "actiuniInterzise": [ { "actiune": "IESIRE_TARA", "intensitate": "TOTALA" } ] } ]}Câmpurile sensibile (ex. description, detalii atribute interne) sunt
mascate conform CF-10.2 din caietul de sarcini — nu apar în răspunsul public.
4. Protocol 2 — gRPC
Section titled “4. Protocol 2 — gRPC”4.1 Fișiere .proto
Section titled “4.1 Fișiere .proto”Mapare 1:1 pe domeniul existent. Exemplu (interdictie.proto):
syntax = "proto3";package md.gov.interdictii.grpc;
option java_package = "md.gov.interdictii.grpc";option csharp_namespace = "Md.Gov.Interdictii.Grpc";
service InterdictieService { rpc GetByIdentificator(IdentificatorRequest) returns (InterdictieListResponse); rpc StreamEvenimente(InterdictieIdRequest) returns (stream EvenimentInterdictie);}
message IdentificatorRequest { string identificator = 1; string categorie = 2; // optional filtru CategorieEntitate}
message InterdictieListResponse { repeated Interdictie rezultate = 1;}
message Interdictie { int64 id = 1; string statut = 2; string categorieDomeniu = 3; string startDate = 4; string endDate = 5; string autoritate = 6;}
message EvenimentInterdictie { string tip = 1; string actor = 2; string data = 3;}
message InterdictieIdRequest { int64 interdictieId = 1;}4.2 Server
Section titled “4.2 Server”grpc-spring-boot-starter(saugrpc-server-spring-boot-starterde lanet.devh) — server gRPC rulat ca bean Spring Boot, port separat 9090, în același proces ca aplicația principală (monolith, fără serviciu nou de deploy).- Implementarea
InterdictieServiceImpl extends InterdictieServiceGrpc.InterdictieServiceImplBaseapelează directservice.InterdictieServiceexistent — zero logică duplicată.
4.3 Generare stub-uri client
Section titled “4.3 Generare stub-uri client”protoc --java_out=sdk/java/grpc --grpc-java_out=sdk/java/grpc interdictie.protoprotoc --csharp_out=sdk/dotnet/grpc --grpc_out=sdk/dotnet/grpc interdictie.protopython -m grpc_tools.protoc -I. --python_out=sdk/python/grpc --grpc_python_out=sdk/python/grpc interdictie.protoprotoc --go_out=sdk/go/grpc --go-grpc_out=sdk/go/grpc interdictie.proto4.4 Autentificare
Section titled “4.4 Autentificare”- mTLS pentru consumatori server-to-server cu certificat emis de CA internă,
sau token bearer transmis prin gRPC metadata (
authorization: Bearer <token>) — alegere per consumator, documentată în contractul de onboarding.
5. Protocol 3 — TCP raw
Section titled “5. Protocol 3 — TCP raw”5.1 Motivație
Section titled “5.1 Motivație”Pentru consumatori cu cerințe de latență minimă și volum mare (ex. o bancă care verifică zilnic zeci de mii de IDNP-uri în batch, sau verificare în timp real la fiecare tranzacție), overhead-ul HTTP (headere, TLS handshake per request fără keep-alive corect configurat, parsare HTTP) poate fi nejustificat. Protocolul TCP raw permite o conexiune persistentă, cu framing minimal și parsare JSON simplă — implementabilă nativ în orice limbaj cu suport de socket-uri (Java, .NET, Python, Go au toate API de socket TCP în biblioteca standard, fără dependențe externe).
5.2 Framing
Section titled “5.2 Framing”┌────────────────────────────┬──────────────────────────────────┐│ 4 bytes, big-endian uint32 │ N bytes, UTF-8 JSON ││ = lungimea payload-ului │ = payload-ul mesajului │└────────────────────────────┴──────────────────────────────────┘- Lungimea nu include cei 4 bytes de prefix, doar payload-ul JSON.
- Encoding text: UTF-8 (necesar pentru caractere românești în câmpuri precum
numeAfisat). - Limită maximă de payload: 1 MiB per mesaj (protecție DoS) — mesaje mai
mari sunt respinse cu
ERROR/PAYLOAD_TOO_LARGE.
5.3 Schema mesaj JSON
Section titled “5.3 Schema mesaj JSON”{ "id": "uuid-sau-secventa-client", "type": "REQUEST | RESPONSE | EVENT | ERROR", "op": "string — numele operațiunii", "payload": { }, "authToken": "string — doar pe primul mesaj de pe conexiune (handshake)"}id— generat de client peREQUEST, ecou-uit identic peRESPONSE/ERRORcorespunzător, pentru a permite pipelining (mai multe request-uri in-flight pe aceeași conexiune, fără a aștepta răspunsul fiecăruia).authToken— obligatoriu doar pe primul mesaj trimis de client după conectare (handshake). Serverul validează și asociază token-ul sesiunii TCP; mesajele următoare pe aceeași conexiune nu retrimit token-ul.
5.4 Operații suportate (set inițial)
Section titled “5.4 Operații suportate (set inițial)”op | Descriere | payload request | payload response |
|---|---|---|---|
AUTH | Handshake — prima operație obligatorie pe conexiune | { "token": "..." } | { "ok": true, "sessionId": "..." } |
CHECK_INTERDICTIE | Verifică dacă o entitate are interdicție activă | { "identificator": "...", "categorie": "PF" } | { "areInterdictie": true, "interdictii": [...] } |
GET_BY_IDNP | Listă completă interdicții active pentru IDNP | { "idnp": "2002004001234" } | { "rezultate": [...] } |
GET_BY_IDNO | Idem pentru IDNO (persoană juridică) | { "idno": "..." } | { "rezultate": [...] } |
SUBSCRIBE_EVENTS | Abonare la evenimente noi (push asincron de tip EVENT pe aceeași conexiune) | { "filtru": { "categorieDomeniu": "FISCAL" } } | { "subscribed": true } urmat de mesaje EVENT |
PING | Keep-alive | {} | { "pong": true } |
Răspunsurile de eroare folosesc type: "ERROR" cu payload
{ "code": "STRING_CODE", "message": "..." } — coduri partajate cu celelalte
două protocoale (vezi §6).
5.5 Implementare server
Section titled “5.5 Implementare server”- Bază: Netty (preferat pentru throughput și model non-blocking) sau Java
NIO direct, ca bean Spring Boot independent, rulat pe port propus 9091,
pornit/oprit împreună cu ciclul de viață al aplicației (
SmartLifecycle). - Fiecare conexiune TCP = o sesiune; după
AUTHreușit, sesiunea e legată de identitatea consumatorului (pentru rate limiting și audit). - Thread model: event loop Netty (sau pool dedicat dacă NIO simplu),
delegă procesarea business către
service.*Servicepe un executor separat pentru a nu bloca I/O.
5.6 Heartbeat și reconectare
Section titled “5.6 Heartbeat și reconectare”- Server trimite/așteaptă
PING/PONGla fiecare 30s; absența unuiPONGîn 90s → server închide conexiunea. - Clienții implementează reconectare cu backoff exponențial (ex. 1s, 2s, 4s,
max 30s) și re-
AUTHautomat la reconectare.
5.7 Exemple client minimale (schiță)
Section titled “5.7 Exemple client minimale (schiță)”Java:
Socket socket = new Socket("interdictii.gov.md", 9091);DataOutputStream out = new DataOutputStream(socket.getOutputStream());byte[] json = "{\"id\":\"1\",\"type\":\"REQUEST\",\"op\":\"AUTH\",\"authToken\":\"...\"}" .getBytes(StandardCharsets.UTF_8);out.writeInt(json.length); // big-endian implicit cu DataOutputStreamout.write(json);C# (.NET):
using var client = new TcpClient("interdictii.gov.md", 9091);using var stream = client.GetStream();var json = Encoding.UTF8.GetBytes("{\"id\":\"1\",\"type\":\"REQUEST\",\"op\":\"AUTH\",\"authToken\":\"...\"}");var lenPrefix = BitConverter.GetBytes(json.Length);Array.Reverse(lenPrefix); // big-endianawait stream.WriteAsync(lenPrefix); await stream.WriteAsync(json);Python:
import socket, struct, json as jss = socket.create_connection(("interdictii.gov.md", 9091))payload = js.dumps({"id": "1", "type": "REQUEST", "op": "AUTH", "authToken": "..."}).encode("utf-8")s.sendall(struct.pack(">I", len(payload)) + payload)Go:
conn, _ := net.Dial("tcp", "interdictii.gov.md:9091")payload, _ := json.Marshal(map[string]string{"id": "1", "type": "REQUEST", "op": "AUTH", "authToken": "..."})lenBuf := make([]byte, 4)binary.BigEndian.PutUint32(lenBuf, uint32(len(payload)))conn.Write(lenBuf)conn.Write(payload)Toate patru exemplele demonstrează același framing — niciun limbaj necesită biblioteci externe pentru a vorbi acest protocol.
6. Strat comun cross-protocol
Section titled “6. Strat comun cross-protocol”6.1 Mapare unitară a erorilor
Section titled “6.1 Mapare unitară a erorilor”Indiferent de protocol, erorile de business folosesc același set de coduri:
| Cod | Sens | HTTP status echivalent | gRPC status echivalent |
|---|---|---|---|
NOT_FOUND | Entitate/interdicție inexistentă | 404 | NOT_FOUND |
INVALID_TRANSITION | Tranziție de statut nepermisă | 409 | FAILED_PRECONDITION |
VALIDATION_ERROR | Date de intrare invalide | 400 | INVALID_ARGUMENT |
UNAUTHENTICATED | Token/API key invalid sau absent | 401 | UNAUTHENTICATED |
RATE_LIMITED | Limită de request-uri atinsă | 429 | RESOURCE_EXHAUSTED |
PAYLOAD_TOO_LARGE | Mesaj TCP peste limita de 1 MiB | 413 | INVALID_ARGUMENT |
INTERNAL | Eroare neașteptată server | 500 | INTERNAL |
6.2 Rate limiting comun
Section titled “6.2 Rate limiting comun”- Un singur serviciu de rate limiting (ex. bucket per consumator, identificat
prin API key / mTLS subject / token de sesiune TCP), consultat de toate
cele trei fronturi înainte de a apela
service.*Service. - Limitele implicite (configurabile per consumator): N request-uri/secundă pentru REST/gRPC, N mesaje/secundă pentru TCP.
6.3 Audit logging al consultărilor externe
Section titled “6.3 Audit logging al consultărilor externe”Reia fluxul deja documentat în platform_design.md §5.3 (activity) și §6.3
(sequence) pentru consultarea publică — extins să acopere uniform toate cele
trei protocoale: fiecare consultare externă (cine, ce identificator, când,
prin ce protocol) e logată în jurnalul de audit existent.
7. Securitate — rezumat comparativ
Section titled “7. Securitate — rezumat comparativ”| Protocol | Autentificare | Transport |
|---|---|---|
| REST (portal intern) | JWT HS512 | HTTPS |
| REST (extern) | API key sau OAuth2 client-credentials | HTTPS |
| gRPC | mTLS sau token în metadata | HTTP/2 + TLS |
| TCP raw | Token în mesajul AUTH (handshake) | TLS opțional (recomandat TLS peste socket pentru producție — SSLSocket/echivalent per limbaj) |
8. Versionare și compatibilitate
Section titled “8. Versionare și compatibilitate”- OpenAPI: versionare semantică în path (
/api/v1,/api/v2la breaking change); spec publicată la fiecare release. .proto: câmpuri noi doar adăugate (niciodată renumerotate/șterse) — regulă standard protobuf de backward-compatibility.- Schema JSON TCP: câmpuri noi opționale;
op-uri noi pot fi adăugate fără breaking change; eliminarea unuiopnecesită perioadă de deprecation anunțată (minim 6 luni) consumatorilor înregistrați.
9. Testare interoperabilitate
Section titled “9. Testare interoperabilitate”- Contract testing: spec OpenAPI + fișiere
.protoca sursă unică de adevăr pentru contractul REST/gRPC; validare automată în CI că implementarea respectă contractul (ex.openapi-generatorvalidate mode). - Smoke tests per SDK generat: pipeline CI separat per limbaj (Java, .NET, Python, Go) care instanțiază SDK-ul generat și execută un apel real către mediul de testare, pentru fiecare din cele 3 protocoale.
- Test TCP dedicat: test automat care deschide o conexiune raw, trimite
AUTH+CHECK_INTERDICTIE, verifică framing-ul răspunsului byte-cu-byte.
10. Decizii deschise / propuneri pentru model (neaplicate încă)
Section titled “10. Decizii deschise / propuneri pentru model (neaplicate încă)”Următoarele necesită confirmare explicită înainte de a fi adăugate în
interdictii.jdl — nu sunt aplicate în această etapă, doar documentate
ca propuneri:
- Entitate
ApiConsumer— consumator extern înregistrat (nume, organizație, protocoale permise, status activ/suspendat). - Entitate
ApiKey(sauApiCredential) — cheie/credențial asociat unuiApiConsumer, cuexpiresAt,scopes,rateLimit. - Mapare
ApiConsumer↔ protocoale permise (un consumator poate fi restricționat doar la REST, sau autorizat și pentru TCP raw). - Tabelă/contor de rate-limit persistat (sau soluție in-memory
distribuită, ex. Redis, dacă se trece la deployment orizontal —
menționat deja ca opțiune în
platform_design.md §7.7).
11. Roadmap implementare strat de integrare
Section titled “11. Roadmap implementare strat de integrare”Aliniat cu platform_design.md §7.6, ca sub-fază a Fazei 1:
| Pas | Conținut |
|---|---|
| 1 | REST + OpenAPI peste serviciile existente (/api/v1), publicare spec |
| 2 | Generare + publicare SDK Java/.NET/Python/Go pentru REST, în CI |
| 3 | Server gRPC (port 9090) + .proto + stub-uri generate |
| 4 | Server TCP raw (port 9091) — framing, AUTH, CHECK_INTERDICTIE, GET_BY_IDNP/GET_BY_IDNO |
| 5 | SUBSCRIBE_EVENTS (push asincron pe TCP) + rate limiting comun + audit unificat |
| 6 | Hardening: mTLS gRPC, TLS opțional pe TCP raw, teste de contract automate în CI |