Live data from Hacker News

How to Design Better APIs

r.bluethl.net

91–100 of 238 posts

Re: How to Design Better APIs

#91
post #45

Earlier quoted context omitted.

Here's an example straight from code I've rewritten at least 5 times because I'm allergic to saving myself time: export enum ErrorCode { InvalidEntity = 1, NotSupported = 2, UnexpectedServerError = 3, InvalidRequest = 4, Validation = 5, // ... } export enum ResponseStatus { Success = "success", Error = "error", } export class ResponseEnvelope { public readonly status: ResponseStatus = ResponseStatus.Success; public r…

I completely disagree. I find envelopes to be unnecessary cruft that just junk up otherwise clear code. And I think packing metadata into an envelope along with the actual data keeps people from thinking clearly about their own APIs, mostly with respect to ambiguity surrounding the word “error”. Validation errors are errors and network failures are errors, but they’re very different animals and should never be confla…

[deleted]

Re: How to Design Better APIs

#92
post #4

Some nice tips in here. However, tip 15, I strongly disagree with: > 15. Allow expanding resources I would suggest the opposite. A REST API should not return nested resources at all. Instead, and to stay with the example provided on the website, to obtain the "orders", the /users/:id/orders endpoint should be called. It might be tempting to return nested resources, because clients would only have to make a single cal…

> to obtain the "orders", the /users/:id/orders endpoint should be called

Ok, but an order has an array of order lines, and each order line has sub-arrays as well, like details of the packages it was shipped in etc.

So that might be 5-10 calls to get an order line, and for a few thousand lines we're looking at several tens of thousand calls to get a full order.

Secondly, you then make some changes and want to replace the order with the data you got. You then have to produce a delta and upload that. And those thousands of calls better be done in a transaction of sorts, otherwise you'll have big issues.

Seems easier to me to just be able to GET or PUT an entire order with all the details in one go.

Re: How to Design Better APIs

#93
post #45

Earlier quoted context omitted.

I completely disagree. I find envelopes to be unnecessary cruft that just junk up otherwise clear code. And I think packing metadata into an envelope along with the actual data keeps people from thinking clearly about their own APIs, mostly with respect to ambiguity surrounding the word “error”. Validation errors are errors and network failures are errors, but they’re very different animals and should never be confla…

Well I imagine this is why sages like uncle bob and sam newman will always be needed. > Validation errors are errors and network failures are errors, but they’re very different animals and should never be conflated. > I don’t want to check for a status code in metadata ever, when an HTTP status is provided. These seem like conflicting views. If the HTTP status is all you check you must be conflating application/opera…

[deleted]

Re: How to Design Better APIs

#94
My recommendation: Use PUT everywhere instead of POST. PUT has to be idempotent, so if the request fails, the client can simply post it again. This solves issue like worrying about creating a second copy of an item if a POST timed out.

Using PUT to create elements means that the client has to supply the ID, but this is easily solved by using GUIDs as IDs. Most languages have a package for create a unique GUIDs.

Re: How to Design Better APIs

#95
post #82

Earlier quoted context omitted.

I upvoted because I'm curious to hear what others are doing. We typically make sure to only make such breaking changes where either the now-required value or a sane filler value could be used. If it's the same API for the same purpose, it's usually not a stretch to assume the values for a new field are derived from some combination of an old field or else are primitive components of an old field such that they can be…

Thanks. This is a fair point. I made up the example only to illustrate the idea. Since Stripe is considered some sort of benchmark here I was curious to see how they tackle all the learnings they will have over time...I feel it is very hard to think through all the future cases especially when you are just about starting out with your product. For example, in financial services and insurance, regs change and what dat…

I think Stripe was originally built on Rails (can’t find anything to confirm that at the moment). But my guess is they enforce things at the app layer, since Rails didn’t really provide a good way to enforce things at the DB layer originally. They support very old API versions by transforming requests backwards and forward through a list of API version transforms, which also suggests to me that this sort of thing is enforced at the app layer rather than the DB.

Re: How to Design Better APIs

#96
post #72
post #70

Can someone share how they handle versioning in their API when it comes to data model changes? For example `POST /users` now takes a required field `avatar_url` but it was not part of `v1`. Since this field is validated in the DB, merely having `v1` `v2` distinction at the API layer is not sufficient. So I was thinking we will have to either 1) disable DB validations and rely on app validations or 2) run two separate…

Those are possible, but ugly solutions. Two cleaner ones are either depracate and remove the v1 api altogether, or when inserting a record to the database from the v1 api, use a default dummy value for avatar_url.

Yes, and definitely favour the deprecation. Treat web APIs like any interface - have minor and major versions, deprecating then dropping old versions.

Re: How to Design Better APIs

#98
post #4

Some nice tips in here. However, tip 15, I strongly disagree with: > 15. Allow expanding resources I would suggest the opposite. A REST API should not return nested resources at all. Instead, and to stay with the example provided on the website, to obtain the "orders", the /users/:id/orders endpoint should be called. It might be tempting to return nested resources, because clients would only have to make a single cal…

> to obtain the "orders", the /users/:id/orders endpoint should be called Ok, but an order has an array of order lines, and each order line has sub-arrays as well, like details of the packages it was shipped in etc. So that might be 5-10 calls to get an order line, and for a few thousand lines we're looking at several tens of thousand calls to get a full order. Secondly, you then make some changes and want to replace…

> Ok, but an order has an array of order lines, and each order line has sub-arrays as well, like details of the packages it was shipped in etc.

> So that might be 5-10 calls to get an order line, and for a few thousand lines we're looking at several tens of thousand calls to get a full order.

You definitely need to pick and choose the granularity you offer. There's a level of normalization in data structuring that approaches absurdity and this would be a good example of an absurd case.

It is however an excellent demonstration of why making overbroad "you must never do X" rules is dangerous.

I think a decent test is to ask yourself the question: "How much data is here that I don't need?"

In the example referenced, if I'm looking at the full order history, I probably want to see only summary/header data with the option to view the full order details, so it wouldn't make sense to return to the user potentially a hundred thousand order lines' worth of data just because they're trying to view their history.

If I then want to view the /orders/:id for one specific order, at that point it does make sense to return all of the related lines, shipment details, etc.

Re: How to Design Better APIs

#99

My recommendation: Use PUT everywhere instead of POST. PUT has to be idempotent, so if the request fails, the client can simply post it again. This solves issue like worrying about creating a second copy of an item if a POST timed out. Using PUT to create elements means that the client has to supply the ID, but this is easily solved by using GUIDs as IDs. Most languages have a package for create a unique GUIDs.

What are the use cases where idempotency is not needed or even harmful?

Re: How to Design Better APIs

#100

Sometimes, I feel that we ought to have a simple protocol, on top of HTTP, to simply do remote procedure calls and throw out all this HTTP verbs crap. Every request is a http POST, with or without any body and the data transfer is in binary. So that objects can be passed back and forth between client and server. Sure, there is gRPC, but it requires another API specification (the proto files). There I said it. HTTP Ve…

gRPC is encoding agnostic, and requires _no_ Protobuf at all.

See: https://grpc.io/blog/grpc-with-json/

Post reply on HN