over the past few days i’ve been trying (with little success) to integrate MongoDB into my server application. i figured that, rather than give up, i would write up my experiences here in the hopes the situation might improve with feedback.
mongo drivers
there are two viable driver libraries in the community today:
mongo-swift-driver
is the “official” driver, but from what i’ve observed, mongokitten
seems to be somewhat more popular. in my opinion, both libraries suffer from serious deficiencies that preclude their use in production, which i will detail in the rest of this write-up.
at a systemic level, having two libraries that do essentially the same thing has also been harmful to my productivity, because i will encounter an issue with one library, spend a lot of time rewriting the code to use the other library, make some progress, and then run into an issue with the other library, continually switching back and forth between the two.
documentation
arguably, documentation is even more important than implementation, because it is simply not possible to use a library if you don’t know how to use it.
mongo-swift-driver
has Jazzy API reference, and to its credit, most of its API has at least a sentence or two describing what it does. it is hard to discover though, because you have to manually click each function signature to view its description, and for some reason, the async
APIs do not appear alongside their promise/future-based counterparts; instead they appear haphazardly some ways down the page.
for a while, the second issue led me to believe that MSD’s docs had not been updated for async
/await
.
they are also not sorted alphabetically, which makes it hard to browse the generic MongoSH reference for an API and find its swift language binding.
while i give the docs credit for existing, many of them seem to be missing some basic information that a new developer might be wondering about. for example, none of the paragraphs for createCollection(_:options:session:)
, including the list of thrown errors, explains what happens if the collection already exists, which is a pretty basic programmer question
MSD has no tutorials linked from its GitHub homepage. it has examples, but they are all using other large web frameworks (Vapor, Kitura, etc.) and there doesn’t seem to be many minimal examples demonstrating basic tasks like:
- managing a database life cycle
- managing a collection life cycle
- updating/upserting a document
- storing and retrieving binary data
mongokitten
had no API reference before i started hosting it myself on swiftinit. but a majority of its API lacks doccomments (see the page for MongoCollection
), so it only gets you so far. on the other hand, it at least has CRUD examples.
the sparse docs (for both libraries) really contrasts with the general MongoDB manual which is really helpful and really well-written. unfortunately, i had a really hard time transferring what i learned from the MongoDB manual to our swift world because…
API inconsistency
mongokitten
seems to have designed its own, “swifty” API which looks different from MongoDB’s general API.
MongoDB has a quite simple and elegant interface that really only requires understanding one concept: the document. i was able to learn a lot about how to interact with MongoDB over mongosh
just from reading the manuals.
which is why i wish mongokitten
just exposed some basic bindings for transmitting documents to and from MongoDB instead of re-engineering its own strongly-typed swift API.
to me, db.collection.updateOne(filter, update, options)
is very easy to understand. but we do not have db.collection.updateOne(filter:update:options:)
in swift. instead we have:
func updateEncoded<E>(where: Document, to: E) async throws -> UpdateReply
func updateEncoded<Query, E>(where: Query, to: E) async throws -> UpdateReply
func updateOne<Query>(where: Query, to: Document) async throws -> UpdateReply
func updateOne(where: Document, to: Document) async throws -> UpdateReply
func upsert<Query>(Document, where: Query) async throws -> UpdateReply
func upsert(Document, where: Document) async throws -> UpdateReply
func upsertEncoded<E>(E, where: Document) async throws -> UpdateReply
func upsertEncoded<Query, E>(E, where: Query) async throws -> UpdateReply
interestingly, we have upsert
(upsertOne
?), but we do not have upsertMany
. i have no idea how to pass options with MongoKitten, and the inconsistent ordering of the arguments is confusing to me.
MSD to its credit hews closely to MongoDB’s general API, and i had a much easier time mapping what i learned from the MongoDB manual to the MSD API.
name collisions
MongoKitten is really prone to toplevel name collisions. i don’t know why it vends a top-level type called “Binary
” or “Sample
”. MSD is better in this regard.
Foundation dependencies
this one is one of the biggest blockers for me, because i work very hard to keep Foundation from getting linked into my binaries, because Foundation really is a big problem in memory-constrained environments, like a server.
both MSD and MongoKitten are intertwined with Foundation in ways that do not seem well-justified to me.
for example, MongoKitten depends on the bson
package, which exposes “convenience APIs” that use Foundation types. these APIs don’t contribute much, but cost a lot because they add a Foundation dependency.
i really wish MongoKitten adopted an organization more like what SwiftNIO does, where they factored out all the Foundation-related APIs into a separate NIOFoundationCompat
overlay.
MSD is even worse because not only does it link Foundation, but it actually requires you to import Data
in order to use it. BSONBinary
has ByteBuffer
and [UInt8]
-taking initializers, but they are not public, and BSONBinary
also uses ByteBuffer
as its internal representation!
C dependencies
MongoKitten is a pure-swift library, so this is not relevant to it. but MSD wraps libmongoc
so you have to do C-isms like call cleanupMongoSwift()
in a defer
block. installing MSD also involves some hoops:
The driver vendors and wraps the MongoDB C driver (
libmongoc
), which depends on a number of external C libraries when built in Linux environments. As a result, these libraries must be installed on your system in order to build MongoSwift.
it’s also not clear to me (as a new user) which parts of its API are safe vs. unsafe. for example, the docs for MongoCursor
make it sound like a dangerous type which requires a lot of management, but i don’t really see many “safety procedures” being performed in examples that use it.
conclusion
i hope this feedback does not come across as too harsh. i really am grateful for everyone who has worked on these two libraries. but as it stands, i cannot really use them, for all of the reasons detailed above, but the Foundation dependencies especially. i hope this changes in future releases.