Table of Contents
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:
- Logger (
log/slogwith JSON output to stdout) - Database connection (
database.Connect) - Auto-migration (
database.Migrate— idempotentCREATE TABLE IF NOT EXISTS) - Reference repositories and handlers (departments, GL accounts — FK parents)
- Budget and actuals repositories
- Services wrapping those repositories
- Handlers wrapping those services
- Route registration on
http.NewServeMux() http.Serverwith explicit timeouts (read: 10s, write: 30s, idle: 120s)- 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 namesgl_accounts— GL codes, descriptions, account type, andfavour_highflag
Transactional data:
budgets— amount by department + GL account + fiscal period + versionactuals— 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
.dbfile - 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.