A rant about APIs

11 points by clintonb 16 hours ago on lobsters | 19 comments

regalialong | 14 hours ago

Show of hands. Who likes handwriting API clients? If your hand is up, I don’t believe you. The OpenAPI specification has existed for 15 years. Publishing an API without one is just disrespectful at this point. Why don’t you like me? Why do you want to make my life harder when I’m trying to give you money? Help me help you. Give me a spec so I can generate a typed client and focus on my business.

People actively want OpenAPI generated clients?

Maybe I just pick the wrong languages (Go and .NET) with the wrong projects, but every experience of mine with generated clients has been pretty sucky, because you'll get loads of generated unidiomatic objects with no guide on how to use since the spec author likely doesn't even know / actively use the language you're writing in.

I thought everyone put up with it because codegen is cheaper than wasting (for lack of a better word) engineers away at making banger API clients.

In contrast to figuring the incantation requires to call the generated output, I've preferred to just write out the requests myself because I can make nicer code with it.

DustyFuzzy | 13 hours ago

I can tell you that when I've tried generating clients for .NET it's very clear the generated code is essentially ported from Java. It's so clunky and weird to work with when you're used to the .NET world.

I still want an OpenAPI doc, because it means I can import it into my tooling of choice (Bruno at work, Yaak at home) to mess around with, but I'm certainly handrolling an actual API interface in the application.

regalialong | 14 hours ago

So you created self-serve credential issuance. Cool. But wait! Now you tell me the credential is associated with the identity of the person that created the credential. Meaning…all logs are associated with that person, so it’s impossible to discern between API calls from our backend applications and calls made by that person in your web app?

Also ugh, yes, thank you. This is such a pet peeve of mine.

Ahh, yes, my email is totally definitely vendorname-oncall@example.com.

Two more problems I've run into (multiple times):

The OpenAPI spec is invariably incomplete or lacking in some detail. For example, maybe there's an invariant that the developer has documented but couldn't (or chose not to) encode in the specification. Or maybe the spec is just out of date or wrong or whatever. Either way, I'd rather just write a better client myself for the specific parts of the API I need.

Also, unless your API provides easy-to-use local instances for tests, I'm probably going to need to mock the API in some way. But the classic advice (for good reason!) is never to mock an external module or interface. Yes, technically the code for the generated client lives within my project, but it's not an interface that I've designed, and correctly mocking it will be very difficult. So even if I do use the generated client, I'm still going to want to wrap that client in a module or interface so that I can mock it at a boundary that I own.

This only really works if the parts of an API that I need to consume are small enough to be manageable, but I feel like that's very often the case in practice.

chriswarbo | 8 hours ago

But the classic advice (for good reason!) is never to mock an external module or interface.

Tangential, but: what is that advice, and what are those reasons? I've never come across them.

I try to avoid mocking wherever possible; but sometimes I have to resort to it, precisely because I'm hitting an external system that (a) I don't control/alter, (b) can't be used directly when testing (e.g. too costly, high latency, whatever) and (c) has behaviour that would be too difficult to fake with a test stub.

I think the oldest formulation is "only mock types you own" described in Mock Roles, Not Objects. The specific advice usually boils down to: if you're going to mock a system, write a wrapper around that system rather than mocking the system directly.

In more concrete terms, say you're using SQLite as a database: don't mock the sqlite module directly, instead create domain functions for the interactions you want to do with the sqlite module and mock those instead. (Stuff like insert_user(...) or list_users(...).) That way, the surface area that you actually need to write mocks for is a lot smaller, and typically a lot less complicated. It's also under your control, so if the underlying module changes (unlikely with SQLite, but more common elsewhere) then your tests don't need to be rewritten to handle pure mock changes.

I mean, with SQLite, I'd probably just use the database directly in the tests because it's so quick and simple, but with an API like the ones in this article it would be different.

adam_d_ruppe | 9 hours ago

Yeah, I never understand the desire to use almost any of these things. The documentation says "GET /orders" and i can literally copy/paste that into my program and it works, even if I'm using esoteric languages, and people reading it know exactly what it is doing, whereas if it is some generated thing there's extra translation layers and... why? What benefit does this bring me? And often the generated code is some bizarre list of flat functions which tends to make little business sense and I probably only care about a sliver subset of them.

I suppose the biggest difference is that I've never used an LSP or similar, so I guess that's where the big difference is, but I have no need for that since I gotta read the docs of some unfamiliar thing anyway

gbalduzzi | 2 hours ago

I agree with you that openapi generated clients are very bad, but it is a bit tedious to support an endpoint properly in the form of serializing / deserializing both input and output in appropriate DTOs

colonelpanic | 5 hours ago

OpenAPI-generated clients are bad, but Protobuf-generated ones are truly terrible. It can always get worse.

steinuil | 5 hours ago

Yep I don't think I've ever seen code generated by one of these tools that I liked (maybe Go's oapi-codegen, at least on the server side, with some tweaking). You can still write your own generator though! I've done that a few times and it probably took me 20 minutes.

[OP] clintonb | 7 hours ago

I will handwrite the first client if I’m only using a single endpoint, or the request/response DTOs are pretty small. Beyond this number-that-is-mostly-intuition, I prefer a generated TypeScript client so I (or agents, now) have less code to write.

Yes, this assumes the spec properly defines the request and response schemas, which is not always the case.

david_chisnall | 13 hours ago

The authentication really annoys me for web hooks. Client TLS certificates are right there, yet everyone builds their own weird bespoke ad-hoc thing. It would be trivial to run your web hook behind something that terminated TLS and had an allow list of domains that client TLS certs must come from. Every time I’ve had to implement a web hook, it’s had precisely one valid source, so that allow list would be one domain. And then you don’t need to write any code to handle authentication and, crucially, you aren’t parsing any data from the attacker, you’re only ever receiving messages from a trusted sender.

The problem is a lot of folks are doing TLS termination in a place quite distant from the application (CDN, LB).

No OpenAPI spec

If the API is an Open-API–style pseudo-REST one, then yeah — this is annoying.

But I don’t think OpenAPI is the right tool for a proper HATEOAS RESTful API in which the user agent and service uses hypertext to interact.

[OP] clintonb | 7 hours ago

That’s fair, but I have yet to see anyone actually implement a HATEOAS RESTful API. Payments companies are mostly exposing RPC over HTTP.

What examples have you seen?

threedaymonk | 9 hours ago

Seriously, just copy the Stripe API. We spent a lot of time and energy building it. It’s good. Take it.

I think this is just familiarity bias. I've also worked on both ends of the Stripe API, and I don't think it's all that. It's accreted plenty of cruft over time, and it can be just as frustrating and confusing to work with as many other APIs.

[OP] clintonb | 7 hours ago

Good push back. I edited out my own confession of writing a rather awkward /v1/paper_checks.

Student | 7 hours ago

I quite like graphql apis. But fundamentally writing a nice api is hard, which is why libraries often win mindshare just by having a nice API.