Skip to content

Latest commit

 

History

History
123 lines (100 loc) · 8.16 KB

File metadata and controls

123 lines (100 loc) · 8.16 KB

Architecture

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.

Foundational tier

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

Platform tier

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

Adapter tier

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)

Starter tier

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.

Reactive ↔ Go translation

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.

Versioning

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".