4 ms·
JSON Patch is a bizarre Frankenstein's monster made of the cognitive dissonance of REST aficionados. JSON Patch is not REST. It is not representational state.
by zzbzq 4y ago
JSON Patch is a bizarre Frankenstein's monster made of the cognitive dissonance of REST aficionados.
JSON Patch is not REST. It is not representational state. But it's also not the way any sane person has ever done RPC over HTTP. Typically RPCs accomplish mutation by just supplying whatever is the most ergonomic at the time; if you want to be able to rename an entity, you implement a /rename endpoint., etc.
In Javascript, there was always a normal, obvious way to handle Patch, which was to leverage null vs. undefined properties in javascript. However, in statically typed languages, people wanted to deserialize data into static types that don't support the concept of "undefined." This was the beginning of the end, because deserializing into a static type is actually a bug for an API that has an evolving schema. No longer can you distinguish between a null and undefined property if the a client omitted it.
So instead we end up with yet another preposterous proposed "standard" of Json patch, a completely insane and difficult to read instruction-set-over-json that is truly a hodgepodge.
Remember, mutations like this need to be assembled by the client. That means not only the code but also the user experience. The classic REST-like ways of doing these mutations originate in the "for free" usage of the HTML form element. That's gone, now you need some insane JsonPatcher library, and you need to somehow manipulate your UI around it.
I work in an industry of idiots.
- dgb23 4y agoI think your comment is funny, I love a good rant. I don't think it's as terrible as you describe. But I see your point. We can already do this kind of thing in many different ways that seem preferable over this.
- malfist 4y agoWhy is it problematic to use a JsonPatcher library over some browser specific feature? This is like refusing to use screws because you own a hammer and don't want to buy a drill.
- withinboredom 4y agoYep, probably should be using a screwdriver. A drill is probably overkill since it’s designed for drilling and not screwing…
- burnished 4y agoI had to go double check some words because of your comment, you are correct though. Turns out 'screwdriver' can refer to manual or powered, where I had been using 'screwdriver' to mean manual and 'drill' was pulling double duty as both powered screwdriver and drills of all sorts. For anyone else curious, while they are frequently used interchangeably, the difference is that an electric screwdriver goes slower to give you better precision.
- wil421 4y agoImpact drivers for screws and the drill for the pilot holes.
- zepolen 4y ago> In Javascript, there was always a normal, obvious way to handle Patch, which was to leverage null vs. undefined properties in javascript Yeah, no, patch as you describe it is not a patch. Given this: {field: [1,2,3]} And given that two clients want to each add 4 and 5 respectively to the document, one will overwrite the other, which breaks the very principle of a patch since you end up with either [1,2,3,4] or [1,2,3,5] instead of [1,2,3,4,5] (or [1,2,3,5,4] depending on who was faster). > I work in an industry of idiots. Very true.
- biaachmonkie 4y agoThen you'd have to implement a version or hash field on the object and verify it before updating and fail on mis-match and handle the version mis-match error on the client and re-retrieve and update again. GET: {version: 1, field: [1,2,3]} PATCH: {version: 1, field: [1,2,3,4]} # succeeds PATCH: {version: 1, field: [1,2,3,5]} # fails with HTTP 409 response # Client displays some sort of warning that the update failed, GETs the object again and merges the new state from the server with it's state and can retry the update GET: {version: 2, field: [1,2,3,4]} PATCH: {version: 2, field: [1,2,3,4,5]} # succeeds
- zepolen 4y agoSure, though prefer to use If-Unmodified-Since or If-Match as those are standard and caches support them properly. Another issue is how do you support both removing `field` from the document or setting it to null? Sending `field: null` will only work for one of those cases. Likewise the moment you start to do this architecture is the moment you fail to scale. You're putting unnecessary load on your backend by forcing the client to refetch the resource in order to make another update, not to mention the scenario where a lot of clients are trying to add items to field > GETs the object again and merges the new state from the server At some critical point all those clients will start blocking each other as they constantly make PATCHes that fail and reGET only to fail again because another client managed to make their PATCH before them. The problem made worse by network latency and app code. Patches that work with changesets will support a much higher concurrent write load as the concurrency/serialization is done server side, and the changesets will be small rather than the entire value. Under load all the individual requests may take longer to complete they will all ultimately succeed. Concurrency conflicts may still occur but they will be a couple orders of magnitude less frequent. Depending on the type of update, eg. adding items to a list, this is what you want. Removing / updating values otoh may be classed as something you'd prefer to have constraint check via If- headers.
- mixedCase 4y ago> However, in statically typed languages, people wanted to deserialize data into static types that don't support the concept of "undefined." This was the beginning of the end, because deserializing into a static type is actually a bug for an API that has an evolving schema No, that's just a bug in your type. data FieldPatch a = Value a -- A value unmarshable to type `a` was given, apply patch | SetToNull -- null was given, apply and patch to null | Undefined -- field was not set, do not patch | UnexpectedValue RawJSON -- optional, allows manual handling of unexpected types You can do this in things like Java too with interfaces if you can tolerate openness, or if you want closed types like in the example, through the visitor pattern.
- brabel 4y ago> You can do this in things like Java too with interfaces if you can tolerate openness Java supports sealed interfaces now, so this is not true anymore.
- eyelidlessness 4y ago> JSON Patch is not REST. It is not representational state. This is a perfectly reasonable take, although it fits well in systems otherwise generally more inclined to use HTTP semantics. I know that’s not REST, but it’s certainly easier to reason about and clearly document (eg OpenAPI) than its RPC analogues. > But it's also not the way any sane person has ever done RPC over HTTP. Why not? Aside: I’ve also used it as a format for internal calls and change tracking. It’s surely not ideal, but it was already in the service and trivial to repurpose. Turned out to be an extremely lucky decision, as it also became a crucial tool for forensic analysis in a major recovery effort. > I work in an industry of idiots. You could just… not be a jerk. You have that option.