app

package
v0.0.0-...-9204231 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 11, 2026 License: AGPL-3.0 Imports: 67 Imported by: 0

Documentation

Overview

Package app wires all Fx modules together into a single application.

Cross-context communication is declared in context_map.go and verified by TestContextMapMatchesRealImports and TestContextMapCoversEventCatalogConsumers in tests/archtest/.

Index

Constants

View Source
const (
	ModuleIdentity = "identity"
	ModuleBilling  = "billing"
	ModuleMultisub = "multisub"
	ModulePayment  = "payment"
	ModuleReseller = "reseller"
	ModulePlugin   = "plugin"
	ModuleNATS     = "nats"
	ModuleInfra    = "infra"
	ModuleHTTP     = "http"
	ModuleTelegram = "telegram"
	ModuleTracing  = "tracing"
)

WiringModule names used in ModuleOrder and WiringConstraints.

View Source
const (
	// ShutdownPhaseHTTP is the maximum time for the HTTP server to drain
	// in-flight requests.
	ShutdownPhaseHTTP = 15 * time.Second

	// ShutdownPhaseConsumers is the maximum time for NATS consumers to
	// drain in-flight message handlers.
	ShutdownPhaseConsumers = 10 * time.Second

	// ShutdownPhaseRelay is the maximum time for the outbox relay workers
	// to finish their current batch after context cancellation.
	ShutdownPhaseRelay = 10 * time.Second
)

Shutdown phase timeouts. Each phase has a dedicated budget; Fx proceeds to the next OnStop hook after the current one returns (or its timeout expires).

Variables

View Source
var ContextMap = []CrossContextDependency{

	{From: "billing", To: "multisub", Mechanism: MechanismEvent,
		Description: "subscription lifecycle events trigger VPN provisioning/deprovisioning"},

	{From: "payment", To: "billing", Mechanism: MechanismEvent,
		Description: "charge/refund completion events trigger invoice state transitions"},

	{From: "multisub", To: "billing", Mechanism: MechanismEvent,
		Description: "traffic exceeded events notify billing for potential action"},

	{From: "identity", To: "plugin", Mechanism: MechanismEvent,
		Description: "user lifecycle events dispatched to notification plugins"},

	{From: "billing", To: "plugin", Mechanism: MechanismEvent,
		Description: "subscription and invoice events dispatched to notification plugins"},

	{From: "multisub", To: "plugin", Mechanism: MechanismEvent,
		Description: "binding lifecycle events dispatched to notification plugins"},

	{From: "billing", To: "payment", Mechanism: MechanismPort,
		Description: "PaymentGateway ACL port for charge creation during checkout"},

	{From: "billing", To: "plugin", Mechanism: MechanismPlugin,
		Description: "PricingModifier + BillingService dispatch pricing and subscription hooks to WASM plugins"},

	{From: "multisub", To: "plugin", Mechanism: MechanismPlugin,
		Description: "VPNProvider + orchestrator dispatch VPN and lifecycle hooks to WASM plugins"},

	{From: "multisub", To: "billing", Mechanism: MechanismACL,
		Description: "PlanProvider + SubscriptionProvider ACL reads plan/subscription data for binding calculation"},

	{From: "multisub", To: "remnawave", Mechanism: MechanismExternal,
		Description: "RemnawaveGateway for VPN user CRUD via Remnawave panel API"},

	{From: "gateway", To: "payment", Mechanism: MechanismGateway,
		Description: "PaymentWebhookHandler verifies and completes payments via PaymentFacade"},

	{From: "settings", To: "billing", Mechanism: MechanismPort,
		Description: "ConfigApplier port for trial days, rate limits"},

	{From: "settings", To: "infra", Mechanism: MechanismPort,
		Description: "ConfigApplier port for speed test, health check interval"},

	{From: "settings", To: "plugin", Mechanism: MechanismPort,
		Description: "ConfigApplier port for max plugins, hot reload"},

	{From: "reseller", To: "billing", Mechanism: MechanismEvent,
		Description: "invoice.paid events trigger commission calculation (planned, not yet implemented)"},
}

ContextMap is the authoritative list of allowed cross-context communication in RemnaCore. Architecture tests (TestContextMapMatchesRealImports) verify that actual adapter-level wiring matches this declaration.

When adding a new cross-context communication path:

  1. Add an entry to this slice.
  2. Run `go test ./tests/archtest/ -v` to verify.
  3. If the test fails, either the entry is wrong or the wiring is wrong.

ModuleOrder documents the Fx module loading sequence. fx.Invoke hooks execute in declaration order, so modules with startup hooks (fx.Invoke) MUST appear in the correct position. Constraints are verified by TestWiringOrderSatisfiesConstraints.

View Source
var ShutdownConstraints = []ShutdownConstraint{
	{
		Before: "http_server",
		After:  "nats_consumers",
		Reason: "HTTP server must stop accepting requests before consumers stop processing events",
	},
	{
		Before: "nats_consumers",
		After:  "outbox_relay",
		Reason: "Consumers must drain before relay stops to avoid event reprocessing",
	},
	{
		Before: "outbox_relay",
		After:  "nats_connection",
		Reason: "Relay must finish publishing before NATS connection closes",
	},
	{
		Before: "nats_connection",
		After:  "pg_pool",
		Reason: "NATS must close before PG pool to avoid relay querying a closed pool",
	},
	{
		Before: "http_server",
		After:  "valkey_client",
		Reason: "HTTP server must stop before Valkey closes to avoid rate limiter errors",
	},
	{
		Before: "http_server",
		After:  "pg_pool",
		Reason: "HTTP server must stop before PG pool closes to avoid handler query errors",
	},
}

ShutdownConstraints enumerates every shutdown ordering invariant.

View Source
var ShutdownPhases = []ShutdownPhase{
	{
		Name:    "http_server",
		Order:   1,
		Timeout: ShutdownPhaseHTTP,
		Reason:  "Stop accepting new requests and drain in-flight HTTP handlers before any backend shuts down",
	},
	{
		Name:    "infra_services",
		Order:   2,
		Timeout: 0,
		Reason:  "Stop health monitor, speed test, and subscription proxy",
	},
	{
		Name:    "nats_consumers",
		Order:   3,
		Timeout: ShutdownPhaseConsumers,
		Reason:  "Stop reading new NATS messages and drain in-flight event handlers",
	},
	{
		Name:    "outbox_relay",
		Order:   4,
		Timeout: ShutdownPhaseRelay,
		Reason:  "Wait for in-flight outbox batches to complete before closing NATS",
	},
	{
		Name:    "partition_manager",
		Order:   5,
		Timeout: 0,
		Reason:  "Cancel background partition maintenance",
	},
	{
		Name:    "nats_connection",
		Order:   6,
		Timeout: 0,
		Reason:  "Close NATS connection after all producers and consumers have stopped",
	},
	{
		Name:    "valkey_client",
		Order:   7,
		Timeout: 0,
		Reason:  "Close Valkey after HTTP server no longer accepts rate-limited requests",
	},
	{
		Name:    "pg_pool",
		Order:   8,
		Timeout: 0,
		Reason:  "Close database pool last — all DB-dependent components must have stopped",
	},
}

ShutdownPhases is the authoritative reference for the graceful shutdown sequence. Tests verify that actual Fx hook ordering matches this.

View Source
var WiringConstraints = []WiringConstraint{
	{
		Before: ModulePlugin,
		After:  ModuleNATS,
		Reason: "loadEnabledPlugins must complete before startBillingEventConsumer and startPluginAsyncConsumer",
	},
	{
		Before: ModuleNATS,
		After:  ModuleHTTP,
		Reason: "event consumers and outbox relay must be running before HTTP server accepts traffic",
	},
	{
		Before: ModuleInfra,
		After:  ModuleHTTP,
		Reason: "health monitor and speed test must start before HTTP server advertises readiness",
	},
	{
		Before: ModulePlugin,
		After:  ModuleHTTP,
		Reason: "plugins must be loaded before HTTP handlers dispatch hooks",
	},
	{
		Before: ModuleIdentity,
		After:  ModuleHTTP,
		Reason: "identity service and JWT issuer must be available before HTTP auth middleware runs",
	},
	{
		Before: ModuleBilling,
		After:  ModuleNATS,
		Reason: "CheckoutService must be available before BillingEventConsumer starts",
	},
	{
		Before: ModuleMultisub,
		After:  ModuleNATS,
		Reason: "MultiSubOrchestrator must be available before BillingEventConsumer routes events to it",
	},
}

WiringConstraints enumerates every ordering invariant between wiring modules. Each constraint corresponds to a real runtime dependency: fx.Invoke hooks execute in declaration order, so placing After before Before would cause a startup failure or silent misbehaviour.

Functions

func AllowedSyncImports

func AllowedSyncImports() map[string][]string

AllowedSyncImports returns the set of allowed synchronous imports between domain packages, derived from ContextMap. Only port and ACL mechanisms represent compile-time dependencies between domain packages.

The returned map keys are source context names; values are slices of target context names that the source is allowed to import.

Note: currently no domain package directly imports another domain package (all cross-context wiring happens at the adapter/app layer). This function exists to catch future violations where someone accidentally adds a direct domain-to-domain import.

func BuildEventCatalog

func BuildEventCatalog() *domainevent.EventCatalog

BuildEventCatalog creates the complete event catalog with all domain events from every bounded context. This function is called at application startup and its result is used for runtime validation and architecture tests.

Every EventType constant in the codebase MUST be registered here. The architecture test TestAllEventTypesInCatalog enforces completeness.

func New

func New() *fx.App

New constructs the Fx application with all modules wired together.

Startup ordering (Fx.Invoke hooks execute in declaration order):

  1. Infrastructure adapters (DB, Valkey, NATS) -- provided by Fx modules
  2. Plugin loading -- must complete before event consumers start
  3. Outbox relay + partition manager
  4. Event consumers -- depend on plugins being loaded
  5. Health monitor + speed test + subscription proxy
  6. HTTP server -- last, only accepts traffic after all deps ready

See WiringConstraints and TestWiringOrderSatisfiesConstraints.

Types

type CrossContextDependency

type CrossContextDependency struct {
	From        string    // source context: "billing", "multisub", etc.
	To          string    // target context or external system
	Mechanism   Mechanism // how they communicate
	Description string    // human-readable explanation
}

CrossContextDependency declares an allowed communication path between two bounded contexts (or between a context and an external system). Architecture tests verify that no undeclared cross-context imports exist.

func EventFlows

func EventFlows() []CrossContextDependency

EventFlows returns the subset of ContextMap entries that use event-driven communication. Useful for documentation generation and architecture visualization tools.

type Mechanism

type Mechanism string

Mechanism describes how two bounded contexts communicate.

const (
	// MechanismEvent is an asynchronous dependency via NATS JetStream (outbox
	// relay). The producing context publishes domain events; the consuming
	// context subscribes through a NATS consumer. No compile-time import
	// exists between the two domain packages.
	MechanismEvent Mechanism = "event"

	// MechanismPort is a synchronous dependency through a domain-owned
	// interface (port). The calling context defines the interface in its own
	// package; the wiring layer (internal/app/) supplies an adapter that
	// translates to the target context's types.
	MechanismPort Mechanism = "port"

	// MechanismACL is a synchronous dependency through an Anti-Corruption
	// Layer adapter. Similar to port, but the adapter reads from the target
	// context's persistence directly (e.g. billing DB tables) rather than
	// calling a domain service.
	MechanismACL Mechanism = "acl"

	// MechanismGateway is a gateway-layer orchestration where the HTTP
	// handler coordinates calls to multiple bounded contexts. No domain-level
	// import exists between the contexts; the handler depends on each
	// context's service independently.
	MechanismGateway Mechanism = "gateway"

	// MechanismExternal is a dependency on an external system (e.g.
	// Remnawave VPN panel) accessed through a domain-owned gateway interface.
	MechanismExternal Mechanism = "external"

	// MechanismPlugin is a dependency dispatched through the WASM plugin
	// system via hookdispatch.Dispatcher. The domain defines the port; the
	// wiring layer supplies a plugin-backed adapter.
	MechanismPlugin Mechanism = "plugin"
)

type ShutdownConstraint

type ShutdownConstraint struct {
	Before string
	After  string
	Reason string
}

ShutdownConstraint documents that one phase must complete before another begins. Verified by TestShutdownConstraintsAreSatisfied.

type ShutdownPhase

type ShutdownPhase struct {
	// Name identifies the phase for logging and testing.
	Name string

	// Order is the 1-based position in the shutdown sequence.
	Order int

	// Timeout is the maximum duration for this phase. Zero means the phase
	// is handled by its own Fx OnStop hook with the default Fx timeout.
	Timeout time.Duration

	// Reason explains why this phase exists at this position.
	Reason string
}

ShutdownPhase documents a single step in the graceful shutdown sequence.

type WiringConstraint

type WiringConstraint struct {
	Before string
	After  string
	Reason string
}

WiringConstraint documents that the module named Before must appear before the module named After in ModuleOrder. Reason explains why.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL