Hacker News new | ask | show | jobs
by physicles 518 days ago
> There is no difference between OpenAPI and REST, it's a strange distinction.

That threw me off too. What the article calls REST, I understand to be closer to HATEOAS.

> I've open sourced a Go library for generating your clients and servers from OpenAPI specs

As a maintainer of a couple pretty substantial APIs with internal and external clients, I'm really struggling to understand the workflow that starts with generating code from OpenAPI specs. Once you've filled in all those generated stubs, how can you then iterate on the API spec? The tooling will just give you more stubs that you have to manually merge in, and it'll get harder and harder to find the relevant updates as the API grows.

This is why I created an abomination that uses go/ast and friends to generate the OpenAPI spec from the code. It's not perfect, but it's a 95% solution that works with both Echo and Gin. So when we need to stand up a new endpoint and allow the front end to start coding against it ASAP, the workflow looks like this:

1. In a feature branch, define the request and response structs, and write an empty handler that parses parameters and returns an empty response.

2. Generate the docs and send them to the front end dev.

Now, most devs never have to think about how to express their API in OpenAPI. And the docs will always be perfectly in sync with the code.

8 comments

HATEOAS is just REST as originally envisioned but accepting that the REST name has come to be attached to something different.
> This is why I created an abomination that uses go/ast and friends to generate the OpenAPI spec from the code

OpenAPI is a spec not documentation. Write the spec first then generate the code from the spec.

You are doing it backwards, at least in my opinion.

That's conceptually true, and yet if the hundreds of code generators don't support Your Favorite OAPI Feature ™ then you're stuck, whereas the opposite is that unless your framework is braindead it's going to at least support some mapping from your host language down to the OAPI spec. I doubt very seriously it's pretty, and my life experience is that it will definitely not be bright enough to have #/component reuse, but it's also probably closer to 30 seconds to run $(go generate something) than to launch an OAPI editor and now you have a 2nd job

I'd love an OAPI compliance badge (actually what I'm probably complaining about is the tooling's support for JSON Schema) so one could readily know which tools to avoid because they were conceived in a hackathon and worked for that purpose but that I should avoid them for real work

This comes down to your philosophical approach to API development.

If you design the API first, you can take the OpenAPI spec through code review, making the change explicit, forcing others to think about it. Breaking changes can be caught more easily. The presence of this spec allows for a lot of work to be automated, for example, request validation. In unit tests, I have automated response validation, to make sure my implementation conforms to the spec.

Iteration is quite simple, because you update your spec, which regenerates your models, but doesn't affect your implementation. It's then on you to update your implementation, that can't be automated without fancy AI.

When the spec changes follow the code changes, you have some new worries. If someone changes the schema of an API in the code and forgets to update the spec, what then? If you automate spec generation from code, what happens when you express something in code which doesn't map to something expressible in OpenAPI?

I've done both, and I've found that writing code spec-first, you end up constraining what you can do to what the spec can express, which allows you to use all kinds of off-the-shelf tooling to save you time. As a developer, my most precious resource is time, so I am willing to lose generality going with a spec-first approach to leverage the tooling.

In my part of the industry, a rite of passage is coming up with one's own homegrown data pipeline workflow manager/DAG execution engine.

In the OpenAPI world, the equivalent must be writing one's own OpenAPI spec generator that scans an annotated server codebase, probably bundled with a client codegen tool as well. I know I've written one (mine too was a proper abomination) and it sounds like so have a few others in this thread.

> In the OpenAPI world, the equivalent must be writing one's own OpenAPI spec generator

Close, it's writing custom client and server codegen that actually have working support for oneOf polymorphism and whatever other weird home-grown extensions there are.

> Once you've filled in all those generated stubs, how can you then iterate on the API spec? The tooling will just give you more stubs that you have to manually merge in, and it'll get harder and harder to find the relevant updates as the API grows.

This is why I have never used generators to generate the API clients, only the models. Consuming a HTTP based API is just a single line function nowadays in web world, if you use e.g. react / tanstack query or write some simple utilities. The generaged clients are almost never good enough. That said, replacing the generator templates is an option in some of the generators, I've used the official openapi generator for a while which has many different generators, but I don't know if I'd recommend it because the generation is split between Java code and templates.

I'm scratching my head here. HATEOAS is the core of REST. Without it and the uniform interface principle, you're not doing REST. "REST" without it is charitably described as "RESTish", though I prefer the term "HTTP API". OpenAPI only exists because it turns out that developers have a very weak grasp on hypertext and indirection, but if you reframe things in a more familiar RPC-ish manner, they can understand it better as they can latch onto something they already understand: procedure calls. But it's not REST.
> This is why I created an abomination that uses go/ast and friends to generate the OpenAPI spec from the code.

This is against "interface first" principle and couples clients of your API to its implementation.

That might be OK if the only consumer of the API is your own application as in that case API is really just an internal implementation detail. But even then - once you have to support multiple versions of your own client it becomes difficult not to break them.

I don't see why it couples clients to the implementation.

Effectively, there's no difference between writing the code first and updating the OpenAPI spec, and updating the spec first and then doing some sort of code gen to update the implementation. The end state of the world is the same.

In either case, modifications to the spec will be scrutinized to make sure there are no breaking changes.

Yeah this is the way, I mean if the spec already exists it makes sense to go spec-first. I went spec-first last time I built an API because I find most generators to be imperfect or lacking features; going spec-first ensured that the spec was correct at least, and the implementations could do the workarounds (e.g. type conversions in Go) where necessary.

That is, generate spec from code and your spec is limited to what can be expressed by the code, its annotations, and the support that the generator has. Most generators (to or from openapi) are imperfect and have to compromise on some features, which can lead to miscommunication between clients/servers.

OpenAPI spec being authored by a human or a machine, it can still be the same YAML at the end of the day, so why would one approach be more brittle / breaks your clients than the other?
The oapi-codegen tool the OP was put out (which I use) solves this by emitting an interface though. OpenAPI has the concept of operation names (which also have a standard pattern), so your generated code is simply implementing operation names. You can happily rewrite the entire spec and provided operation names are the same, everything will still map correctly - which solves the coupling problem.