0

I am implementing a feature in an app where a user can unlock achievements, and when the client requests what achievements the user has unlocked, the client needs to know the user hasn't seen it before. There are UI considerations around new achievements vs. old ones.

The only way I can see solving this nicely is to break the idempotent and safe rule of GET requests. How I am currently thinking of solving it (which breaks this rule):

GET /users/:id/achievements

Achievements is data stored something like:

last_accessed: Date
achievements: [{name: Enum, unlocked_at: Date}]

The GET endpoint would then compare what is new and not based on last_accessed and unlocked_at, and return data, BUT it would then have to update the last_accessed Date, which is what breaks the GET rule. That, and the fact that if you query it again, the 'new' achievements would be moved over to the 'not new' list on subsequent queries.

Please note that solving this purely client side is not an option, as if the user logs in on a different device, we can not have every achievement be seen as new.

Any advice or recommendations on how to solve this problem?

  • You can also have e.g. ?since={last-seen-id/timestamp} to allow clients to decide what data they might need. – jonrsharpe Mar 20 '20 at 14:12
2

Any advice or recommendations on how to solve this problem?

Sure. Just don't assume something was "seen" just because you sent it to the client. Create another API call (probably a PUT or POST) that marks an entry as "seen". Do not change this mark on a GET request. Now you have GET requests that are pure and read-only and you have full control client-side what you consider "seen", independent of what the server has actually sent you.

For example if the server sends you 500 items and you can only display 10 at a time, I would not consider the other 490 "seen" until the user actually scrolls and displays them. But that is something the server will never know from just answering a GET request for data. So it's a WIN-WIN. Cleaner calls and better UX.

  • Or simply use a single POST. – Robert Harvey Mar 20 '20 at 14:11
1

The GET endpoint would then compare what is new and not based on last_accessed and unlocked_at, and return data, BUT it would then have to update the last_accessed Date, which is what breaks the GET rule. That, and the fact that if you query it again, the 'new' achievements would be moved over to the 'not new' list on subsequent queries.

What you are describing isn't really a violation. Consider how Fielding describes GET in 2002.

HTTP does not attempt to require the results of a GET to be safe. What it does is require that the semantics of the operation be safe, and therefore it is a fault of the implementation, not the interface or the user of that interface, if anything happens as a result that causes loss of property

What we've all agreed is that the client's request is essentially read-only, GET isn't asking you to update the current representation, it's just asking for a copy of whatever representation is current.

The fact that the representation of your resource changes over time is completely normal.

A resource is a conceptual mapping to a set of entities, not the entity that corresponds to the mapping at any particular point in time.

More precisely, a resource R is a temporally varying membership function....

Finally, note that while all of the above agree that your implementation is permitted, there's no promise that it works the way you intend. For example, if the response to the GET request never makes it back to the client (unreliable network), then the user won't see the achievements you have erased.

As far as REST is concerned, that's fine -- a general purpose client doesn't expect to see every representation of a resource during its lifetime.

But if you have a real business need here, you may need a communication protocol that is more robust.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.