Introduction
Hi all! My name is Kaitlin Mahar, and I’m a software engineer at MongoDB on the drivers team. We build the official client libraries for using MongoDB in just about every popular programming language.
I work on our driver for Swift, MongoSwift. My team (@mbroadst, @patrick) and I would like to pitch the driver to SSWG and the community for consideration.
We’re currently working on adding an asynchronous SwiftNIO-based version of our API to meet the SSWG incubation process minimal requirements. This is still in the design phase, and we’d love to hear feedback from the community on what we’ve come up with so far.
Current State and Future Plans
We initially developed MongoSwift about 18 months ago with a synchronous API for use with the mobile/embedded version of MongoDB. However, we’ve increasingly shifted our focus toward providing a great server-side experience. We did work over the summer to implement automatic connection pooling in the driver and are now tackling the crucial asynchronous API.
The driver complies with nearly all of the MongoDB driver specifications, which are documents our team writes detailing everything from what a driver’s CRUD API should look like to how it should select which server to send a command to. These specs ensure consistent experiences using MongoDB across languages, while still allowing room for implementations and public APIs to vary in language-appropriate ways.
MongoDB stores data in BSON (binary JSON) documents, and the driver includes a Document
type which supports inserting and retrieving a number of built-in Swift types (e.g. Int
, String
, Bool
) as well as various BSON-specific types we’ve implemented such as ObjectId
. We also have a BSONEncoder
and BSONDecoder
that support converting between Codable
types and Document
s.
The latest release we’ve tagged is 0.1.3. We are aiming to do a 1.0 release once the async API is ready.
libmongoc
The driver currently wraps the MongoDB C driver, a.k.a. libmongoc. So far, this choice has allowed us to focus a lot on the API and ergonomics of the driver and less on internals. After our API is stabilized we plan to start on rewriting the internals in pure Swift using SwiftNIO.
In the meantime, we’ll be able to provide an asynchronous API that uses SwiftNIO primitives by wrapping all blocking libmongoc calls in an NIOThreadPool
. We plan to continue maintaining the synchronous API to provide a great experience when using the driver in scripts or the Swift REPL.
At the moment, users are required to install libmongoc separately on their system, but we have work in progress to vendor libmongoc and build it with SwiftPM instead to provide a better user experience.
Testing/Supported Usage
MongoSwift supports Swift 5.0+ and MongoDB 3.6+, and is tested in CI on MongoDB's in-house CI system Evergreen on MacOS and Linux against a variety of MongoDB deployments (standalone, replica set, sharded cluster) both with and without SSL enabled.
Sample Usage of Async API
// Option 1: initialize a client that will lazily connect upon first I/O
let client = try MongoClient("mongodb://127.0.0.1/", using: myEventLoop)
// Option 2: initialize a client and attempt to ping the server with it.
// this provides consistency with other DB drivers e.g. PostgresNIO
let futureClient = try MongoClient.connect("mongodb://127.0.0.1/", on: myEventLoop)
// Create a collection and use it with a `Codable` type of your choice
struct Cat {
let _id: ObjectId
let name: String
let color: String
}
// these methods perform no I/O, they just set up local objects containing
// the state necessary to construct commands
let db = client.db("home")
let collection = db.collection("cats", withType: Cat.self)
// insert a `Cat` with a randomly generated _id and print the
// resulting `InsertOneResult`
let chester = Cat(_id: ObjectId(), name: "Chester", color: "tan")
try collection.insertOne(chester).whenSuccess { result in
print("result: \(result)") // InsertOneResult(insertedId: .objectId("..."))
}
// Get a count of documents in a collection
try collection.countDocuments().whenSuccess { count in
print("count: \(count)") // 1
}
// Retrieve all documents from a collection
let result = try collection.find().flatMap { cursor in
cursor.all()
}
result.whenSuccess { documents in
print(documents) // [Cat(_id: "...", name: "Chester", color: "tan")]
}
// Perform an operation for each document in a collection, querying
// the server as-needed rather than holding all the documents in memory
let result = try collection.find().flatMap { cursor in
cursor.forEach { cat in
// do something with cat
}
}
result.whenSuccess {
print("finished processing all documents")
}
result.whenFailure { error in
print("error processing documents: \(error)")
}
Thanks for reading, we very much look forward to hearing any thoughts or feedback you have on the driver so far and our future plans!