Skip to content

Architecture

Daniel J. Saldaña edited this page Oct 29, 2026 · 1 revision

Architecture

🇪🇸 Español

Goliat separates collection (an Azure Function) from presentation (a Next.js application backed by PostgreSQL). They communicate through a single versioned JSON document in Blob Storage.

Azure Function (timer / queue / authenticated HTTP)
        │  Resource Graph + ARM, managed identity
        ▼
Blob Storage  ──  azure/latest.json (schemaVersion 2, ETag)
        │  read-only identity
        ▼
Next.js backend  ──  normaliser → PostgreSQL snapshot (one transaction)
        │
        ▼
Dashboard (React, shadcn/ui, Recharts)  ──  Entra ID or demo login
        │
        └──  FinOps → Azure Cost Management (server-side identity, Cost Management Reader)

Interactive diagrams of the application flow and the Azure deployment are available on the website and inside the dashboard. They are rendered with Archify from docs/architecture.json and docs/azure-deployment.json.

Components

Component Responsibility
Azure Function Collects resources with Reader permissions, builds and validates the JSON document, publishes it with an ETag condition. See Inventory Collector.
Blob Storage Holds azure/latest.json, deployment packages and the Function's operation state.
Next.js backend Downloads the blob, validates the contract, normalises 73 views and publishes them in one transaction. Serves protected APIs.
PostgreSQL Stores inventory snapshots, Goliat role assignments and their audit trail.
Dashboard React UI in English and Spanish, with charts, search, filters and export.

Manual refresh flow

  1. The Refresh button calls POST /api/inventory/azure/refresh/.
  2. The backend asks the Function for a new generation using an Entra token (role Inventory.Generate) and returns 202 with the operation ID.
  3. The UI polls the operation until it is succeeded.
  4. The backend downloads the blob with the confirmed ETag, normalises it and persists it.
  5. The UI reloads the views, keeping the subscription, filters and page.

A PostgreSQL advisory lock allows only one manual refresh at a time across all users and replicas. A second request receives 409 refresh_busy.

Data contract

The Function publishes UTF-8 JSON with schemaVersion: 2:

{
  "schemaVersion": 2,
  "provider": "azure",
  "generatedAt": "2026-10-09T05:00:00.000Z",
  "snapshot": {
    "catalog": [],
    "tagKeys": [],
    "reports": [],
    "totals": [],
    "tags": []
  }
}
  • reports contains 69 source reports, all required even when empty.
  • The backend derives four more views (subscriptions, resource totals and two tag groupings), giving 73 views.
  • Unknown versions, duplicate or missing reports are rejected.
  • The download uses If-Match and AZURE_MAX_OBJECT_MB. If the blob changes during the read or validation fails, the previous snapshot is kept.

Full contract: docs/backend.md and docs/azure-function.md.

Repository layout

Path Content
app/ Next.js App Router: pages, metadata, login, protected docs, read and refresh APIs.
src/ Components, hooks, services and i18n catalogues by feature. src/core/ has no React or HTTP.
server/ PostgreSQL pool, schema and migrations, authentication, access control, logging, FinOps, administration and CLI.
server/modules/azure/ Blob reader, normalisers, queries, repositories and seed data.
functions/ Independent Node.js Azure Functions collector.
iac/ Bicep and PowerShell for the Azure deployment.
docs/ Reference documentation and architecture diagrams.
site/ Static product website for GitHub Pages.
tests/ Unit, PostgreSQL integration and Playwright browser tests.

Scaling notes

  • The web never runs collectors or migrations at start-up and can be replicated behind a load balancer.
  • Migrations run as a separate pre-deployment job; the web process never executes DDL.
  • Snapshots are global. Running independent jobs per subscription would replace the visible inventory with partial results.
  • A paginated API (GET /api/inventory/azure/<reportId>/?limit=100) is available for large views; the current UI still uses the full snapshot endpoint.

Clone this wiki locally