1
Architecture
samantha edited this page 2026-03-21 07:26:15 +00:00

Architecture

Overview

The FP&A Engine is a single-binary Go application. There are no external services, no message queues, and no separate migration tools. SQLite is embedded via a pure-Go driver, so the entire application ships as one file.


Project Structure

FPandA-Engine/
├── main.go                   # Entry point: wiring, routing, graceful shutdown
├── Engine/
│   └── internal/
│       ├── database/         # DB connection, migration, and repository implementations
│       │   ├── connect.go
│       │   ├── migrate.go
│       │   ├── reference_repo.go
│       │   ├── budget_repo.go
│       │   └── actuals_repo.go
│       ├── handler/          # HTTP layer — decode request, call service, encode response
│       │   ├── reference.go
│       │   ├── budget.go
│       │   ├── actuals.go
│       │   └── variance.go
│       ├── model/            # Domain types (structs shared across layers)
│       │   ├── budget.go
│       │   ├── actuals.go
│       │   └── variance.go
│       └── service/          # Business logic
│           ├── budget_service.go
│           └── variance_service.go
├── tests/                    # Go test files (uses stdlib testing, not Python)
├── go.mod
├── go.sum
└── .gitignore

Layered Design

The application follows a clean three-layer architecture:

HTTP Request
     ↓
  Handler          ← decodes JSON, validates input, calls service, encodes response
     ↓
  Service          ← business logic: variance calculation, favourability, budget rules
     ↓
  Repository       ← SQL queries against SQLite via database/sql
     ↓
  SQLite (fpa.db)

Each layer depends only on interfaces one level below it. Services do not know about HTTP; repositories do not know about business rules.


Startup Sequence

main.go wires the application in this order:

  1. Logger (log/slog with JSON output to stdout)
  2. Database connection (database.Connect)
  3. Auto-migration (database.Migrate — idempotent CREATE TABLE IF NOT EXISTS)
  4. Reference repositories and handlers (departments, GL accounts — FK parents)
  5. Budget and actuals repositories
  6. Services wrapping those repositories
  7. Handlers wrapping those services
  8. Route registration on http.NewServeMux()
  9. http.Server with explicit timeouts (read: 10s, write: 30s, idle: 120s)
  10. Signal listener for graceful shutdown on SIGINT/SIGTERM

Routing

Uses Go 1.22's built-in method+path routing pattern on net/http.ServeMux. No third-party router is needed.

mux.HandleFunc("POST /api/v1/budgets", budgetH.Create)
mux.HandleFunc("PUT /api/v1/budgets/{id}", budgetH.Update)

Database Schema

Three logical groups of tables:

Reference data (must exist before budgets/actuals — FK parents):

  • departments — department codes and names
  • gl_accounts — GL codes, descriptions, account type, and favour_high flag

Transactional data:

  • budgets — amount by department + GL account + fiscal period + version
  • actuals — upserted by period + department + GL account (idempotent)

Schema is applied automatically at startup. See internal/database/migrate.go for the full DDL.


Why SQLite

SQLite is sufficient for FP&A data volumes (hundreds of departments, thousands of GL lines, 12 periods per year). Using a file-based embedded database means:

  • Zero infrastructure to provision or maintain
  • The database travels with the binary as a single .db file
  • Backups are a file copy
  • Tests use :memory: with no cleanup required

If the data volume grows to the point where SQLite becomes a bottleneck, the repository layer can be swapped for Postgres without touching the service or handler layers.