Live data from Hacker News

How to Design Better APIs

r.bluethl.net

231–238 of 238 posts

Re: How to Design Better APIs

#231
post #229

Earlier quoted context omitted.

Do you intentionally just ignore the point made about timezone changes ?

Well, how do operating systems handle this sort of stuff? With updates. When it's at all possible to update. There are many devices that don't receive any updates at all any more. There are some devices that weren't designed to be updatable to begin with. It's a necessarily manual process and it creates a lot of confusion either way. And it's sufficiently rare to ignore. And these changes tend to be announced in adva…

So you'd rather push out an update every time for your app to rewrite the times it has stored to match the timezone change, instead of "just" updating the timezone database and the app automatically doing the right thing?

And we've had examples in the past years of this happening with only weeks of notice, so no, it's not something that never happens.

Re: How to Design Better APIs

#232

For a second there I thought this was about non-web APIs: https://caseymuratori.com/blog_0024

Side note: I find it quite annoying that web devs have hijacked the term “API“ as a synonyme for “web API“, to the point where many devs are not even aware of the original broader meaning.

Nevertheless, as a (largely) non-web dev I found the article interesting and useful.

Re: How to Design Better APIs

#234

Earlier quoted context omitted.

Such simple approach is limited only to errors without arguments. For more complex use cases, where we would want an error message to indicate that field value was too long and in addition provide maximum field length, we would need to introduce new field in the error response. While it is solvable by adding this information to client application side. It would create a situation where the logic is duplicated in two…

Interesting! Do you find that returning an array of errors works in practice? Most validation I’ve seen looks like: raise error if foo raise other_error if bar This pattern turns into one exception per response, and some foresight in architecting exceptions would be needed

From my experience it serves very well for validating inputs of large forms - e.g. loan application, international payments, etc.

If you need to validate some business logic like if sender's account has funds and receiver's account is not blocked, then this approach starts to look a bit strange. I would guess that the most of the time developers would implement checks so they would fail on first condition and would not check later one. This would result with single error in the array that kinda would look strange.

Validating business logic and returning several errors at the time requires good knowledge of the domain and in depth design of the system you are working. As this creates more complexity and slows down delivery, most of the time it is ditched and used only for particular use cases.

We could say that this would be more applicable to corporate solutions, but IMHO it really depends of the scale of the project, man power and the user experience you would want to create.

Re: How to Design Better APIs

#235
post #104

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.

I don't think we should strive to remove non-idempotent cases. If something is not idempotent does not mean it is bad. It just means that request should be handled differently. In your example (and I ask this as I remained confused after also reading SO): Let's say that you need the client to provide the ID in the request body. In this case, how is using PUT when creating a new resource idempotent if the ID should be…

Upserts are more powerful because the client can always generate a new uuid to get the POST behavior you desire, but the reverse is not the case: there is no straightforward way to safely retry timeouts, partial success, side effects, etc.

You say the same request has a different impact on the system, but what that means is the system converges to the requested state.

Maybe this is overkill if your retry strategy is to have a user in the loop, but I don't see how it's simpler for the client even without any retry behavior (if/else new/reuse uuid vs if/else put/post).

Re: How to Design Better APIs

#236

Earlier quoted context omitted.

The Phoenix framework does this for forms. It requires a whole system built around a type called a Changeset that describes input parameters, their validity, and how to modify a struct to reflect the valid changes. In practice this ends up tightly coupled to the database layer for simplity.

Does "tightly coupled with the database" work, in practice?

It's only tightly coupled if you let it be. You can create changesets around any data shape, whether thats connected to your schema' table, a partial view of a table or entirely independent.

Re: How to Design Better APIs

#237
post #161
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…

Another two reasons to avoid nested resources, are performance and coupling. A nested resource is hard to optimize. Simple, atomic, flat resources can be cached (by clients, proxies or server) much more efficient. Once you allow nested resources, there's no going back, as clients will depend on them. So you've effectively disabled lots of performance improvement options. A nested resource implies data relations. Tigh…

And a third one is more architectural in nature. Your domain has bounded context (even if you haven't defined these explicitely). Within those bounded contexts, you'll have atomicity-constraints: transaction boundaries. Those should typically align, but are hard to find in a new project and will grow and shift over time.

To take that User/Project example: the Project and User have different transaction boundaries: it matters not that a User.username is updated while a Project.projectname is updated differently. But it does matter when a username and password get updated simultanous. And -for the sake of the xample- it matters that the user + users-roles must be within one transaction boundary.

By allowing nested resources on mutations you essentially break the bounded contexts and the transaction boundaries.

By not allowing nested resources on mutations, but allowing them on-read, you introduce inconsistencies: the resources on-insert differ from the resources on-read. This becomes even more problematic when you do allow certain nested resources on mutations but not on others (because, behind the scenes you've determined a bounded context/transaction boundary). To an API consumer this is entirely arbitrary. Why can I "PUT user.roles[1]" but not "PUT user.projects[1]", yet have both included at wish on-read?

With nested resources, here too, you paint yourself in corners. They take away a lot of choices you likely want to make in future. Being a bit more restraining in what you allow clients to do, keeps those options open for you. In this case, keeps the option to move transaction boundaries when business needs require (and they will).

Re: How to Design Better APIs

#238
post #229

Earlier quoted context omitted.

Do you intentionally just ignore the point made about timezone changes ?

Well, how do operating systems handle this sort of stuff? With updates. When it's at all possible to update. There are many devices that don't receive any updates at all any more. There are some devices that weren't designed to be updatable to begin with. It's a necessarily manual process and it creates a lot of confusion either way. And it's sufficiently rare to ignore. And these changes tend to be announced in adva…

This argument is complete non-sense (just because a device doesn't receive software updates it doesn't mean it can't receive date/time updates).

But who cares. We told you, that there are valid use-cases where timestamps are not the best solution and brought examples. If you don't want to see them, it is your loss.

Post reply on HN