Development Guide
Setting Up a Dev Environment
git clone https://git.samantha42.xyz/samantha/FPandA-Engine
cd FPandA-Engine
go mod tidy
go run ./main.go
No database setup is needed. The SQLite file is created automatically on first run.
Running Tests
go test ./...
Tests use Go's built-in testing package. The test suite was migrated from Python to Go's native test runner — see commit 771eb9d. Use DB_PATH=:memory: in test setup to avoid leaving .db files on disk.
To run a specific package:
go test ./Engine/internal/service/...
To run with verbose output:
go test -v ./...
Project Conventions
Layer responsibilities:
handler/— HTTP concerns only. Decode the request body, call a service method, encode the response. No business logic here.service/— All business logic. Favourability calculation, variance rollup, budget rules. No SQL here.database/— All SQL. Repository structs implement interfaces used by the service layer.model/— Plain Go structs shared across layers. No methods with business logic, only struct validation.
Error handling:
Go makes every error path explicit. Handlers check service errors and return appropriate HTTP status codes. The server never panics on request errors.
Logging:
Use the slog logger passed into components via dependency injection (or the package-level logger if needed). Log at ERROR for unexpected failures, INFO for lifecycle events. Do not log every request unless debugging.
Adding a New Endpoint
- Add the domain type to
model/if needed - Add the repository method to the relevant file in
database/ - Add the service method in
service/ - Add the handler method in
handler/ - Register the route in
main.goon themux
Follow the existing patterns — each handler function decodes JSON, calls one service method, and encodes the response.
Adding a New Budget Version
Budget versions are stored as a string field. Currently supported: original, forecast1, forecast2, forecast3. To add a new version (e.g. forecast4), update the validation in the budget service and add the new value to the Finance Concepts documentation.
Database Migrations
Schema changes are applied via database.Migrate() on startup using CREATE TABLE IF NOT EXISTS and ALTER TABLE IF NOT EXISTS (for additive column changes). This is intentionally simple — there is no migration versioning system.
For destructive changes (dropping columns, renaming tables), apply them manually to existing databases and update the migration function. Document any required manual steps here.
Building a Release Binary
go build -o fpa-engine ./main.go
For a lean, statically linked binary suitable for containers:
CGO_ENABLED=0 GOOS=linux go build -a -ldflags '-extldflags "-static"' -o fpa-engine ./main.go
Note: modernc.org/sqlite is a pure-Go SQLite implementation — CGO is not required, and the binary has no system library dependencies.