diff --git a/Guides/BSON-Guide.md b/Guides/BSON-Guide.md index 28850edfb..be534919b 100644 --- a/Guides/BSON-Guide.md +++ b/Guides/BSON-Guide.md @@ -102,163 +102,3 @@ Previously, `Document`/`BSONDocument` had computed properties, `extendedJSON` an #### Errors Previously, the BSON library used the same types of errors as the driver. As of 1.0.0, the BSON library has its own set of errors. Please see the [error handling guide](https://github.com/mongodb/mongo-swift-driver/blob/main/Guides/Error-Handling.md) for more details. - -### Migrating from the 0.0.1-0.1.3 API to the 0.2.0 BSON API -In version 0.2.0 of `MongoSwift`, the public API for using BSON values was changed dramatically. This section will describe the process for migrating from the old API (BSON API v1) to this new one (BSON API v2). -#### Overview of BSON API v1 - The previous API was based around the `BSONValue` protocol. Types that conformed to this protocol could be inserted to or read out of `Document` and could aslo be used in `Document` literals. The protocol was also used in various places around the driver as an existential type or conformance requirement. A related protocol, `BSONNumber`, inherited from `BSONValue` and provided some numeric conversion helpers for the various BSON number types (e.g. `Double`, `Int32`, `Int`). -```swift -var doc: Document = [ - "a": 5 - "b": ObjectId() -] -let value: BSONValue? = doc["a"] // 5 -let intValue = (value as? BSONNumber)?.int32Value // Int32(5) -doc["c"] = "i am a string" -``` -This API provided a number of benefits, the principal one being the seamless integration of standard Swift types (e.g. `Int`) and driver custom ones (e.g `ObjectId`) into `Document`'s methods. It also had a few drawbacks, however. In order for `BSONValue` to be used as an existential type, it could not have `Self` or associated type requirements. This ended being a big restriction as it meant `BSONValue` could not be `Equatable`, `Hashable`, or `Codable`. Instead, all of this functionaltiy was put onto the separate wrapper type `AnyBSONValue`, which was used instead of an existential `BSONValue` in many places in order to leverage these common protocol conformances. - -Another drawback is that subdocument literals could not be inferred and had to be explicitly casted: -```swift -let x: Document = [ - "x": [ - "y": [ - z: 4 - ] as Document - ] as Document -] -``` -#### Required Updates -In BSON API v2, `BSONNumber`, `BSONValue`, and `AnyBSONValue` no longer exist. They are all entirely replaced by the `BSON` enum. - -##### Updating `BSONValue` references - -Anywhere in the driver that formerly accepted or returned a `BSONValue` will now accept or return a `BSON`. Wherever `BSONValue` is used as an existential value in your application, a `BSON` will probably work as a drop-in replacement. Any casts will need to be updated to call the appropriate helper property instead. -```swift -func foo(x: BSONValue) -> String? { - guard let stringValue = x as? String else { - return nil - } - return "foo" + stringValue -} -``` -becomes: -```swift -func foo(x: BSON) -> String? { - guard let stringValue = x.stringValue else { - return nil - } - // or - guard case let .string(stringValue) = x else { - return nil - } - return "foo" + stringValue -} -``` -Similarly, `BSON`'s `Equatable` conformance can be leveraged instead of the old `bsonEquals`. -```swift -func foo(x: BSONValue, y: BSONValue) { - if x.bsonEquals(y) { ... } -} -``` -becomes simply: -```swift -func foo(x: BSON, y: BSON) { - if x == y { ... } -} -``` -**Generic Requirement** -Currently, there is no equivalent protocol in BSON API v2 to `BSONValue`, so if your application was using it as a generic requirement there is no alternative in the driver. You may have to implement your own similar protocol to achieve the same effect. If such a protocol would be useful to you, please [file a ticket on the driver's Jira project](https://github.com/mongodb/mongo-swift-driver#bugs--feature-requests). - -##### Updating `BSONNumber` references -`BSON` should be a drop-in replacement for anywhere `BSONNumber` is used, except for as a generic requirement. One thing to note that `BSONNumber`'s properties (e.g. `.int32Value`) are _conversions_, whereas `BSON`'s are simple unwraps. The conversions on `BSON` are implemented as methods (e.g. `asInt32()`). - -```swift -// old -func foo(doc: Document) -> Int? { - // conversion - guard let int = (doc["a"] as? BSONNumber)?.intValue else { - return nil - } - // cast - guard let otherInt = doc["b"] as? Int32 else { - return nil - } - return int*Int(otherInt) -} - -// new -func foo(doc: Document) -> Int? { - // conversion - guard let int = doc["a"]?.asInt() else { - return nil - } - // cast - guard let otherInt = doc["b"]?.int32Value else { - return nil - } - // or can use case let - guard case let .int32(otherInt) = doc["b"] else { - return nil - } - return int * Int(otherInt) -} -``` -##### Updating `AnyBSONValue` references -`BSON` should be able to serve as a complete replacement for `AnyBSONValue`. -`Codable` usage: -```swift -// old -struct X: Codable { - let x: AnyBSONValue -} -// new -struct X: Codable { - let x: BSON -} -``` -`Equatable` usage: -```swift -// old -let a: [BSONValue] = ["1", 2, false] -let b: [BSONValue] = ["not", "equal"] -return a.map { AnyBSONValue($0) } == b.map { AnyBSONValue($0) } -// new -let a: [BSON] = ["1", 2, false] -let b: [BSON] = ["not", "equal"] -return a == b -``` -`Hashable` usage: -```swift -// old -let a: [AnyBSONValue: Int] = [AnyBSONValue("hello"): 4, AnyBSONValue(ObjectId()): 26] -print(a[AnyBSONValue(true)] ?? "nil") -// new -let a: [BSON, Int] = ["hello": 4, .objectId(ObjectId()): 26] -print(a[true] ?? "nil") -``` - -##### Updating `Document` literals -`BSON` can be expressed by a dictionary literal, string literal, integer literal, float literal, boolean literal, and array literal, so document literals consisting of those literals can largely be left alone. All the other types that formerly conformed to `BSONValue` will need to have their cases explicitly constructed. The cast to `Document` will no longer be required for subdocuments and will need to be removed. All runtime variables will also need to have their cases explicitly constructed whereas in BSON API v1 they could just be inserted directly. -```swift -// BSON API v1 -let x: Document = [ - "_id": self.getDocument() - "x": Date() - "y": [ - "z": [1, 2, false, ["x": 123] as Document] - ] as Document -] -``` -becomes -```swift -// BSON API v2 -let x: Document = [ - "_id": .document(self.getDocument()) - "x": .datetime(Date()), - "y": [ - "z": [1, 2, false, ["x": 123] - ] -] -``` - diff --git a/Guides/Multithreaded-Usage.md b/Guides/Multithreaded-Usage.md index 551448dc3..74b9d2354 100644 --- a/Guides/Multithreaded-Usage.md +++ b/Guides/Multithreaded-Usage.md @@ -1,19 +1,54 @@ -# Using MongoSwift in Mulithreaded Applications +# Using the Driver in Multithreaded Applications -## Threadsafe Types -As of MongoSwift 0.2.0, the following types are safe to use across threads: +## Async API +Our asynchronous API is designed to be used in SwiftNIO-based applications running atop `EventLoopGroup`s +composed of one or more `EventLoop`s. + +You must pass in your application's `EventLoopGroup` when initializing a `MongoClient`, like: +```swift +let client = try MongoClient("mongodb://localhost:27017", using: myEventLoopGroup) +``` + +We strongly recommend using a single, global `MongoClient` per application. Each client is backed by a pool of connections per each server in the in MongoDB deployment, and utilizes a background thread to continuously monitor +the state of the MongoDB deployment. Using a single client allows these resources to be efficiently shared +throughout your application. + +### Safe Use Across Event Loops +The following types are all designed to be safe to access across multiple threads/event loops: * `MongoClient` * `MongoDatabase` * `MongoCollection` *We make no guarantees about the safety of using any other type across threads.* -## Best Practices -Each `MongoClient` is backed by a pool of server connections. Any time a client or one of its child objects (a `MongoDatabase` or `MongoCollection`) makes a request to the database (a `find`, `insertOne`, etc.) a connection will be retrieved from the pool, used to execute the operation, and then returned to the pool when it is finished. +That said: each of these types will, by default, not necessarily always return `EventLoopFuture`s on the +same `EventLoop` you are using them on. Each time an `EventLoopFuture` is generated, they will call +`EventLoopGroup.next()` on the `MongoClient`'s underyling `EventLoopGroup` to select a next `EventLoop` to use. -Each `MongoClient` uses its own background thread to monitor the MongoDB topology you are connected to. +To ensure thread safety when working with these returned futures, you should call `hop(to:)` on them in order +to "hop" the future over to your current event loop, which ensures any callbacks you register on the future +will fire on your current event loop. -**In order to share the connection pool across threads and minimize the number of background monitoring threads, we recommend sharing `MongoClient`s across threads.** +Depending on your use case, a more convenient alternative for you may be to use versions of these core driver +types which are "bound" to particular `EventLoop`s, i.e. that always automatically return `EventLoopFuture`s +on the `EventLoop` they are bound to (as opposed to any `EventLoop` from the underlying `EventLoopGroup`). -## Usage With Server-side Swift Frameworks +To use the "bound" API, you can call `bound(to:)` on your global `MongoClient` to instantiate an `EventLoopBoundMongoClient`, which a small wrapper type around a `MongoClient` that returns futures solely +on its bound `EventLoop`. Any child `MongoDatabase`s or `MongoCollection`s retrieved from the bound client will automatically be bound to the same `EventLoop` as the client. + +Please see the [EventLoopFuture](https://apple.github.io/swift-nio/docs/current/NIO/Classes/EventLoopFuture.html) +documentation for more details on multithreading. +### Usage With Server-side Swift Frameworks See the [`Examples/`](https://github.com/mongodb/mongo-swift-driver/tree/main/Examples) directory in the driver GitHub repository for examples of how to integrate the driver in multithreaded frameworks. + +## Sync API +In the synchronous API, we strongly recommend using a single, global `MongoClient` per application. Each client is backed by a pool of connections per each server in the in MongoDB deployment, and utilizes a background thread to continuously monitor +the state of the MongoDB deployment. Using a single client allows these resources to be efficiently shared +throughout your application. + +The following types are safe to share across threads: +* `MongoClient` +* `MongoDatabase` +* `MongoCollection` + +*We make no guarantees about the safety of using any other type across threads.*