Sari la conținut
← Studii de caz
Captură de ecran din facturom: platforma de facturare pe care o construim și o operăm

Platform Engineering4 min read

facturom: platforma de facturare pe care o construim și o operăm

Runtime-ul din spatele platformei noastre de facturare — un API gateway, API-uri publice și de administrare separate, patru worker-i dedicați integrărilor și un simulator pentru API-ul guvernamental pe care nu îl putem bombarda în development.

  • Platform Engineering
  • Microservices
  • Docker
  • Self-hosted

Context

facturom (facturom.ro) este propria noastră platformă de facturare, aflată în prezent în development. Facturarea în România înseamnă două dependențe externe dure: ANAF, autoritatea fiscală, cu al cărei API de e-facturare trebuie să se integreze orice platformă conformă, și BNR, banca națională, pentru cursurile valutare. Înseamnă și gestionarea documentelor, a secretelor și a e-mailului la un standard la care ne-ar fi rușine să greșim.

Acest studiu de caz nu este despre produs. Este despre runtime — pentru că noi construim facturom și îl operăm, pe propria infrastructură, iar platform engineering-ul este partea care se transferă direct în munca pentru clienți. Stack-ul înseamnă peste o duzină de servicii, fiecare dintre ele rulând în producție chiar acum.

Constrângeri

  • Un API guvernamental pe care nu îl poți bombarda. Endpoint-urile de e-facturare ale ANAF nu sunt un sandbox pe care să iterezi liber. Development-ul și CI-ul trebuie să exerseze integrarea end-to-end fără să atingă vreodată sistemul real.
  • Secrete în multe servicii. Peste o duzină de containere, fiecare având nevoie de credențiale — așa ajung secretele împrăștiate prin fișiere env. Am vrut un singur loc în care trăiesc secretele și un audit trail pentru cine le citește.
  • O echipă mică. Fiecare serviciu pe care îl adăugăm este un serviciu pe care îl operăm. Arhitectura trebuia să fie exact atât de „micro” cât era necesar și nimic mai mult.

Abordare

Forma platformei:

gateway
├── core-api   — public API
├── admin-api  — back-office API, separate service, separate surface
├── web / site / admin-web — frontends
├── email      — outbound mail worker
├── enrich     — data enrichment worker
├── anaf       — e-invoicing integration worker
└── bnr        — exchange-rate worker
supporting: secrets manager · object storage · cache · Postgres · ANAF simulator

Un singur gateway, API-uri separate. Totul intră prin gateway. API-ul public și API-ul de administrare sunt servicii separate, cu suprafețe de atac separate — un bug din partea de administrare nu poate fi atins prin calea publică.

Un worker per integrare. Email, enrichment, ANAF și BNR rulează fiecare în propriul container. Scopul este izolarea eșecurilor: când un endpoint guvernamental are o după-amiază proastă, worker-ul ANAF face back-off și reîncearcă, în timp ce facturarea, e-mailul și tot restul merg mai departe. Fiecare worker vine cu un healthcheck de container, așa că o integrare blocată este vizibilă în docker ps, nu descoperită într-un tichet de suport.

Secretele într-un secrets manager adevărat. Self-hosted și open source. Serviciile iau ce au nevoie; credențialele nu trăiesc în fișiere compose.

Starea acolo unde îi este locul. Postgres pentru înregistrări, object storage compatibil S3 pentru documentele generate și upload-uri, un cache dedicat pentru caching și cozi. Totul plictisitor, totul deliberat.

Simulatorul. Piesa pe care o considerăm cea mai interesantă din punct de vedere ingineresc: un simulator al API-ului ANAF rulează ca parte a stack-ului, astfel încât întregul flux de e-facturare — trimitere, polling, căi de eroare — poate fi testat local și în CI împotriva unui serviciu care se comportă ca API-ul guvernamental, fără API-ul guvernamental. Codul de integrare care poate fi testat doar în producție este cod de integrare care eșuează în producție.

Fiecare serviciu este construit în CI și tras dintr-un registry privat ca imagine cu tag. Deploy-ul este docker compose up -d cu tag-uri fixate — aceeași disciplină pe care o descriem în toată infrastructura noastră.

Ce a fost greu

Lista sinceră. În primul rând, integrarea ANAF în sine — API-urile externe cu semantică strictă și procesare asincronă sunt locul unde codul optimist moare, iar simulatorul există pentru că aveam nevoie să reproducem modurile de eșec la cerere. În al doilea rând, păstrarea uniformității între atât de multe servicii: un singur pattern de packaging, o singură abordare de logging, o singură convenție de healthcheck, rezistând entropiei în care fiecare serviciu devine un caz special. În al treilea rând, să știm când să ne oprim din împărțit — fiecare graniță din diagramă există dintr-un motiv operațional, nu de dragul diagramelor de arhitectură.

Unde suntem acum

Platforma rulează astăzi, sănătoasă, pe propria noastră infrastructură, cu produsul încă în development deasupra ei. Pattern-urile — gateway în fața unor API-uri separate, un worker per dependență externă, secrete într-un vault, un simulator pentru integrarea pe care nu o poți repeta în producție — sunt aceleași pe care le aducem în platformele clienților.

Dacă platforma ta a crescut dincolo de ce poate duce un singur fișier compose și o rugăciune, vorbește cu noi.