DEV Community

Grady Zhuo
Grady Zhuo

Posted on

Introducing KurrentDB-Swift: Bringing Event Sourcing to Server-Side Swift

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!
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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
    }
}
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

๐Ÿ” 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])
Enter fullscreen mode Exit fullscreen mode

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)
}
Enter fullscreen mode Exit fullscreen mode

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
Enter fullscreen mode Exit fullscreen mode

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:

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")
]
Enter fullscreen mode Exit fullscreen mode

Five-Minute Quickstart

  1. Install Kurrent (if needed):
   docker run -d -p 2113:2113 \
     eventstore/eventstore:latest \
     --insecure --enable-atom-pub-over-http
Enter fullscreen mode Exit fullscreen mode
  1. 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])
Enter fullscreen mode Exit fullscreen mode
  1. Read it back:
   let events = try await client.readStream("test-stream")
   for try await event in events {
       print(event)
   }
Enter fullscreen mode Exit fullscreen mode

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:

  1. โญ Star the repo - Helps with discoverability
  2. ๐Ÿ”ง Try it out - The best feedback comes from real usage
  3. ๐Ÿ› Report issues - Found a bug? Let me know!
  4. ๐Ÿ’ก Request features - What would make it better?
  5. ๐Ÿ“– Improve docs - Spot a typo or unclear explanation?
  6. ๐Ÿ”จ Contribute code - PRs welcome!
  7. ๐Ÿ“ฃ 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

๐ŸŽฏ 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:


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)