|
| 1 | +# A Guided Tour of the Rust Driver Codebase |
| 2 | + |
| 3 | +These are notes intended to accompany an informal walkthrough of key parts of the driver's code; they may be useful on their own but are not intended to be comprehensive or prescriptive. |
| 4 | + |
| 5 | +## Constructing the Client |
| 6 | + |
| 7 | +[src/client.rs](../src/client.rs) |
| 8 | + |
| 9 | +Entry point of the API. The first thing most users will interact with. |
| 10 | + |
| 11 | +* It's just a wrapper around an `Arc`-wrapped internal struct so users can cheaply `clone` it for convenient storage, passing to spawned tasks, etc. |
| 12 | + * (actually a `TrackingArc`, which if a compile-time flag is turned on will track where clones are constructed for debugging) |
| 13 | +* Notable internal bits: |
| 14 | + * `topology`: tracks the servers we're connected to and maintains a pool of connections for each server. |
| 15 | + * `options`: usually parsed from user-provided URI |
| 16 | +* `Client` can be constructed from: |
| 17 | + * A URI string. By far the most common. |
| 18 | + * An options object directly. Power user tool. |
| 19 | + * A builder if the user needs to enable in-use encryption. |
| 20 | +* Events! |
| 21 | + * Three different kinds: |
| 22 | + * `Command`: "we told the server to do something" |
| 23 | + * `CMAP`: "something happened with an open connection" |
| 24 | + * `SDAM`: "something happened with a monitored server" |
| 25 | + * plus logging (via `tracing`)! |
| 26 | +* `pub fn database`: gateway to the rest of the public API |
| 27 | +* `register_async_drop`: Rust doesn't have `async drop`, so we built our own. |
| 28 | +* `select_server`: apply criteria to topology, get server (which has connection pool) |
| 29 | + |
| 30 | +## Doing Stuff to Data |
| 31 | + |
| 32 | +[src/db.rs](../src/db.rs) |
| 33 | + |
| 34 | +Gotta go through `Database` to do just about anything. Primarily users will be getting handles to `Collection`s but there are a bunch of bulk actions that can be done directly. |
| 35 | + |
| 36 | +* Like `Client`, it's just an `Arc` around an inner struct so users can cheaply `clone` it and pass it around. |
| 37 | + * The inner struct is much lighter: a handle on the parent `Client`, a string name, and some options. |
| 38 | +* `new` isn't public. Have to get it from `Client`. |
| 39 | +* Can get a `Collection` from it, but there aren't any data operations in here, leading to... |
| 40 | + |
| 41 | +## Anatomy of an Action |
| 42 | + |
| 43 | +[src/action/aggregate.rs](../src/action/aggregate.rs) |
| 44 | + |
| 45 | +*Actions* are the leaves of the public API. They allow for fluent minimal-boilerplate option setting with Rustic type safety and minimal overhead; the drawback is that the internals are a little gnarly. |
| 46 | + |
| 47 | +A usage example: |
| 48 | + |
| 49 | +```rust |
| 50 | +let cursor = db |
| 51 | + .aggregate([doc!{ ... }]) // [1] |
| 52 | + .bypass_document_validation(true) // [2] |
| 53 | + .session(s) // [3] |
| 54 | + .await?; // [4] |
| 55 | +``` |
| 56 | + |
| 57 | +Breaking down what's happening here: |
| 58 | + |
| 59 | +1. This constructs the transient `Aggregate` type. Typically users will never deal with this type directly; it'll be constructed and consumed in the same method call chain. The transient action types can be thought of as _pending_ actions; they contain a reference to the target of the call, the parameters, the options, and the session. |
| 60 | +2. This sets an option in the contained options object via the `option_setters!` proc macro, which also generates helpful doc links. |
| 61 | +3. This sets the pending action to use an explicit session. Note that this can only be done if the action was using an implicit session; Rust lets us enforce at compile-time that you can't call `.session` twice :) We track this at the type level because it changes the type of the returned `Cursor`. |
| 62 | +4. The `action_impl` proc macro will generate an `IntoFuture` impl for `Aggregate`, so when `await` is called it will be converted into a call to `execute`. |
| 63 | + |
| 64 | +With all that, the body of `execute` is pretty small - it constructs an `Aggregate` _operation_ and hands that to the client's `execute_cursor_operation`. This pairing between action and operation is very common: _action_ is the public API and _operation_ is the command sent to the server. |
| 65 | + |
| 66 | +## Observing an Operation |
| 67 | + |
| 68 | +[src/operation/insert.rs](../src/operation/insert.rs) |
| 69 | + |
| 70 | +Redirecting from aggregate to insert here; aggregate has the cursor machinery on top of operation. |
| 71 | + |
| 72 | +An `Operation` is a command that can be run on a mongodb server, with the bundled knowledge of how to construct the `Command` from the parameters of the `Operation` and how to interpret the `RawCommandResponse` from the server. |
| 73 | + |
| 74 | +The `Operation` trait is split into `Operation` (used for type constraints) and `OperationWithDefaults` (provides default impls and a blanket impl of `Operation`) to allow forwarding types to implement the base `Operation` without new methods silently introducing bugs. |
| 75 | + |
| 76 | +Most `Operation` impls are straightforward: aggregate components into a buffer with minor conditional logic, deserialize the response. |
| 77 | + |
| 78 | +## Examining an Executor |
| 79 | + |
| 80 | +[src/client/executor.rs](../src/client/executor.rs) |
| 81 | + |
| 82 | +This is very much where the sausage is made; it's also very rare for it to need changes. |
| 83 | + |
| 84 | +* `execute_operation` ... throws away some output of `execute_operation_with_details` |
| 85 | +* `execute_operation_with_details` does some pre-retry-loop validation, calls into `execute_operation_with_retry` |
| 86 | +* `execute_operation_with_retry` |
| 87 | + * tracks transaction state |
| 88 | + * selects a server |
| 89 | + * checks out a connection from the pool of the selected server |
| 90 | + * `execute_operation_on_connection` |
| 91 | + * handles errors and retries |
| 92 | +* `execute_operation_on_connection` |
| 93 | + * builds command from operation |
| 94 | + * lots of session-handling |
| 95 | + * sends wire-format `Message` from `Command` |
| 96 | + * does bookkeeping from response |
| 97 | + |
| 98 | + ## Future Fields |
| 99 | + |
| 100 | + This is far from comprehensive; most notably, this doesn't cover: |
| 101 | + * the internals of `Topology` |
| 102 | + * the `bson` crate and our integration with `serde` |
| 103 | + * the testing infrastructure |
| 104 | + |
0 commit comments