Motivation:
An amazing hypermedia story for the JSON API (module) will provide a ladder for Drupal-based decoupled applications to reach a higher level of sophistication. This next generation of decoupled Drupal applications will have rich and domain-specific feature sets that go far beyond the typical "website". These applications are already beginning to be built today, but they require close coupling to the backend because their possible interactions cannot be communicated via an API in a generic and standardized way.

We first allowed presentation to be decoupled from data storage, now we need operations on that data to be decoupled from its presentation.

Background:
Recently, @e0ipso shared this link: http://gtramontina.com/h-factors/ which highlights JSON APIs poor hypermedia support. At the same time, I've been thinking a lot about hypermedia recently.

We've not even begun to scratch the surface for what excellent hypermedia controls could do for APIs built with JSON API. We're exploring that space a bit in #2795279: [PP-2] [META] Revisions support, but it's still a fairly static implementation not extensible by the applications built on top of this module.

The only control we already provide is fixed across all instances of JSON API and enables only one, well-defined use case. Pagination.

Means:
The next level of support that we can provide is to allow developers to define and customize their own links between JSON API resources. Every API is built for a different purpose and every API needs to serve specific business logic. To drive client-server interactions specific to those needs, our responses need to contain links specific to every application.

This will take time and will need to happen incrementally. Let's let this issue serve as a hub for those issue that improve JSON API's hypermedia developer story. I propose we begin writing this story of hypermedia support by building the infrastructure below and dog-fooding that infrastructure by migrating the 2.x version of revision support to it.

Components:
Things which I'm thinking about, most of which will probably become issues in the issue queue, in no particular order:

  1. Work with JSON API maintainers to get better link support/Use a JSON API profile to add better link support. While http://gtramontina.com/h-factors/ shows that the spec doesn't have great hypermedia support, it doesn't preclude it. The only real impediment that the spec places on us is that it limits link cardinality to 1 (although I've seen the spec maintainers speak optimistically about loosening this restriction).
  2. A custom URI scheme for our use.
  3. A customizable set of link relation types, defined under our own URI scheme.
  4. A customizable set of link attributes, defined under our own URI scheme.
  5. A link relation type for "anchor" links. When present, these links would use a JSON Pointer to link to other members of the response document. See #2993654: Provide JSON Pointer as a link on a resource to ease matching with included items
  6. A link relation type for adding resource identifiers to a relationship.
  7. A link relation type for removing resource identifiers from a relationship.
  8. A link relation type for replacing a relationship's resource identifiers.
  9. A link relation type for path aliases.
  10. The ability for other modules to add and remove links from various JSON API objects (to include resource, relationship and error objects as well as the top-level links object).
  11. A reference JavaScript client that understands these implementations.

Goals:
As a taste of what these kinds of enhancements could enable, here's a complex, juicy tidbit:

{
  "type": "product",
  "id": "1",
  "links": {
    "drupal://jsonapi/extensions/commerce/links/relation-types/add-to-cart": {
      "href": "https://example.com/order/4/relationships/items",
      "drupal://jsonapi/links/target-attributes/relationship-arity": 3,
      "drupal://jsonapi/links/target-attributes/operation": "drupal://jsonapi/links/target-attributes/operations/add-to-relationship",
    }
  }
}
  • The drupal://jsonapi/extensions/commerce/links/relation-types/add-to-cart URI is in reference to a custom link relation type that defines the semantics of the link as a URL for adding an item to one's own cart.
  • The drupal://jsonapi/links/attributes/operation URI is in reference to a custom link attribute that defines the link as a URL upon which a specification-defined operation can be performed.
  • The drupal://jsonapi/links/target-attributes/operations/add-to-relationship URI is in reference to a defined HTTP method and request format for adding a resource identifier to a relationship.
  • The drupal://jsonapi/links/target-attributes/relationship-arity URI is in reference to a custom link attribute that communicates the highest arity of any duplicates for the context resource object.

Another example might be drupal://jsonapi/links/target-attributes/operations/add-to-collection/schema, drupal://core/forms/metadata and drupal://core/forms/validator for a link defining the schema for an acceptable request document for an operation, a link defining metadata that describes how a form should be rendered by a frontend application, and a link from which a JavaScript file for validating a form submission could be obtained (yes, it's possible!).

"But those URIs are so ugly!"

JSON API v1.1 will likely contain the ability to assign "aliases" to these URIs. Using those aliases, my example could become:

{
  "type": "product",
  "id": "1",
  "links": {
    "add-to-cart": {
      "href": "https://example.com/order/4/relationships/items",
      "relationship-arity": 3,
      "operation": "add-to-relationship",
    }
  }
}
CommentFileSizeAuthor
#12 drupal.test_admin_content(iPad).png232.28 KBgabesullice
Support from Acquia helps fund testing for Drupal Acquia logo

Comments

gabesullice created an issue. See original summary.

richgerdes’s picture

I very much so like this and I am very happy to help move this along. Here are some initial thoughts.

"But those URIs are so ugly!"

+1, I agree and love the aliases, and unclear what the actual use of those urls are. I would argue that a well built api should be usable by a human (developer) as well. The complicated urls make it way harder to understand whats going on there. Granted you sometimes do need to provide more meta data in regards to this.

A link defining metadata that describes how a form should be rendered

I just opened #2994211: [Meta] Expose Form Metadata via Schemata to track this concept which has been stewing in my brain since earlier this year when I talked about form with the HAX team. I think there is a lot of potential for this, especially as a successor to the current form ajax system which could come out of implementing form building between jsonapi and schemata.

11. A reference JavaScript client that understands these implementations

I am very much for promoting a JavaScript client. As discussed at Decoupled Days, @gabesullice and I have both worked our own clients which understand jsonapi (and mine understands OpenAPI schema). If we can collaborate we should be able to do this well. Ultimately, I would want a client which doesn't depend on anything custom in our api. If we want to implement extensions to the spec, which can be "discovered" by the api, I think this needs to be done via something which is in (or almost in) the JSON API spec I think it would be good to work with the maintainers to outline this. Then as a result the client code should be able to work with any api service which provides the required information (such as OpenAPI spec for JSON API and the links to the api's extensions).

Wim Leers’s picture

I haven't read anything here yet, I just came to 😂😂😂😂😂😂😂😂 for the title! 👏👏👏

gabesullice’s picture

Issue summary: View changes

[I'm] unclear what the actual use of those urls are. I would argue that a well built api should be usable by a human (developer) as well. The complicated urls make it way harder to understand whats going on there.

I'm 100% with you on the API being "human-friendly". These aren't URLs they're URIs (i vs L). IOW, they're "Universal Resource Identifiers" and what they are is machine-readable, unique strings that identify codified meanings. Let'ts break one down...

drupal://jsonapi/extensions/commerce/links/relation-types/add-to-cart

This identifies a link relation type unique to Drupal, used by JSON API, but provided by the commerce module. Its name is "add-to-cart". If we had these, we would have a mechanism to discover and document the meaning of this link relation type. Its meaning would be "a link of this relation type is a link that tells the client how to add a resource to a user's cart". In my example, it would have a "human-friendly" alias of add-to-cart. Let's try another...

drupal://jsonapi/links/target-attributes/operation

This is a target attribute unique to Drupal, used by JSON API. It describes the link as one that provides an "operation" that the client can perform with the link. drupal://jsonapi/links/target-attributes/operations/add-to-relationship identifies that operation. That is, it denotes the procedure the client should perform. In this case, that the client should send a POST request to the given href using the context resource ({"type": "post", "id": 1}). These operations would be documented and could be "built in" with the JS client.

I just opened #2994211: [Meta] Expose Form Metadata via Schemata: [Meta] Expose Form Metadata via Schemata to track this concept

See also: https://github.com/jsdrupal/drupal-admin-ui/issues/262

I would want a client which doesn't depend on anything custom in our api.... Then as a result the client code should be able to work with any api service which provides the required information (such as OpenAPI spec for JSON API and the links to the api's extensions).

Amen. This is probably the lowest priority thing for me at the moment though. I don't think a client can be built that has all those automations until we've formalized the server side of things.

gabesullice’s picture

Issue summary: View changes

Renamed every instance of action to operation.

gabesullice’s picture

Issue summary: View changes

Added a child issue.

e0ipso’s picture

I'm sorry it's taking me so long to find the time to read this. I know I align with most principles that Gabe has shared about this in the past. I have been trying to timidly promote them myself, so I have confidence I'll be in line with most of this.

I am worried (before I read on) about the complexity this brings in. I'd like to float the idea to move this to a separate contrib.

gabesullice’s picture

I'm sorry it's taking me so long to find the time to read this.

Don't be sorry, it's FOSS after all :)

I know I align with most principles that Gabe has shared about this in the past. I have been trying to timidly promote them myself, so I have confidence I'll be in line with most of this.

❤️

I'd like to float the idea to move this to a separate contrib.

I see a lot of space here for other modules and so I guess we probably will agree on that sentiment.

Certain parts of this must live here though, like the core idea of adding/removing links from different places in the document in a thoughtful, spec-compliant and BC-safe way.

In my example, I showed an "add-to-cart" link. As I envision it, that link would not be generated by JSON API, it would be generated by something like a commerce_jsonapi or my_store_customizations module. To get that link into the response, that module would implement an event subscriber. And JSON API would be the one to fire the event. So, it would be JSON API's responsibility to aggregate and validate all those 3rd party links, but not to figure out which links to add/not add.

For instance, I'd like JSON API to ensure that any link relation types are in fact valid ones. Or, if two 3rd party modules provide links with the same URI but different link relation types, I'd like to merge those two links into one link with both relation types on it.

Finally, certain core related links should also probably live in JSON API. Just like we generate the self link for all resources. This issue might mean that we expand on that a bit and add the edit link relation type to the self link if the user has permission to update that entity, etc.

e0ipso’s picture

Certain parts of this must live here though, like the core idea of adding/removing links from different places in the document in a thoughtful, spec-compliant and BC-safe way.

I'm still finding this troublesome. While I wholeheartedly agree with most of what you wrote in the IS (I have read it now) I am not ready to agree with your use of must in the quote above. There are many things that are done in JSON API Extras that don't happen in JSON API via extension of internal APIs. There is nothing that precludes us from using that same solution.

In fact, I think we should start developing this in JSON API Extras (or JSON API Links) and then evaluate if we want to move this to JSON API. I see no strong reason against this.

gabesullice’s picture

In fact, I think we should start developing this in JSON API Extras (or JSON API Links) and then evaluate if we want to move this to JSON API. I see no strong reason against this.

I'm fine with prototyping much of this in a separate contrib. However, that raises the question, what internal API can it extend? I think that's the must I was referring to. I think the "right" API for that is an event subscriber used internally.

I would prefer not to put this in Extras because I fear it's becoming the equivalent of REST UI in that it's practically a necessity (perhaps only perceived) rather than a pure enhancement or proving ground.

e0ipso’s picture

I don't see it's a problem that JSON API Extras is very relevant. In fact I see it as a great sign. I agree that Extras is essential to any jsonapi site.

gabesullice’s picture

I don't see it's a problem that JSON API Extras is very relevant. In fact I see it as a great sign. I agree that Extras is essential to any jsonapi site.

I agree with all of this :) It is not a problem that JSON API Extras is very relevant. Full stop.

What I think is a problem is if JSON API is not useful without Extras, and it's essentially an implicit dependency.


How does that impact this issue?

Let's say that the Admin UI Initiative wants to to show an "Edit" button on the content overview page and that the button must only be shown if hook_entity_access('update', $entity) is allowed. It would be poor UX to show that button and when a user clicks it, to then go to a forbidden page (or worse yet, show an error on save). How can they communicate the access results of that hook to the frontend in an elegant way?

The answer is of course hypermedia controls. Without them, they'll need to replicate access logic on the frontend or come up with some workaround that couples their implementation tightly to the backend.

Eventually, when their work is added to core, they'll only be able to rely on what's in core, where JSON API proposes to be. They won't be able to rely on JSON API Extras for that and so JSON API won't be useful to them in that scenario.

e0ipso’s picture

What I think is a problem is if JSON API is not useful without Extras, and it's essentially an implicit dependency.

It is useful, but not complete. This was like this from start by design. Adding one random piece (the hypermedia support) will not change this. Following that reasoning one could argue that we ought to merge FieldEnhancers into JSON API, otherwise the schema is wrong (yes, even with @DataType level normalizers) and that means that JSON API Extras is an implicit dependency.

I get that you are incredibly passionate about hypermedia controls, and I am convinced you'll do an incredible work on it. I don't think that JSON API is the right place to seed this work. I think it's totally possible that we'll move this in eventually.


Let's say that the Admin UI Initiative wants to to show an "Edit" button on the content overview page and that the button must only be shown if hook_entity_access('update', $entity) is allowed. It would be poor UX to show that button and when a user clicks it, to then go to a forbidden page (or worse yet, show an error on save). How can they communicate the access results of that hook to the frontend in an elegant way?

The answer is of course hypermedia controls. Without them, they'll need to replicate access logic on the frontend or come up with some workaround that couples their implementation tightly to the backend.

This is not relevant to the disagreement, meaning that we both agree on the importance of this.

Let's say that the Admin UI Initiative wants to to show an "Edit" button on the content

The Admin UI initiative is hardly a good representative of the use of the JSON API module.

Eventually, when their work is added to core, they'll only be able to rely on what's in core

We can talk at length about that. But to summarize, I have 0 problems merging this back into JSON API from JSON API Hypermedia when that's the last blocker for the Admin UI. Following that reasoning we'll need to get Schemata into core first. That'll be a challenge.

Wim Leers’s picture

I share @e0ipso's concerns.

I think that @gabesullice is saying that today it's impossible to even implement this in a contrib module like JSON API Extras (regardless of whether this additional linkage should be added by JSON API Extras or some other module). We've made changes in JSON API to enable certain features in JSON API Extras before. I'm fine with doing the same for this linkage functionality. But I agree with @e0ipso's main point: the bulk of the linkage functionality (the API that allows other modules like Drupal Commerce to add additional links) ought not to live in the JSON API module.

gabesullice’s picture

But I agree with @e0ipso's main point: the bulk of the linkage functionality (the API that allows other modules like Drupal Commerce to add additional links) ought not to live in the JSON API module.

@e0ipso and I came to a compromise in chat, which I hope you'll also agree with. JSON API will have classes for "link collections" (the parallel to JSON APIs "links object") and a "web link" (a value object for a link mirroring RFC8288's conception of a link). The normalizer for the link collection can then be overridden in a separate contrib which provides the extension points that something like Drupal Commerce could use.

mglaman’s picture

The normalizer for the link collection can then be overridden in a separate contrib which provides the extension points that something like Drupal Commerce could use.

What if two contrib want to implement this? Or what if Drupal Commerce provides one for "core", and Shipping needs to provide way? Does this scale?

I just took a peak at \Drupal\jsonapi\Normalizer\Value\EntityNormalizerValue::rasterizeValue and saw

    $rasterized['links']['self']['href'] = $this->linkManager->getEntityLink(
      $rasterized['id'],
      $this->context['resource_type'],
      [],
      'individual'
    );

I was hoping that we could leverage link relation types (core.link_relation_types.yml) and be able to add link templates to entities, and it could be generated that way.

So, for example, the Cart API would alter the Order entity definition and add

  "https://drupal.org/link-relations/add-to-cart" = "/jsonapi/cart/{commerce_order}/add"

The order now links to the add to cart route automatically because we've defined it as a link. But, I guess the problem is "how do you know which links should be present, build them all and execute access checks?" That seems like a performance problem.

e0ipso’s picture

#16 brings up the point of performance. When dealing with HATEOAS we incur in the danger to start adding stateful links to the response. If this happens it will impact cache hit ratios (and therefore performance) greatly.

gabesullice’s picture

#16

What if two contrib want to implement this? Or what if Drupal Commerce provides one for "core", and Shipping needs to provide way? Does this scale?

@mglaman, I think you may have missed a subtle point:

The normalizer for the link collection can then be overridden in a separate contrib which provides the extension points that something like Drupal Commerce could use.

I wasn't suggesting that modules all override the same normalizer. I was thinking that a module named jsonapi_hypermedia would override the normalizer once. From there, the jsonapi_hypermedia would dispatch events that 3rd party modules would subscribe to (like Commerce for example).

To help mitigate performance concerns, the event names could be specific to particular parts of the document and the events could carry a lot of contextual information so that subscribers can do as little processing as possible before escaping when they don't need to add links.

"Marking up" a document with solid linking will always have some trade-off between response size+speed and usefulness. It's up to the module maintainer adding links to be conscious of cacheing and it's up to the site builder to be conscious about what gets enabled.


#17:

It's not lost on me that adding support for hypermedia as the engine of application state means we'll be introducing stateful links ;). It's kind of tautological really.

I'm not sure what to conclude from that comment. I think you just mean that we should be mindful of this?

My thinking has always been that links need to be able to carry cacheable metadata, link providers need to be considerate of caching, and links should be optional feature of the response document.

Toggling links would be an excellent use case for negotiable profiles (they just landed in JSON API 1.1 last week). But before that, I think enabling or disabling jsonapi_hypermedia should be sufficient to get started with this.

In any case, I don't think performance concerns are a reason to postpone or hesitate on this feature.

mglaman’s picture

I think you may have missed a subtle point:

Whoops, I did.

In any case, I don't think performance concerns are a reason to postpone or hesitate on this feature.

Performance can always be fixed. To be honest, the lack of data in links now is a detriment to advanced use cases of the API where there are a lot of interactions in the application. When I use GatsbyJS to consume the JSON API output for an ecommerce app, I would love it if the data "just provided" the add to cart links for each variation once resolved.

e0ipso’s picture

It's kind of tautological really.

Technically you can have stateless responses that facilitate tracking state :-P, but yeah I know what you mean.

My thinking has always been that links need to be able to carry cacheable metadata

This will likely introduce cache contexts to the response, hence reducing cache hit ratios. This was my point.

I think you just mean that we should be mindful of this?

You are correct, we can fix this later. We just need to remember to revisit this at some point.

Wim Leers’s picture

It's not lost on me that adding support for hypermedia as the engine of application state means we'll be introducing stateful links ;). It's kind of tautological really.

Hah, I was thinking the same thing after reading @e0ipso's #17 :)

I'm not sure what to conclude from that comment. I think you just mean that we should be mindful of this?

My thinking has always been that links need to be able to carry cacheable metadata, link providers need to be considerate of caching, and links should be optional feature of the response document.

Exactly!

This will likely introduce cache contexts to the response, hence reducing cache hit ratios. This was my point.

These already exist. Pretty much every response already A) varies by the user.permissions cache context. Which other cache contexts do you fear would be added?

Wim Leers’s picture

#2995960: Add a Link and LinkCollection class to support RFC8288 web linking. landed! Which means the foundation for this now exists. @gabesullice already started experimenting with https://www.drupal.org/project/jsonapi_hypermedia, I think he'll be pushing that forward more soon.

I listened to https://www.drupaleasy.com/podcast/2018/12/drupaleasy-podcast-212-commer... a few days ago, where @bojanz and @mglaman go in some detail about their experience providing decoupled capabilities for https://www.drupal.org/project/commerce. I know that https://www.drupal.org/project/commerce_cart_api was also prototyped in JSON:API (at https://github.com/mglaman/commerce_cart_api/tree/jsonapi), but it was abandoned due to blockers. It'd be great to have the Commerce folks chime in and have a fast feedback loop :)

Wim Leers’s picture

In the add-to-cart example in the issue summary, how would the client know which HTTP method to use and what the appropriate body would be?

The concrete example is a product that could be added to the cart through this add-to-cart state change. If we're using the existing API, that'd indeed require PATCHing "href": "https://example.com/order/4/relationships/items" (see https://jsonapi.org/format/#crud-updating-to-many-relationships for why that URI makes sense).
AFAICT this means that the client needs to implement knowledge about this add-to-relationship operation, which would prescribe the use of PATCH plus a particularly formatted request body. Once the client implements that, it is effectively able to perform any state change that maps to adding to a relationship.

But what about state changes that do not involve modifying connections between resources, but that are effectively aliases for PATCH requests to the current resource, to improve developer ergonomics?

It seems a change-attribute operation could make sense — let's look at a hypothetical:

{
  "type": "node--article",
  "id": "835c2196-7a87-4bef-81a2-92574d5cdbc5",
  "attributes": {
    "title": "My awesome article that is published but not on the front page",
    "status": true,
    "promote": false,
…
  },
  "relationships": {
…
  },
  "links": {
    "unpublish": {
      "href": "https://example.com/jsonapi/node/article/835c2196-7a87-4bef-81a2-92574d5cdbc5",
      "attribute-name": "status",
      "attribute-value": false,
      "operation": "change-attribute",
    },
    "promote": {
      "href": "https://example.com/jsonapi/node/article/835c2196-7a87-4bef-81a2-92574d5cdbc5",
      "attribute-name": "status",
      "attribute-value": false,
      "operation": "change-attribute",
    }
  }
}

And the Content Moderation module then could add additional links such as:

{
…
  "attributes": {
    "title": "My awesome article that is published but not on the front page",
    "moderation_state": "draft",
…
  }
…
  "links": {
    "unpublish": {
… 
   },
    "promote": {
…
    },
    "send-to-legal-review": {
      "href": "https://example.com/jsonapi/node/article/835c2196-7a87-4bef-81a2-92574d5cdbc5",
      "attribute-name": "moderation_state",
      "attribute-value": "legal_review",
      "operation": "change-attribute",
    }

  }
}

Does this fit with how you see this working?

What is reading material you would recommend, that led you to make this concrete proposal?

e0ipso’s picture

Given that JSON:API's hypermedia support is so poor that we need to create a profile for it, should we model them after solid patterns like https://rawgit.com/uber-hypermedia/specification/master/uber-hypermedia....? That will help with the reinventing the wheel problem.

gabesullice’s picture

In the add-to-cart example in the issue summary, how would the client know which HTTP method to use and what the appropriate body would be?

This is why my proposal is mostly about link relations. It's the link relations that define the semantics of the link, which can encompass everything from the which methods are acceptable to what data to send.

Since I wrote the issue summary, my thinking has changed a little bit and the example is outdated.

I see the link being more like this:

{
  "type": "product",
  "id": "1",
  "links": {
    "add-to-cart": {
      "href": "https://example.com/order/4/relationships/items",
      "rel": [
        "https://jsonapi.org/profiles/drupal/actions/#add-to-relationship",
        "drupal://jsonapi/extensions/commerce/links/relation-types/add-to-cart/"
      ],
      "params": {
        "arity": 3,
      }
    }
  }
}

The above is using my JSON:API links proposal.

We see that add-to-cart is no longer a link relation, just a string. Instead, the link has two link relations:

  1. https://jsonapi.org/profiles/drupal/actions/#add-to-relationship
  2. drupal://jsonapi/extensions/commerce/links/relation-types/add-to-cart/

The first, is a link relation defined by a yet-to-be-written profile. It would be defined like so:

This profile establishes the extension link relation https://jsonapi.org/profiles/drupal/actions/#add-to-relationship (henceforth, only written as add-to-relationship for readability). A client application processing an add-to-relationship link MUST use the HTTP POST verb when issuing any requests to the link's href URI. Additionally, the client MUST send a request document containing only the resource identifier object for the context object—or the resource identifier object for the object identified by the link's anchor parameter, if one is defined—as its primary data.

I didn't mention arity, just to keep it simple here.

The drupal://jsonapi/extensions/commerce/links/relation-types/add-to-cart/ relation would not be defined by a profile, but would reside in a namespace that lets any Drupal module define its own relations. We could define a docs page for these. Essentially, the add-to-cart would just "piggy-back" on the add-to-relationship relation. It's just there to disambiguate this particular add-to-relationship link from another one. It says, this link would add an item to a users cart. Maybe there would also be an add-to-wishlist link with exactly the same operational semantics (POST, etc.) but which has different meaning for this e-commerce site.

But what about state changes that do not involve modifying connections between resources, but that are effectively aliases for PATCH requests to the current resource, to improve developer ergonomics?

You're spot on! I think the same actions profile could define a new link relation, https://jsonapi.org/profiles/drupal/actions/#update-attributes:

This profile establishes the extension link relation https://jsonapi.org/profiles/drupal/actions/#update-attributes (henceforth, only written as update-attributes for readability). A client application processing an update-attributes link MUST process it according to the semantics for updating resource attributes. The client MUST send a request document containing a resource object with a type
and id value equal to the the resource object of the link's context as its primary data. Additionally, the resource object MUST contain exactly the same object as is given by the link's attributes parameter as the resource object's attributes object. Finally, the client MUST not attempt to update any other values while processing the link.

So, a publish link would be represented like so:

{
  "type": "node--article",
  "id": 42,
  "links": {
    "publish": {
      "href": "https://example.com/jsonapi/node/article/42",
      "rel": [
        "https://jsonapi.org/profiles/drupal/actions/#update-attributes",
        "drupal://jsonapi/extensions/core/links/relation-types/publish/"
      ],
      "params": {
        "attributes": {
          "status": 1,
        }
      }
    }
  }
}

If the client processed this link as I defined it above, it would know it must be a PATCH because that's what the spec defines for updating attributes. The context object is node--article:42, so it would know that's the data it should send. Finally, it would jsut directly set the attributes member with the provided object. You'd get:

PATCH /jsonapi/node/article/42

{
  "type": "node--article",
  "id": 42,
  "attributes": {
    "status": 1,
  }
}

Because of the way JSON:API only updates the fields that are provided in a PATCH request and leaves others unchanged, this is super powerful and simple for a client to implement.


should we model them after solid patterns like [UBER]

Honestly, I haven't looked at that spec, so I can't answer completely.

In general, I think I want stay as faithful to RFC standards as possible. Using link relations and the web linking spec for as much as they'll give us. IMO, simplicity is the name of the game. I want to avoid creating (or using) anything that tries to do all the things, in favor of something that is leap forward in capability but also incrementally adoptable and familiar. IOW, it's better to have something with less features if it means more people will use it.

I realize that might read as me being super negative about UBER, but it's not meant to be! Like I said, I have not even read it. It might be exactly what I'm looking for; I'm just laying out a framework for evaluating any possible solutions :)

Wim Leers’s picture

WRT Drupal Commerce + JSON:API: https://spree-guides.now.sh/api/v2/storefront/#operation/Completed%20Use... is an e-commerce platform providing JSON:API support.

gabesullice’s picture

@Wim Leers asked me to summarize where this issue stands...


Since I wrote the issue summary, we've made lots of progress. Less than I would have hoped in the same time frame, but significant.

What happened related to hypermedia in the last six months (roughly, in no particular order)?

  1. #2997277: Place all URLs under the `href` key prepared us for a richer hypermedia feature-set w/o breaking existing clients.
  2. #2995960: Add a Link and LinkCollection class to support RFC8288 web linking. added an API-ready object for handling rich links and coupled links to the top-level object.
  3. #3033473: Clean-up: Remove LinkManager removed the deprecated API for links.
  4. #3032468: Compatibility with upcoming JSON:API 2.2 release: JSON:API Extras 3.4 piloted LinkCollections from a DX perspective.
  5. #3015438: Wrap entity objects in a ResourceObject which carries a ResourceType coupled links to resource objects.
  6. #2992836: Provide links to resource versions (entity revisions) showed how Links and LinkCollections can be used to provide version states. HATEOAS FTW!
  7. #2853066: Spec Compliance: Inaccessible collection/related resources surface errors: should be 200 with hypermedia + metadata converted meta.errors to be hypermedia focused.

What's left/impacted?

  1. #3036285: Add a \JsonApiResource\Relationship object to carry relationship data, metadata and a link collection. links need to be coupled to a concept of relationship objects. The top level and resource objects are already taken care of. I think that means we'll probably need a JsonApiResource\Relationship object. I envisioned the DX for this idea in this comment.
  2. #3036279: Clean-up: Use a LinkCollection for document omission links. we should convert omissions to a LinkCollection so we're not maintaining complicated deduplication logic in two places.
  3. #3032787: [META] Start creating the public PHP API of the JSON:API module: I think hypermedia is a great candidate for this issue. Links and LinkCollections are a nice DX for the API, but I think the mechanism by which we alter LinkCollections today (by decorating normalizers) will not scale. I think we need a plugin system.
  4. #2794431: [META] Formalize translations support: the "proper" way to provide translations is via an Accept-Language negotiation while using the hreflang target attribute of resource objects' self links. This would be very similar to #2992836: Provide links to resource versions (entity revisions).
  5. #2984628: Consider how to communicate allowed methods on resource object self links is in limbo.

What are the obstacles I see?

  1. github.com/json-api/json-api: Introduce RFC8288 Web Linking to v1.2: will make rich linking possible within the base spec. Otherwise, we need to author a profile.
  2. We need to standardize on a URI scheme. There's already some context about that in this issue. URIs came up again here: #2857730-48: Add a link to get only published entities in collections.

Update: Added some missing issue links.

Wim Leers’s picture

Thanks, that's very helpful!

gabesullice’s picture

Wim Leers’s picture

Category: Feature request » Plan
Wim Leers’s picture

Wim Leers’s picture

Project: JSON:API » Drupal core
Version: 8.x-2.x-dev » 8.8.x-dev
Component: Code » jsonapi.module
Status: Active » Postponed (maintainer needs more info)

@gabesullice, I believe that with the existence of https://www.drupal.org/project/jsonapi_hypermedia and even stable releases, this can now be considered "done"?

It'd be great if you could update this issue 🙏

mglaman’s picture

Should this remain open to most child issues which improve Hypermedia support in core?

Otherwise I would say close and document the contrib module.

Wim Leers’s picture

Exactly. But I'd like @gabesullice to follow up on all of the unclosed related issues. We don't want to leave loose ends.

gabesullice’s picture

gabesullice’s picture

gabesullice’s picture

Assigned: gabesullice » Unassigned
Status: Postponed (maintainer needs more info) » Fixed

@gabesullice, I believe that with the existence of https://www.drupal.org/project/jsonapi_hypermedia and even stable releases, this can now be considered "done"?

I think this issue has served its purpose and marking it "Fixed". Although I don't think the hypermedia story is completely fleshed out yet, this issue is no longer needed to plan/sequentialize/track fledgling hypermedia issues.

In the long term, JSON:API Hypermedia should be merged into the Drupal core JSON:API module to to finalize "the hypermedia story". I think that avenue is open to us. The PHP API is being expanded and the APIs pioneered by JSON:API Hypermedia are being validated by various modules in the ecosystem (see JSON:API Comment for one example).

Status: Fixed » Closed (fixed)

Automatically closed - issue fixed for 2 weeks with no activity.