UNPKG

@coding-blocks/jsonapi-server

Version:

A config driven NodeJS framework implementing json:api

182 lines (152 loc) 4.78 kB
### Foreign Key Relations We want to be able to back our resources in any datastore we like, in any system we like. This means we can't rely on any database layer relationships to join resources. #### Problem: supporting back-links In other words, each resource would maintain its own linkage. Consider these resources: ``` Bookings have people: "HPABCDE": { owner: "Dave" } People have Bookings: "Dave": { bookings: [ "HPABCDE" ] } "Fred": { bookings: [ ] } ``` If I were to update the owner of HPABCDE to "Fred": PATCH -> /bookings/HPABCDE/relationships/owner then the dataset would look like this: ``` Bookings have people: "HPABCDE": { owner: "Fred" } People have Bookings: "Dave": { bookings: [ "HPABCDE" ] } "Fred": { bookings: [ ] } ``` Notice the reverse linkage between bookings and people is now broken. We could get around this by automatically forcing other update requests: ``` PATCH -> /bookings/HPABCDE/relationships/owner ---- becomes PATCH -> /bookings/HPABCDE/relationships/owner PATCH -> /people/Dave/relationships/bookings PATCH -> /people/Fred/relationships/bookings ``` All 3x requests combined will produce the desired end linkage: ``` Bookings have people: "HPABCDE": { owner: "Dave" } People have Bookings: "Dave": { bookings: [ ] } "Fred": { bookings: [ "HPABCDE" ] } ``` The number of requests vary on the relationship (1-1, 1-many, many-many, many-1). Consider now if one of the 3 updates fails - we need to rollback the other 2. We now have to do all of this: ``` PATCH -> /bookings/HPABCDE/relationships/owner ---- becomes GET -> /bookings/HPABCDE/relationships/owner GET -> /people/Dave/relationships/bookings GET -> /people/Fred/relationships/bookings PATCH -> /bookings/HPABCDE/relationships/owner PATCH -> /people/Dave/relationships/bookings PATCH -> /people/Fred/relationships/bookings [if one fails...] PATCH -> /bookings/HPABCDE/relationships/owner PATCH -> /people/Dave/relationships/bookings ``` There's also now a racehazard whereby two people try to update the same resource and mess up the relations. This is solvable by passing in a checksum to the PATCH requests, allowing us to identify if a resource has been remotely modified. The checksum would need to be returned as a part of the JSON:API formatted response, alongside "id" and "type". In a nutshell - all of the above is a terrible idea. #### Solution: we only support forward-links This: ``` Bookings have people: "HPABCDE": { owner: "Dave" } People have Bookings: "Dave": { bookings: [ ] } "Fred": { bookings: [ "HPABCDE" ] } ``` becomes: ``` Bookings have people: "HPABCDE": { owner: "Dave" } People don't know about their Bookings: "Dave": { } "Fred": { } ``` HOWEVER, we still want to maintain the reverse linkage, so we'll still offer up a linkage like this: ``` /rest/people/26aa8a92-2845-4e40-999f-1fa006ec8c63/bookings ``` although under the hood, we'll re-map it to something like this: ``` /rest/bookings/relationships/?customer=26aa8a92-2845-4e40-999f-1fa006ec8c63 ``` and query for `bookings` where `customer=?`. The main gotcha here is this in this situation: ``` Bookings maintain links to People People maintain links to Trips Trips maintain links to Bookings ``` Looking up a person creates a reverse lookup against Bookings, creating a reverse lookup against Trips, causing a reverse lookup against People, creating a reverse lookup against.... Our solution here is to add `meta` blocks to relationships to inform the consumer what kind of linkage they are looking at, and to not provide foreign keys directly: ``` relationships: { author: { meta: { relation: "primary", readOnly: false }, links: { // get information about the linkage - list of ids and types self: "http://localhost:16006/rest/comments/6b017640-827c-4d50-8dcc-79d766abb408/relationships/author", // get full details of all linked resources related: "http://localhost:16006/rest/comments/6b017640-827c-4d50-8dcc-79d766abb408/author" }, data: { type: "people", id: "ad3aa89e-9c5b-4ac9-a652-6670f9f27587" } }, article: { meta: { relation: "foreign", belongsTo: "articles", as: "author", readOnly: true, many: true }, links: { // get information about the linkage - list of ids and types self: "http://localhost:16006/rest/articles/relationships/?comments=6b017640-827c-4d50-8dcc-79d766abb408", // get full details of all linked resources (perform a search against the foreign key) related: "http://localhost:16006/rest/articles/?filter[comments]=6b017640-827c-4d50-8dcc-79d766abb408" } } } ```