Event Sourcing is transforming how we build modern applications. Companies like Uber, Netflix, and Amazon use it to build scalable, auditable systems. But if you're a Swift developer wanting to leverage this powerful pattern, you've been out of luckโuntil now.
Today, I'm excited to share KurrentDB-Swift, a production-ready Swift client for Kurrent (formerly EventStoreDB) that brings enterprise-grade Event Sourcing capabilities to the Swift ecosystem.
๐ Quick Stats
Before we dive in, here's what KurrentDB-Swift brings to the table:
- โฑ๏ธ Over 1 year of active development
- ๐ช 425+ commits across 46 releases
- ๐ฆ Available on Swift Package Index
- โ Zero data-race safety (Swift 6 ready)
- ๐ Comprehensive documentation with tutorials
- ๐ง Actively maintained (last update: 3 days ago)
๐ก Why This Matters
The Problem
Server-Side Swift is maturing rapidly. We have Vapor, Hummingbird, and excellent tools for building web services. But when it comes to Event Sourcingโa critical pattern for building complex, scalable systemsโSwift developers have been left behind.
Kurrent (the company behind EventStoreDB) provides official clients for .NET, Java, Node.js, Go, and Rust. But Swift? Nothing.
The Solution
KurrentDB-Swift fills this gap. It's not just a wrapperโit's a ground-up implementation designed to feel natural in Swift, leveraging modern concurrency, type safety, and the language features Swift developers expect.
๐ What is Event Sourcing?
If you're new to Event Sourcing, here's a quick primer.
Traditional Approach: Store Current State
// Database stores only the current state
struct BankAccount {
var balance: Decimal = 100.00
}
// We lose the history
account.balance += 50 // balance is now 150
account.balance -= 30 // balance is now 120
// How did we get here? We don't know!
Event Sourcing: Store All Events
// Database stores every event that happened
enum AccountEvent {
case opened(initialBalance: Decimal)
case deposited(amount: Decimal)
case withdrawn(amount: Decimal)
}
// Complete audit trail
let events = [
.opened(initialBalance: 100),
.deposited(amount: 50), // balance: 150
.withdrawn(amount: 30) // balance: 120
]
// We can see EXACTLY what happened, when, and why
Benefits
- โ Complete audit trail - Know exactly what happened
- โ Time travel - See state at any point in history
- โ Event-driven architecture - React to changes in real-time
- โ Easy debugging - Replay events to reproduce issues
- โ Business insights - Analyze patterns and behaviors
๐ Show, Don't Tell: A Real Example
Let's build a simple order system to see KurrentDB-Swift in action.
Define Your Events
import KurrentDB
// Domain events as Swift types
enum OrderEvent: Codable {
case created(customerId: String, items: [Item])
case paid(amount: Decimal, method: String)
case shipped(trackingNumber: String)
case delivered(timestamp: Date)
}
struct Item: Codable {
let productId: String
let quantity: Int
let price: Decimal
}
Write Events
// Connect to Kurrent
let client = KurrentDBClient(settings: .localhost())
// Create events
let orderId = UUID().uuidString
let streamName = "order-\(orderId)"
let events = [
EventData(
eventType: "OrderCreated",
model: OrderEvent.created(
customerId: "customer-123",
items: [Item(productId: "prod-456", quantity: 2, price: 29.99)]
)
),
EventData(
eventType: "OrderPaid",
model: OrderEvent.paid(amount: 59.98, method: "credit-card")
),
EventData(
eventType: "OrderShipped",
model: OrderEvent.shipped(trackingNumber: "TRACK-789")
)
]
// Append to stream
try await client.appendStream(streamName, events: events) {
$0.revision(expected: .any)
}
Read Event History
// Read all events for this order
let history = try await client.readStream(streamName) {
$0.forward().startFrom(revision: .start)
}
for try await response in history {
if let event = try response.event {
print("[\(event.created)] \(event.eventType)")
// Reconstruct state or update read models
}
}
Subscribe to New Events
// React to new orders in real-time
let subscription = try await client.subscribeToStream("$ce-order") {
$0.fromStart()
}
for try await event in subscription {
// Update dashboard, send notifications, etc.
await handleNewOrderEvent(event)
}
๐ Key Features Deep Dive
1. Type-Safe by Design
// โ Stringly-typed (common in other languages)
store.append("order-123", "OrderCreated", "{\"data\": \"...\"}")
// โ
Type-safe Swift
struct OrderCreated: Codable {
let customerId: String
let items: [Item]
}
let event = EventData(
eventType: "OrderCreated",
model: OrderCreated(customerId: "123", items: [...])
)
try await client.appendStream("order-123", events: [event])
The compiler ensures you can't mix up event types or forget required fields.
2. Modern Concurrency
Full support for Swift's async/await:
// Async/await throughout
try await client.appendStream(...)
// AsyncSequence for streaming
for try await event in client.readStream(...) {
await process(event)
}
// Subscriptions work beautifully
let subscription = try await client.subscribeToStream("orders")
for try await event in subscription {
await updateReadModel(event)
}
3. Optimistic Concurrency Built-in
Prevent race conditions with optimistic concurrency control:
// Ensure the stream is at expected revision
try await client.appendStream("cart-123", events: [event]) {
$0.revision(expected: .specific(5))
}
// Throws if someone else modified the stream
// Your application can retry with the latest state
4. Production-Ready Features
- โ Connection management with automatic reconnection
- โ TLS/SSL support for secure connections
- โ Cluster configuration for high availability
- โ Projection management for building read models
- โ Persistent subscriptions for reliable event processing
- โ Swift 6 compatible with zero data-race safety
๐ Documentation That Actually Helps
One thing I'm particularly proud of is the comprehensive documentation. It's hosted on Swift Package Index and includes:
- Getting Started - From zero to first event in 5 minutes
- Appending Events - All the ways to write events
- Reading Events - Querying your event streams
- Projections - Building read models
Full API documentation is also available for every public type and method.
๐๏ธ Why Build This From Scratch?
Why gRPC?
I chose gRPC over HTTP for several reasons:
- Performance - Binary protocol, efficient streaming
- Type Safety - Protocol Buffers give compile-time guarantees
- Bi-directional Streaming - Perfect for subscriptions
- Industry Standard - Used by major companies worldwide
Why Not Wrap Existing Clients?
I could have wrapped the .NET or Node.js clients, but that would feel un-Swift. Instead, I:
- โ Used idiomatic Swift APIs that feel natural
- โ Leveraged Swift's type system for safety
- โ Integrated with Swift Concurrency (async/await)
- โ Made it feel like a native Swift library
The result is a library that Swift developers will find familiar and comfortable.
๐ฏ Real-World Use Cases
Event Sourcing with KurrentDB-Swift is perfect for:
E-commerce Systems
Track every action: orders placed, payments processed, items shipped, returns initiated.
Financial Applications
Complete audit trail for transactions, account changes, and compliance.
Collaborative Tools
Track document edits, user actions, and maintain complete history.
IoT and Monitoring
Store sensor readings, device events, and system state changes.
Microservices
Event-driven architecture with reliable event distribution.
๐ฎ What's Next
The core functionality is solid and production-ready, but there's always more to do:
Planned Improvements
- [ ] More comprehensive examples and tutorials
- [ ] Performance benchmarks vs other language clients
- [ ] Additional articles on CQRS patterns
- [ ] Snapshotting strategies guide
- [ ] Event versioning best practices
Community Input Wanted
I'd love to hear from you:
- What features would be most valuable?
- What pain points have you encountered?
- What examples or documentation would help most?
Join the conversation in GitHub Discussions.
๐ Get Started Today
Ready to try Event Sourcing with Swift?
Installation
// Package.swift
dependencies: [
.package(url: "https://github.com/gradyzhuo/KurrentDB-Swift.git", from: "1.11.2")
]
Five-Minute Quickstart
- Install Kurrent (if needed):
docker run -d -p 2113:2113 \
eventstore/eventstore:latest \
--insecure --enable-atom-pub-over-http
- Write your first event:
import KurrentDB
let client = KurrentDBClient(settings: .localhost())
let event = EventData(eventType: "TestEvent", model: ["hello": "world"])
try await client.appendStream("test-stream", events: [event])
- Read it back:
let events = try await client.readStream("test-stream")
for try await event in events {
print(event)
}
That's it! You're doing Event Sourcing in Swift. ๐
๐ค How You Can Help
KurrentDB-Swift is open source and community-driven. Here's how you can contribute:
- โญ Star the repo - Helps with discoverability
- ๐ง Try it out - The best feedback comes from real usage
- ๐ Report issues - Found a bug? Let me know!
- ๐ก Request features - What would make it better?
- ๐ Improve docs - Spot a typo or unclear explanation?
- ๐จ Contribute code - PRs welcome!
- ๐ฃ Spread the word - Blog posts, tweets, talks
Every contribution helps make Server-Side Swift better for everyone.
๐ญ Why Server-Side Swift Needs This
Server-Side Swift is at an exciting inflection point. We have:
- โ Mature frameworks (Vapor, Hummingbird)
- โ Excellent tooling
- โ Great performance
- โ Code sharing with iOS/macOS
But to compete with Node.js, Go, and Rust ecosystems, we need robust libraries for common architectural patterns. Event Sourcing is one of those critical patterns.
KurrentDB-Swift is a step toward making Server-Side Swift a first-class citizen for building complex, scalable, event-driven systems.
๐ Acknowledgments
This project wouldn't exist without:
- The Kurrent team for building an excellent event store
- The grpc-swift team for solid gRPC implementation
- The Swift Server Work Group for pushing the ecosystem forward
- The Swift community for feedback and encouragement
- Everyone who has contributed or provided input
๐ Resources
- ๐ฆ Swift Package Index
- ๐ Documentation
- ๐ป GitHub Repository
- ๐ฌ Discussions
- ๐ Issues
๐ฏ Final Thoughts
If you're building systems that need:
- Complete audit trails
- Event-driven architecture
- CQRS patterns
- Temporal queries
- Complex business logic with full history
Give KurrentDB-Swift a try. It's production-ready, well-documented, actively maintained, and built specifically for Swift developers.
Let's make Server-Side Swift the go-to choice for event-driven systems. ๐
About the Author
Hi! I'm Grady Zhuo, a Swift developer passionate about Server-Side Swift and Event Sourcing. I built KurrentDB-Swift to fill a gap in the Swift ecosystem and help make event-driven architecture accessible to Swift developers.
Let's connect:
- ๐ป GitHub: @gradyzhuo
- ๐ฆ Twitter/X: @gradyzhuo
- ๐ผ LinkedIn: gradyzhuo
Questions? Thoughts? Feedback?
I'd love to hear from you! Leave a comment below, open a discussion on GitHub, or reach out on social media. Let's build something amazing together! ๐
Top comments (0)