The Go port of Firefly Framework mirrors the layering enforced by the Java reactor and the .NET solution: four tiers, left-to-right dependency direction, each tier strictly above the one to its right.
Spring / PyFly parity. The Go port matches the conceptual surface of the Spring Boot stack (and its Python sibling PyFly) where it makes sense in idiomatic Go: typed configuration binding (
config), application orchestration (lifecycle), management endpoints (actuator), task scheduling (scheduling), resilience patterns (resilience), HTTP-layer authn/authz (security), SQL migrations (migrations), OpenAPI generation (openapi), internationalisation (i18n), Server-Sent Events (sse), transactional helpers (transactional), and a shared testing toolkit (testkit). Go's lack of a JVM container means we express dependency injection as constructor injection and middleware composition rather than a runtime bean factory — but the public contract on the wire is identical to Java and .NET.
FOUNDATIONAL → PLATFORM → ADAPTERS → STARTERS
A module never imports a module from a tier to its right. The Go
module graph (go.work + per-module go.mod) enforces this — there
is no replace directive that bypasses the layering.
Primitives every service uses, no transitive infrastructure dependencies.
| Module | Purpose |
|---|---|
kernel |
RFC 7807 ProblemDetail, generic Result[T], Clock, FireflyError hierarchy |
utils |
Try / TryOf, Retry.Do with exponential backoff + jitter, slug, AES-256-GCM crypto, template rendering |
validators |
IBAN (mod-97), BIC, Luhn, credit card, E.164 phone, currency (ISO 4217), email, password strength, sort code, BSB, SSN, VAT, Spanish DNI/NIE/NIF |
web |
Problem-Details renderer, correlation-id middleware, idempotency middleware (pluggable store), PII masking |
config |
Layered Static / YAML / Env / Flag sources, profile selection, struct-tag binder |
i18n |
Locale-aware message bundles, Accept-Language picker, region→language fallback |
The infrastructure layer.
| Module | Purpose |
|---|---|
cache |
Adapter port + Memory / NoOp / Fallback implementations + typed Typed[T] |
observability |
slog + correlation enrichment, OTel tracer / meter handles, health composite, startup banner |
data |
Generic Filter DSL, Page[T] envelope, Repository[T,K] + memory impl |
cqrs |
Generic command/query bus, type-dispatched handlers, validation + caching middleware |
eda |
Event envelope, Publisher/Subscriber, in-memory broker, Kafka/RabbitMQ scaffolds |
eventsourcing |
AggregateRoot + EventStore (in-memory), snapshots, projection runner |
orchestration |
Saga (sequential + reverse-order compensation), Workflow (DAG), TCC |
ruleengine |
YAML DSL → AST → recursive evaluator (models, interfaces, core, web, sdk sub-packages) |
plugins |
Lifecycle SPI + composite registry |
lifecycle |
Application.Run(ctx) orchestrator with ordered hooks + signal-based drain |
actuator |
/actuator/{health,info,metrics,env,goroutines,version} endpoints; lock-free Counter / Gauge |
scheduling |
Cron parser + Scheduler runner with FixedRate, FixedDelay, Cron triggers |
resilience |
CircuitBreaker, RateLimiter, Bulkhead, Timeout, composable [Chain] |
security |
Authentication ctx, [BearerMiddleware], path-pattern [FilterChain] RBAC |
migrations |
Flyway-style versioned SQL migration runner (Postgres + SQLite verified) |
openapi |
OAS 3.1 spec generator from Go types + handler descriptors, Swagger-UI shim |
sse |
Server-Sent Events writer with heartbeat + Last-Event-Id resumption |
transactional |
Context-bound WithTx over database/sql.DB, nested-tx participation |
testkit |
HMAC signers (Stripe / GitHub / HMAC / Twilio), SpyBroker, JSON test helpers |
Pluggable integrations. Each port lives in a parent module; concrete provider adapters live in dedicated modules so consumers only pull in the cloud SDKs they actually use.
| Parent / port | Default impl in module | Provider stubs |
|---|---|---|
client |
REST builder (stdlib, retry, problem decode) | SOAP, gRPC, WebSocket placeholders |
configserver |
Spring-Cloud-Config-compatible handler + memory store | — |
idp |
idpinternaldb (bcrypt + HS256 JWT) |
idpkeycloak, idpazuread, idpawscognito |
ecm |
local-fs ContentStore + in-memory document service |
ecmstorageaws, ecmstorageazure, ecmesignaturedocusign, ecmesignatureadobesign, ecmesignaturelogalty |
notifications |
MemoryChannel + Dispatcher |
notificationssendgrid, notificationsresend, notificationstwilio, notificationsfirebase |
callbacks |
Full impl (HMAC-signing dispatcher + audit + REST admin + SDK) | — |
webhooks |
Full impl (HMAC / Stripe / GitHub / Twilio validators + pipeline + DLQ + ingest endpoint + SDK) | — |
One-call composition.
| Starter | Bundles |
|---|---|
startercore |
web + cache + observability + eda + cqrs |
starterapplication |
startercore + plugins.Registry |
starterdomain |
startercore + in-memory eventsourcing.EventStore |
starterdata |
startercore (consumer supplies its own DB pool) |
backoffice |
starterapplication + back-office context middleware |
Each starter ships an embedded banner printed at startup (via
observability.PrintBanner) naming the active starter, the application
name and the resolved Go runtime — mirroring the Spring Boot
banner-on-start behaviour and the .NET port's banner.txt.
The Java framework is built on Project Reactor (Mono, Flux); the
.NET port uses Task/IAsyncEnumerable. Idiomatic Go does not have
reactive streams — the translation rules are:
| Java (Reactor) | Go idiom |
|---|---|
Mono<T> |
(T, error) with context.Context for cancellation |
Flux<T> |
chan T (or Go 1.23 iter.Seq[T]) |
Mono.error(...) |
return zero, err |
Mono.deferContextual(...) |
Read from context.Context |
| Subscribers | Goroutines reading from channels |
Backpressure (Flux.onBackpressureBuffer) |
Bounded channels + select |
Every method that takes time accepts a leading ctx context.Context
and respects its cancellation — the canonical Go contract.
Calendar-versioned (YY.MM.PATCH) — kept in lock-step with the Java
and .NET releases. The current version is exposed as
kernel.Version = "26.04.01".