Backplane is a small server-side Swift framework that ties together
the SSWG ecosystem (swift-service-lifecycle, swift-argument-parser,
swift-service-context, swift-log, swift-metrics,
swift-distributed-tracing, swift-configuration) into a single
ergonomic harness for building CLI-driven services. It abstracts away
the boilerplate of the bootstrap dance — installing the global
logging/metrics/tracing systems in the right order, after CLI parsing,
before any service is built — without taking power away from the
underlying libraries.
Backplane is structured for both long-running services (HTTP servers, queue workers) and one-shot tasks (migrations, backfills) sharing the same service graph and configuration story.
- Self-describing services. A type conforming to
BackplaneServicecarries its own construction (static make(context:)), dependencies, subgroup, and replacement strategy — naming its key in a command'srequiredServicesis the entire registration.services()exists only for overrides: protocol-key bindings, third-party types via closures, test doubles. Backplane boots only the transitive closure of a command's required services, with per-subgroup failure policies, blue-green or cold replacement, hot reload, and recovery of failed entries. - Keypath-driven references.
\.databasestyle references everywhere — autocomplete-friendly and the resolved type is inferred from the keypath. A key's resolved type can also be a protocol, backed by a concrete registration (passive:/factory:as:onEntryDescriptor). - Per-entry configuration scopes. A factory's
context.configis pre-scoped to its entry id — the entry"postgres"readspostgres.hostashost, and the same service type registered under two keys reads two scopes (two databases, one type). - Two command shapes:
PersistentCommand(services run until signalled) andTaskCommand(services come up, the command does its work, the group shuts down gracefully). - Bootstrap pipeline.
BackplaneCommand.bootstrap(...)returns aBootstrapPlanthat the framework applies throughBootstrapCoordinator— globals install in tracing → metrics → logging order, after CLI flags are parsed, before the firstLoggeris built. - Vendor-neutral structured logging via
StructuredLogHandlerdriven by an extensibleStructuredLogProfile. - CLI option groups for common observability flags
(
LoggingOptions,TracingOptions,MetricsOptions). - Trait-gated optional integrations: every cloud-vendor or ecosystem dependency is opt-in via a Swift Package trait, so apps pay nothing for what they don't use.
- Swift 6.2+
- macOS 15+
Add Backplane to Package.swift:
.package(url: "https://github.com/<org>/swift-backplane.git", from: "2.0.0"),By default, only the core Backplane library is resolved. Optional
integrations are enabled per-consumer via package traits — add only
the ones you need:
.package(
url: "https://github.com/<org>/swift-backplane.git",
from: "2.0.0",
traits: ["Postgres", "OTel", "GCP"]
),| Trait | Library product | Adds |
|---|---|---|
| (none) | Backplane |
Core framework, always available. |
| (none) | BackplaneVault |
ConfigStore (writable, secret-aware config; safe for concurrent stores on one file via locked read-merge-writes) + ConfigEncryption protocol + PassthroughEncryption + FileLock (cross-process advisory lock). Imported on its own, no extra deps. |
Postgres |
BackplanePostgres |
PostgresNIO-backed service key, configuration, migrations. |
OTel |
BackplaneOTel |
swift-otel integration: BootstrapPlan factory + CLI flags. |
GCP |
BackplaneGCP |
Cloud Trace tracer/exporter + Cloud Logging LogHandler. |
KeyfileEncryption |
(extends BackplaneVault) |
LocalKeyfileEncryption — AES-256-GCM reference impl backed by a machine-local keyfile. Pulls swift-crypto. |
Macros |
(extends Backplane) |
The #service declaration macro — shorthand for declaring a key on Services. Pulls swift-syntax at build time; off by default. |
Then depend on each library product in the targets that need it:
.executableTarget(
name: "MyService",
dependencies: [
.product(name: "Backplane", package: "swift-backplane"),
.product(name: "BackplanePostgres", package: "swift-backplane"),
.product(name: "BackplaneOTel", package: "swift-backplane"),
]
),No services() registry — \.postgres is required by the commands,
and BackplanePostgresService conforms to BackplaneService, so its
entry (and its config scope) materialises from the key alone:
import Backplane
import BackplanePostgres
@main
struct MyApp: BackplaneApplication {
typealias RootCommand = AppCommand
static let identifier = "my-app"
}
struct AppCommand: AsyncParsableCommand {
static var configuration = CommandConfiguration(
subcommands: [Serve.self, Migrate.self],
defaultSubcommand: Serve.self
)
}
struct Serve: PersistentCommand {
typealias App = MyApp
static let configuration = CommandConfiguration(abstract: "Run the server")
@OptionGroup var logging: LoggingOptions
@OptionGroup var tracing: TracingOptions
var requiredServices: ServiceList { [\.postgres] }
func bootstrap(config: ConfigReader, environment: Environment) async throws -> BootstrapPlan {
var plan = BootstrapPlan()
plan.logLevel = logging.resolvedLogLevel
plan.logHandlerFactory = logging.format.factory(default: BackplaneLogging.cloudRunOrStream.asFactory)
return plan
}
}
struct Migrate: TaskCommand {
typealias App = MyApp
static let configuration = CommandConfiguration(abstract: "Run database migrations")
var requiredServices: ServiceList { [\.postgres] }
func execute(with context: BackplaneContext) async throws {
let pg = try await context.requireService(\.postgres)
try await PostgresMigrator.migrate(
myMigrations,
on: pg.client,
logger: context.logger
)
}
}A service that conforms to BackplaneService is complete at the point
it's written — construction, dependencies, subgroup, and replacement
strategy all live on the type:
final class Notifier: BackplaneService {
let webhookURL: String
init(webhookURL: String) { self.webhookURL = webhookURL }
static func make(context: BackplaneContext) async throws -> Notifier {
// context.config is pre-scoped to the entry id ("notifier").
Notifier(webhookURL: try context.requireConfig().requiredString(forKey: "webhookURL"))
}
static var dependencies: ServiceList { [\.postgres] }
static var subgroup: SubgroupTag { .integrations }
func start() async throws { /* … */ }
func shutdown() async { /* … */ }
}
extension Services {
public var notifier: ServiceKey<Notifier> { "notifier" }
}Declare [\.notifier] in a command's requiredServices and Backplane
materialises the entry, its postgres dependency, and both config
scopes — nothing else to write.
For everything a conformance can't express, services() on the app is
the override surface: bind a protocol key to a concrete
implementation (passive: / factory:as:), build a third-party type
with a closure, swap in a test double, or override a conforming type's
defaults (EntryDescriptor(\.postgres, subgroup: .integrations) —
explicit descriptors always win).
A service key is a computed var on the Services namespace, returning
a typed ServiceKey<T>. Consumers extend Services once per key:
extension Services {
public var database: ServiceKey<PostgresClient> {
ServiceKey(id: "database")
}
}That's the entire boilerplate — name the property for the value, not
…Key. ServiceKey is ExpressibleByStringLiteral, so the body can
also be just "database". With the Macros trait enabled,
#service(PostgresClient.self, name: "database") expands to the same
declaration. The keypath \.database is then usable wherever
Backplane accepts a service key — requireService, dependency lists
on EntryDescriptor, requiredServices on a command. The return
type of requireService(\.database) is inferred from the keypath, so
no cast is needed at the call site. The key's id doubles as the
entry's configuration scope.
Satellite targets ship their keys the same way: BackplanePostgres
exposes Services.postgres, which is why the snippet above writes
\.postgres without declaring it itself.
Full DocC documentation lives alongside each module:
Backplane— getting started, key concepts, local + cloud deployment guides.BackplanePostgres— Postgres walkthrough.BackplaneOTel— OTel walkthrough.BackplaneGCP— Cloud Trace + Cloud Logging walkthrough.BackplaneVault— writable, encryption-aware configuration: core model, reading & writing, encryption backends, file locking.
Build the docs locally with the Swift DocC Plugin:
swift package --traits Postgres,OTel,GCP \
generate-documentation --target Backplaneswift test # core only
swift test --traits Postgres,OTel,GCP,KeyfileEncryption,Macros # everythingThe test suite covers dependency resolution, lifecycle modes, bootstrap ordering, structured logging, CLI option parsing, and the optional integrations.
See LICENSE.