
Show HN: Buttery, a DSL/runtime for defining HTTP APIs - evinism
https://github.com/evinism/buttery
======
smt88
I'm a heavy user of Open API Spec. I do have issues with that project, so I'm
certainly hoping something better will replace it, but that said:

What motivated you to build this instead of just using OAS?

Why did you create a DSL when you could've used, say, TypeScript? Using an
existing language allows you to rely on widespread, well-understood, efficient
tooling, so a DSL is sometimes an extreme choice that implies a forceful
rejection of existing languages.

~~~
evinism
I'm not sure any answer other than "I felt like it was a good choice" is gonna
be 100% accurate, but a few things, some of which are speculative, some of
which are reactionary.

(I'm sadly not very familiar with OAS, so it's very possible that OAS supports
many of the things I'm going for!)

\- This takes very heavily after protos, which use a DSL.

\- This rejects protos because of a few reasons, not the least of which is
that every field in proto3 is optional. While it makes sense for google, I
don't think it makes sense for everybody.

\- gRPC is non-http, and I was unhappy with gRPC-web

\- I wanted it to be a terse specification, such that just glancing over the
definition file gives you a sense of what the API consists of.

\- I wanted a solution that handled websockets/server push as a first-class
citizen, and not as a secondary add-on

\- I wanted to provide library-level validation of request / response shape.

\- I wanted to extend it beyond typescript, notably into python (and hopefully
beyond)

\- I like the idea of generic structs and oneofs (which I'm going to implement
in some future version). I think if we had an easy way to define generics in
cross-language APIs, we'd probably use it a fair amount.

\- I like the idea of having an extensible language, e.g. be able to provide a
JSONLogic type. Imagine a field `priceCalculation: JsonLogic<string, number>`.
That'd be pretty cool!

~~~
smt88
Let me preface this by saying that I use OAS because it is the most widely-
supported HTTP API spec. I don't love it, but as of version 3, it handles all
of my use cases and has good tooling support. This prevents me from having to
edit the spec as raw text -- I can typically get by using a GUI (recently been
happy with Apicurio Studio).

That said, I do wish there were a better format for it than YAML/JSON, and I
think some of their design decisions were strange.

Note that a lot of OAS's strengths and weaknesses were inherited from its
parent project, JSON Schema, which is also widely supported.

> _I wanted it to be a terse specification, such that just glancing over the
> definition file gives you a sense of what the API consists of._

OAS specs in YAML (as opposed to JSON) are pretty terse and easy to scan,
especially if the spec is kept fairly DRY by defining/refusing object schemas.

> _I wanted a solution that handled websockets /server push as a first-class
> citizen, and not as a secondary add-on_

OAS fails this one.[1]

> _I wanted to provide library-level validation of request / response shape._

This is something all the major formats (JSON Schema/OAS, RAML, Blueprint,
etc.) do. It's probably the first thing people want to do with an API spec.
OAS has great support for validation in most languages, although
implementation of the spec can be spottier with less-popular stacks like PHP.

> _I wanted to extend it beyond typescript, notably into python (and hopefully
> beyond)_

One of my projects uses Spot[1] to define its API. Spot has CLI tools that
translate it into a plain OAS file (.yaml format), and then there are more CLI
tools that generate code (models, validators, controllers, and even servers)
from that spec file.

If you don't mind a polyglot toolchain -- and I think most of us have one at
this point -- then the Spot => OAS => Python process works fine and is easy to
set up. You don't even need to start with Spot, of course.

> _I like the idea of generic structs and oneofs (which I 'm going to
> implement in some future version). I think if we had an easy way to define
> generics in cross-language APIs, we'd probably use it a fair amount._

I agree that this is super important. OAS supports it, although it can be hard
to read a lot of branches inheritance trees in that particular format.

You can split your definitions into separate files to make it easier (in both
Spot and in plain OAS itself).

> _I like the idea of having an extensible language, e.g. be able to provide a
> JSONLogic type. Imagine a field `priceCalculation: JsonLogic <string,
> number>`. That'd be pretty cool!_

This makes sense, but at the end of the day, "JsonLogic<string, number>" is
just a text format. With OAS, you can define a regex to validate inputs and
outputs, which has been "good enough" in my experience, but definitely falls
short of what you want.

What about GraphQL? That seems to have some of the features you're interested
in, although the GraphQL paradigm is so different from HTTP (and has some
tradeoffs) that I'd understand not even considering it.

1\. [https://github.com/OAI/OpenAPI-
Specification/issues/523](https://github.com/OAI/OpenAPI-
Specification/issues/523)

~~~
evinism
Yeah, GraphQL certainly has many of the features that I was looking at here
too, and I figure that it's the rough direction in which webdev seems to be
going.

I just... like RPCs.

There's also a vague sense of "buttery should work well for multiple services
in a way that other methods might struggle to do so." The fact that RPCs are
grouped into services means that a load balancer can shift RPCs to different
backends based on which buttery service they're talking to. I'm not sure how
that actually compares against common solutions, but it IS interesting :)

Thanks for taking the time to think about all these things! It's very helpful
for me.

