# Working with Change Streams from Your Swift Application

My day job is to work with our biggest customers to help them get the best out of MongoDB when creating new applications or migrating existing ones. Their use cases often need side effects to be run whenever data changes — one of the most common requirements is to maintain an audit trail.

When customers are using MongoDB Atlas, it's a no-brainer to recommend Atlas Triggers. With triggers, you provide the code, and Atlas makes sure that it runs whenever the data you care about changes. There's no need to stand up an app server, and triggers are very efficient as they run alongside your data.

Unfortunately, there are still some workloads that customers aren't ready to move to the public cloud. For these applications, I recommend change streams. Change streams are the underlying mechanism used by Triggers and many other MongoDB technologies — Kafka Connector, Charts, Spark Connector, Atlas Search, anything that needs to react to data changes.

Using change streams is surprisingly easy. Ask the MongoDB Driver to open a change stream and it returns a database cursor. Listen to that cursor, and your application receives an event for every change in your collection.

This post shows you how to use change streams from a Swift application. The principles are exactly the same for other languages. You can find a lot more on change streams at Developer Center.

## Running the example code

I recently started using the MongoDB Swift Driver for the first time. I decided to build a super-simple Mac desktop app that lets you browse your collections (which MongoDB Compass does a **much** better job of) and displays change stream events in real time (which Compass doesn't currently do).

You can download the code from the Swift-Change-Streams repo. Just build and run from Xcode.

Provide your connection-string and then browse your collections. Select the "Enable change streams" option to display change events in real time.

### The code

You can find this code in CollectionView.swift.

We need a variable to store the change stream (a database cursor)

```swift
@State private var changeStream: ChangeStream>?
```

as well as one to store the latest change event received from the change stream (this will be used to update the UI):

```swift
@State private var latestChangeEvent: ChangeStreamEvent?
```

The `registerChangeStream` function is called whenever the user checks or unchecks the change stream option:

```swift
func registerChangeStream() async {
    // If the view already has an active change stream, close it down
    if let changeStream = changeStream {
        _ = changeStream.kill()
        self.changeStream = nil
    }
    if enabledChangeStreams {
        do {
            let changeStreamOptions = ChangeStreamOptions(fullDocument: .updateLookup)
            changeStream = try await collection?.watch(options: changeStreamOptions)
            _ = changeStream?.forEach({ changeEvent in
                withAnimation {
                    latestChangeEvent = changeEvent
                    showingChangeEvent = true
                    Task {
                        await loadDocs()
                    }
                }
            })
        } catch {
            errorMessage = "Failed to register change stream: \(error.localizedDescription)"
        }
    }
}
```

The function specifies what data it wants to see by creating a `ChangeStreamOptions` structure — you can see the available options in the Swift driver docs. In this app, I specify that I want to receive the complete new document (in addition to the deltas) when a document is updated. Note that the full document is always included for insert and replace operations.

The code then calls `watch` on the current collection. Note that you can also provide an aggregation pipeline as a parameter named `pipeline` when calling `watch`. That pipeline can filter and reshape the events your application receives.

Once the asynchronous watch function completes, the `forEach` loop processes each change event as it's received.

When the loop updates our `latestChangeEvent` variable, the change is automatically propagated to the `ChangeEventView`:

```swift
 ChangeEventView(event: latestChangeEvent)
```

You can see all of the code to display the change event in `ChangeEventView.swift`. I'll show some highlights here.

The view receives the change event from the enclosing view (`CollectionView`):

```swift
let event: ChangeStreamEvent
```

The code looks at the `operationType` value in the event to determine what color to use for the window:

```swift
var color: Color {
    switch event.operationType {  
    case .insert:
        return .green
    case .update:
        return .yellow
    case .replace:
        return .orange
    case .delete:
        return .red
    default:
        return .teal
    }
}
```

`documentKey` contains the `_id` value for the document that was changed in the MongoDB collection:

```swift
if let documentKey = event.documentKey {
   ...
        Text(documentKey.toPrettyJSONString())
   ...
    }
}
```

If the database operation was an update, then the delta is stored in `updateDescription`:

```swift
if let updateDescription = event.updateDescription {
    ...
        Text(updateDescription.updatedFields.toPrettyJSONString())
    ...
    }
}
```

The complete document after the change was applied in MongoDB is stored in `fullDocument`:

```swift
if let fullDocument = event.fullDocument {
    ...
        Text(fullDocument.toPrettyJSONString())
    ...
    }
}
```

If the processing of the change events is a critical process, then you need to handle events such as your process crashing. 

The `_id.resumeToken` in the `ChangeStreamEvent` is a token that can be used when starting the process to continue from where you left off. Simply provide this token to the `resumeAfter` or `startAfter` options when opening the change stream. Note that this assumes that the events you've missed haven't rotated out of the Oplog.

### Conclusion

Use Change Streams (or Atlas triggers, if you're able) to simplify your code base by decoupling the handling of side-effects from each place in your code that changes data.

After reading this post, you've hopefully realized just how simple it is to create applications that react to data changes using MongoDB Change Streams. Questions? Comments? Head over to our Developer Community to continue the conversation!