Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

endpoint versioning #524

Open
yoshuawuyts opened this issue May 21, 2020 · 1 comment
Open

endpoint versioning #524

yoshuawuyts opened this issue May 21, 2020 · 1 comment

Comments

@yoshuawuyts
Copy link
Member

Fastify has a fun way of versioning endpoint using Accept-Version headers. The way it's declared looks like this:

fastify.route({
  method: 'GET',
  url: '/',
  version: '1.2.0',
  handler: function (request, reply) {
    reply.send({ hello: 'world' })
  }
})

I was thinking this could be an interesting mechanism for Tide as well. What if we could switch logic based on API version headers like this:

let mut app = tide::new();

app.at("/user")
    .get(|_| async move { Ok("user v2.3.0") })
    .version("1.2.0").get(|_| async move { Ok("user v1.2.0") })
    .version("2.3.0").get(|_| async move { Ok("user v2.3.0") });

The version param would switch based on the Accept-Version header and the Accept: version="..." header. We'd need to figure out the specifics of how to integrate, but I think this could provide an exciting mechanism for versioned endpoints. Which are something that are incredibly important for applications.

Alternatives

The APIs as infrastructure post by Stripe describes an interesting practice they use to provide upgrades to their APIs. This seems like a lot of work to maintain, but it's interesting to think how we could support it. One way we could perhaps do this is if we continued to lean into str::find-like APIs: APIs that can take different types of arguments, including closures. Defining the API part of Stripe's upgrade logic could then in Tide be defined as:

user tide::version::Date;

let mut app = tide::new();

let latest = |_| async move { Ok("user v2.3.0") };
let change_body = |res| async move { res.set_body("user v1.2.0"); res };

app.at("/user")
    .get(latest)
    .version(Date::new("2020-01-30").get(|_| async move {
        let res = latest().await?;
        Ok(change_body(res).await?)
    })
    .version(Date::new("2020-03-30").get(latest);

This is a super rough sketch of what it could look like, but the overall point I'm trying to convey is that there's a way the design above could be made forward compatible with some really elaborate versioning schemes. Thanks!

@jbr
Copy link
Member

jbr commented May 21, 2020

I love this idea, especially the first sketch! It would be extra cool if it supported semver ranges in route definitions. A client would report a specific version and the router would resolve to the first semver range that matched. I'm not sure if that's implied by the sketch or an extension. This could also populate the request with a parsed semver to make for easy conditional logic within a single route handler

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants