[Discussion] MongoDB Swift driver

I see the PR was merged. Just for our own planning purposes around approximately when we will release, etc. I have some process related questions that maybe you or anyone else in SSWG can answer (apologies if these answers are documented somewhere, but I don't see it mentioned in the incubation process description) -

  • Should edits we want to make to the proposal going forward (e.g. incorporating the switch to using an EventLoopGroupProvider) just be opened as PRs?
  • I know there is usually a second [Feedback] thread containing an edited proposal, at what point do we move onto that thread?

Thanks :slight_smile:

I won't speak for the SSWG, but historically the proposal docs are "products of their time". For example, the Redis proposal no longer reflects the current state of the library's API.

However, seeing as this proposal hasn't reached the feedback thread yet, I would say you could just open a PR with edits, if you want to improve the document before that thread begins.

Again, historically this has been when the author feels that they have adequately responded to feedback and left enough time for people to voice their concerns.

1 Like

the proposal in https://github.com/swift-server/sswg/tree/master/proposals does not need to reflect API changes after the proposal is excepted, but it does need to reflect the state of the proposal prior to the SSWG reviewing it. in other words, as you prepare the [Feedback] thread, if the proposal has materially changed you want to submit a PR into https://github.com/swift-server/sswg/tree/master/proposals to reflect that, and the same at the end of the feedback period which is normally 2 weeks after the [Feedback] thread was posted.

related, I am about to suggest to the SSWG a small procedural change such that the [Feedback] thread will be just a "call for action" with link to the updated proposal text in https://github.com/swift-server/sswg/tree/master/proposals.

@Mordil answer above is a correct. to add, if you feel you have not received enough useful feedback you can call out specific questions, tradeoffs or design dilemma you want the community to chime in on.

1 Like

Thanks for the details @Mordil @tomerd!

One question that's come up for us recently we'd love to hear community thoughts on is how options are provided to API methods. As it stands, we have structs for each API method that contain allowed options for that method. But we've considered an alternative approach where we instead just accept all the options directly as arguments to the method.

For example, countDocuments looks like this:

public func countDocuments(
    _ filter: Document = [:],
    options: CountDocumentsOptions? = nil,
    session: ClientSession? = nil
) -> EventLoopFuture<Int>

and CountDocumentsOptions looks like this:

public struct CountDocumentsOptions: Codable {
    /// Specifies a collation.
    public var collation: Document?

    /// A hint for the index to use.
    public var hint: Hint?

    /// The maximum number of documents to count.
    public var limit: Int?

    /// The maximum amount of time to allow the query to run.
    public var maxTimeMS: Int?

    /// A ReadConcern to use for this operation.
    public var readConcern: ReadConcern?

    /// A ReadPreference to use for this operation.
    public var readPreference: ReadPreference?

    /// The number of documents to skip before counting.
    public var skip: Int?

    /// Convenience initializer allowing any/all parameters to be optional
    public init(
        collation: Document? = nil,
        hint: Hint? = nil,
        limit: Int? = nil,
        maxTimeMS: Int? = nil,
        readConcern: ReadConcern? = nil,
        readPreference: ReadPreference? = nil,
        skip: Int? = nil
    ) { ... }
}

So right now if you wanted to perform a count using some options you'd do something like

var opts = CountDocumentsOptions()
opts.readPreference = .primaryPreferred
opts.skip = 100

// alternatively construct options in one line
let opts = CountDocumentsOptions(readPreference: .primaryPreferred, skip: 100)

collection.countDocuments(["a": 1], options: opts).map { ... }

The alternative way of doing this would be to have countDocuments looks like this:

public func countDocuments(
    _ filter: Document = [:],
    collation: Document? = nil,
    hint: Hint? = nil,
    limit: Int? = nil,
    maxTimeMS: Int? = nil,
    readConcern: ReadConcern? = nil,
    readPreference: ReadPreference? = nil,
    skip: Int? = nil,
    session: ClientSession? = nil
) -> EventLoopFuture<Int>

And then to call the method with options you'd do

collection.countDocuments(
    ["a": 1],
   readPreference: .primaryPreferred, 
   skip: 100
).map { ... }

The first approach is more verbose, but it does allow reuse of options structs and prevents our method signatures from becoming extremely lengthy (some API methods like find have 20+ valid options users can specify).

Both approaches have precedent amongst our other drivers.

Would love to hear what you all think!

1 Like

+1 to the options struct pattern. I think it makes the API more readable at a glance. Being able to re-use the options structs is also a win for maintainability and extensibility.

3 Likes

The discussion here was very positive and the SSWG agrees this should move forward to the final [Feedback] phase.

The [Feedback] post should be similar to the original [Discussion] post but updated with all of the latest changes / requested clarification. You can see an example here:

If all goes well in the [Feedback] phase, the last step will be voting on accepting the package to the index.

Thank you for putting this detailed proposal together @kmahar!

@tomerd can you lock this thread?

3 Likes

+1 on that too. It'll also make your life a lot easier if you ever have to add another option without breaking API.

1 Like

+1

1 Like