Proposal for RESTful entity API

We encourage users to post events happening in the community to the community events group on https://www.drupal.org.
Crell's picture

The other week, I posted a call for feedback about the viability of using PUT for Drupal entities as part of a REST implementation. We had a number of concerns, relating to how hooks and revisioning impact the idempotence requirements of PUT.

We had a fair bit of feedback and some good discussion, and even some input from the co-editor of the HTTP 2.0 specification, Julian Reschke. In short, hooks are not a problem, revisions are not a problem, forward-revisions are a problem we can work around.

Based on that discussion, ongoing discussions elsewhere, and a few offline conversations, I propose the following strategy for handling REST operations on entities in Drupal 8.

Disclaimer

For the purposes of this example, I will be talking about nodes. The same logic applies to any other entity that is revisionable (or not), but it helps to have a concrete example. Also, from a pure hypermedia perspective all URIs are irrelevant opaque strings that are subject to change, so paths don't matter. Humans care about paths, though, and I need something for examples, so for the sake of discussion please just ignore that detail for now. It doesn't affect the discussion at hand. (I'm not certain we'll be able to pull off fully buzzword-complaint hypermedia in this round anyway, although I'd like to.)

I will also reference "in JSON" below a lot, by which I mean "JSON-LD in the format we're developing for Drupal entities, or any other appropriate format, including XML-based, that people decide to support other than HTML, because HTML pages and REST just don't mix." (See why "in JSON" is easier to type?)

Easy details

First off, some conclusions based on the above-referenced conversations that make life easier:

  1. Logging and other "incidentals" do not break the idempotence of PUT.
  2. Hooks that modify "something else" do not break the idempotence of PUT.
  3. Hooks that modify the object being PUT are out of our control, so if some contrib module decides to break the contract of PUT that's not our problem.
  4. Recording the last-updated time of an entity counts as an "incidental". We're good there.
  5. Per the HTTP spec, incidental changes that cause some other IRI to change are OK. (This is very important.)
  6. The creation of an old revision of an entity, with its own IRI, does not break idempotence, provided that following a PUT to that IRI with a GET will return the same object that was just PUT. (That is, forward revisons are tricky but resolved below.)

The plan

All entities have a canonical path, in the form /node/{node} where {node} is a site-specific ID. That canonical path is the entity's RESTful ID, aka URI.

Another concept that has been kicked around a few times is offering more than one forward revision line of work. Effectively, it should be conceptually possible to do "branching" of an entity, with different people working on different, alternate versions of an entity. Think git-style branching, where each revision (vid) of a node equates to a Git commit object, and a "branch" is simply the label of a given revision. Each revision tracks its parent revision, which is a simple ID. Poof, you have multiple forward branches. I have discussed this concept with Workbench Moderation maintainer Steve Persch (stevector) at length this year, and we've concluded that it would be fairly straightforward to implement braching but merging would be very difficult, perhaps impossible.

I am not proposing that we adopt branching forward revisions right now. That is almost certainly a Drupal 9 question (or a really cool Drupal 8 contrib). However, I mention it because we can and should design our REST structure to allow for easy extention to such a concept. It also helps to clarify how revisions work in this model, as our current single-line-of-revisions is then simply a degenerate case and it makes it easier to explain.

Additionally, many many people suggested it's time we bite the bullet and just get rid of the concept of toggling revisions on and off. There is always a revision. Period. That is, switch to CRAP, not CRUD. I completely agree. For that reason, this is modeled on a CRAP design. CRUD is trivial to implement as a special case of CRAP by following "Create revision" operation with a "Delete old revision" operation. Even if we don't switch Drupal 8 proper to that model, the REST API should be designed around it for simplicity and future-proofing.

IRI GET PUT DELETE POST PATCH
/node/{node} The active revision in JSON 11 Delete the node and all revisions X 12
/node/{node}/revision/{vid} That revision in JSON X Delete that one revision X X
/node/{node}/branch/{branch} (eg, "draft") 1 2 3 X 4
/node X X X Create a new node/revision, return new IRI X
/node/{node}/revision/{vid}/active 5 6 7 X X
/node/{node}/branch/{branch}/active 8 9 10 X X

Legend

(Some of these are out of order because I didn't want to bother reordering them yet. That will get done later.)

X
Explicitly disallowed
1
The most recent revision on that branch, in JSON.
2
Create new revision (which implies a new /node/{node}/revision/{vid} gets created), and set /node/{node}/{branch} to refer to that revision. That means a subsequent GET will return the just-created revision. This is also almost identical to the mental model of a git branch. If that branch does not exist at the time of the PUT, the branch gets created on-the-fly branching off of the current active revision (subject to access control checks, of course). Returns HTTP 204 No Content.
3
Delete branch. This may or may not also imply deleting the revisions of that branch. I think for now we should not allow this operation, since core will have only a single branch, but we can leave this open for contrib elaboration.
4
Identical to PUT, except that the newly created revision is the combination of the previous revision specified by this branch and the body of the PATCH. It is very likely core won't support this, but contrib may do so. We're just defining what the full API would look like if someone wrote it. Returns HTTP 200 with the just-created revision in JSON.
5
HTTP 204 No Content if this revision is currently the active revision (aka, would be return by GET /node/{node}). HTTP 404 Not Found otherwise. Effectively this means the "active" subpath exists only if that is the active revision.
6
Sets that revision as the active revision. Returns HTTP 204 No Content. Also implies that /node/{node} now returns the same response as this IRI. Also implies that all other /active IRIs for the same node go away and now return HTTP 404 Not Found.
7
Make this revision not active. IF this was the active revision, returns HTTP 204 No Content. By implication it means that /node/{node} now returns 404 Not Found, or maybe 403 Access Denied. IF this was not the active revision, returns 404 Not Found.
8
Identical to point 5, for whichever revision this branch refers to.
9
Identical to point 6, for whichever revision this branch refers to.
10
Identical to point 7, for whichever revision this branch refers to.
11
Create a new revision and set it to be the active revision. Effectively identical to PUT /node/{node}/branch/draft followed by PUT /node/{node}/revision/{vid}/active. If forward-revisions are enabled, this will return a 403.
12
Identical to point 11 (PUT), except the saved revision is the combination of the previous revision active revision and the body of the PATCH. Returns the just-created revision in JSON. Again, unlikely to have core support but that's fine.

Explanation

Important to note here is that there is no CRUD support. There is node revision CRUD support, which by implication means CRAP for nodes. That is very much by design. If you really want to you can emulate it by adding a delete-old-revision hook to the revision-save operation. But really, let's just make the REST API "revisions are enabled only".

Also, I will say again that I am not proposing that we support git-style revision branching in Drupal 8 core. I am suggesting that we consider single-line forward revisions as a degenerate case of a more robust system, which add more fanciness in contrib at our leisure.

There is also, of course, distinct access control on each one of these possible operations. That can be sorted out later. Several people have also asked what happens if field-permissions are in play. After some discussion off-thread, the general consensus came down to "if you enable field permissions, PUT stops working and returns 403s, and you can deal with the consequences." Really, that's all we can do without changing the semantics of PUT, which we can't do.

Also of note, I don't anticipate PATCH to be supported in core at all. We're just carving out how it should work, and leaving it to contrib to implement.

For full HATEOAS/hypermedia support, I recommend following Lin's suggestion and not putting links in the body of the JSON-LD (or Atom, or whatever other format) but rather putting them in HTTP headers. That allows us to be consistent across different data formats, and also means we don't need to deal with links in the content itself.

Not addressed here is resource collections; vis, resources that are by definition just a list of other resources, like a node list. IMO, there should be no global list. Rather, creating context-specific useful lists should be the responsiblity of Views, which can put a properly-referencing contextually meaningful collection at whatever the heck IRI it wants. That's just a matter of providing the appropriate Views plugins, and then pushing buttons.

Although I'm talking about nodes here, any data entity can support this same model. Those that don't have revisions would instead implement PUT on their main IRI as above, and ignore the rest. That includes even config entities, which introduces all kinds of exciting possibilities.

Since we're heavy into development and on a short-timeline, I'd like to timebox this discussion to Friday 2 November. We can tweak until then, but after that we start coding.

Comments

Hypermedia

cosmicdreams's picture

If like me you were looking for a formal definition of what "Hypermedia" means in this context, here ya go: http://martinfowler.com/articles/richardsonMaturityModel.html

Software Engineer @ The Nerdery

One potential problem you'll

wjaspers's picture

One potential problem you'll find when creating this service layer is that application firewalls and service providers may alter the HTTP headers as they are exchanged. Even though the HTTP spec outlines which headers should be left untouched, which should be forwarded, and so on, not all parties en-route behave as they should. The end result are services that don't behave exactly as expected.

IMHO its GOOD to implement the whole spec (POST, PUT, PATCH, etc...), and put an extension or module layer on top of this to handle any wackiness that emerges.

The other major issue is that you'll need to generate media types, which to my current understanding, could be very complicated (in Drupal's case). A node is not a node is not a node is not a node. (That is, each node and each type serve different purposes from site to site.) How would adding this service layer properly handle the variation in content types?

This is a very exciting endeavor!

Would this aim for a full HATEOAS implementation? (Hypernedia as the Engine Of Application State).

How about: /node/x/revisions

Mile23's picture

How about: /node/x/revisions GET returns a list of revision IDs, maybe with an in-the-last-x-period option, so: /node/x/revisions/date-or-timestamp

Semantic hot water comes when I have permission to view and revise a node but not a specific field in that node -- GETting the node gives me the node minus the field, but if I PUT a new revision the field vanishes. No round trip, violating REST. Or I have permission to revise a node but not edit a field, similar problem.

I suppose wanting to edit a field I have permission to edit while I don't have permission to edit the node could result in a new revision. Right? :-)

/node/add/{type} Do not

klausi's picture

/node/add/{type}

Do not duplicate operation ("add") and data body information ("type") in URL paths. This should simply be "/node" where you can POST new nodes to.

The discussion about the canonical path for all entities is handled in http://drupal.org/node/1803586 . The current proposal is "/entity/[entity-type]/[entity-id]", i.e "/entity/node/5".

/node/{node}/revision/{vid}/active

The "active" sub-resource of a node revision confuses me a bit. You send an empty PUT request to make it active? That sounds like a message-oriented approach. REST is about transferring representations of a resource. I suggest that you just POST to "/node/{node}/revision/{vid}" with a body of {"active": true} that gets merged into the node revision. Again: no need to expose operations in URL paths. Same for the branches.

Overall I think full revision support is a bit too much till feature freeze. I will push for the first row in your table including POST/PUT updates and creation. See http://drupal.org/node/1816354 for a start. Maybe we can convince people that revision support is not a feature but merely a necessity, so we would have more time to work on this until code freeze :-)

Hm, reading about operations

klausi's picture

Hm, reading about operations in the "REST API Design rule book" there is only a rule regarding CRUD: "CRUD function names should not be used in URIs". So "/node/add/{type}" is definitively not good, but "/node/{node}/revision/{vid}/active" might be fine, as it is not a plain CRUD operation. Still, the resource action might be better described as "activate" for writing. Fore reading I think we want to do collection filtering, which should be done in query part of the URL. Example "node/5/revision?active=true". "A URI’s query component is a natural fit for supplying search criteria to a collection or store."

Book: http://en.wikipedia.org/w/index.php?title=Special%3ABookSources&isbn=144...

Cool. So, something like this?

mitchell's picture
Operation VERB Destination
create POST /{entity-type}
read GET /{entity-type}/{id}
update PATCH /{entity-type}/{id}
delete DELTE /{entity-type}/{id}

Just another comment in

rszrama's picture

Just another comment in support of Klaus's observation, the verb shouldn't be in the URL but in the method. I suppose this particular URL (the node/add/{type} URL) could've been what Larry mentioned was purely representative for the sake of the discussion.

To create a new node or revision without knowing the ID in advance, I'd expect us to POST to the collection resource. (I'll provide a standalone thread here in the comments for collection resources. : ) So if I want to add a new node, I'd POST to node. If I want to add a new revision without knowing the vid in advance, I'd expect to POST to node/{nid}/revision(s).

Revision Diff / PATCH-ing

mitchell's picture

I agree with using the message body this way. For REST module: PATCH/update I described a PATCH request as containing the diff payload in the body.

Diff module's current uri scheme is '/node/{nid}/revisions/view/{vid:1}/{vid:2}' , but I think it could become a GET to '/node/{nid}/' of {"vid":1, "vid":2}'.

Also, revision, branch, draft, activate, etc all seem to have this option of using data encoded in the uri or using the message body data object. I don't know when one should be chosen over the other, but the message body would seem to allow more complex data and use of TypedData API on the fields of the acted upon entity.

Exposing routes to operations might also be discussed in Various subsystems require an Entity Operations API. Introduce one.</a

Small nitpick, the current

mradcliffe's picture

Small nitpick, the current terminology for "active" is "default revision" from 218755: Support revisions in different states

Semantic hot water comes when

linclark's picture

Semantic hot water comes when I have permission to view and revise a node but not a specific field in that node -- GETting the node gives me the node minus the field, but if I PUT a new revision the field vanishes.

fago also pointed this out in another thread. Something similar is covered in REST in Practice (available through Safari Books Online).

In REST in Practice, the example found in section 5.4.1 uses POST instead of PUT. The reasoning for this is that "With PUT, the state encapsulated by the incoming representation will, if legal, wholly replace the state of the resource hosted by the service. This obliges a client to PUT all the resource state, including links, as part of the representation it sends." The idea that links are part of the resource state was surprising to me. I assume it is because the example uses XML and includes the links in the payload, rather than as HTTP Link headers.

However, in Drupal fields are clearly part of the resource state. If the book's interpretation of the semantics of PUT is correct, then I wonder whether we can use PUT at all, considering that we allow field based permissioning.

The book does mention that PATCH would be an appropriate verb, but that PATCH only recently reached RFC and that it isn't widely supported at time of writing. It looks like the PATCH RFC is dated March of 2010. Does anyone know the current status of support? Would we consider supporting PATCH in core rather than PUT?

In REST you exchange

lanthaler's picture

In REST you exchange representations of resources, not the resources itself and the methods you use express the client's intent not what the server implements. In this case the clients intend is quite clear: "replace the resource with this representation". This maps directly to PUT.

The cleanest solution would probably be to make the client submit all fields, including the ones it has no permissions to edit. The server would then just ignore them or return an error if read-only fields haven been modified.

There's a very clear post by Roy on this (specifically talking about versioned resources):

Just ignore the definition of idempotent in RFC 2616. Anything
specified in HTTP that defines how the server shall implement the
semantics of an interface method is wrong, by definition. What
matters is the effect on the interface as expected by the client,
not what actually happens on the server to implement that effect.

Markus Lanthaler
@markuslanthaler

Thanks, that does clarify one

linclark's picture

Thanks, that does clarify one question I had been thinking about yesterday, particularly regarding the data that we won't have in ld+json (as opposed to vnd.drupal.ld+json), like a text field's format.

However, we unfortunately still have the field permissioning problem I believe. These fields might not be permissioned as read-only... the client might not have permissions to view them at all, in which case the JSON that they GET before PUTting would not contain the fields.

Would it violate the semantics of PUT if we merge in field values from the existing entity for those fields that the client do not have permissions to view?

No that won't be an issue. In

lanthaler's picture

No that won't be an issue. In that case it's a full representation from the client's point of view and its intent is to replace the existing resource with the one it supplied.

If it wouldn't be that way, also an alternative media type wouldn't solve the problem as it is just a different representation of the same resource.

Markus Lanthaler
@markuslanthaler

Just found a related

linclark's picture

Just found a related discussion from 2009 on Roy Fielding's blog, It is okay to use POST.

This discussion also touches on the kind of sub-resource that klausi is discussing above.

This + JSON-LD = list of allowed web services?

cosmicdreams's picture

When this idea is combined with JSON-LD's implementation is it possible to ask the web service for a list of legal actions. Either a Web Service Desicripnt or something like it?

WSDL 2.0 landed in 2008 with support for REST services. see:
http://en.wikipedia.org/wiki/Web_Services_Description_Language#History
http://www.ibm.com/developerworks/webservices/library/ws-restwsdl/

I'm starting to think we could provide this kind of functionality in contrib. Something like xmlsitemap for the web service. That only makes sense if the web service is something that will vary based on the application's configuration.

on irc, mitchell also pointed me to another of issues where this conversation started up, that I've now can't find. Can others help me get those posted here for cross reference?

Software Engineer @ The Nerdery

The idea would be for

rszrama's picture

The idea would be for resources and actions to be discoverable within the response message of an API request. This is where hypermedia comes in (as you linked above): I can GET a list of nodes and in each item find the URI to GET the complete node. Or perhaps I might receive with the list of nodes a template to submit a follow-up query to the same API endpoint to filter / page the nodes returned. On a node, I may have form controls that provide me with all I need to know to submit a follow-up API request to update or delete the node. There would likely be a link in there, too, to a list of the nodes revisions.

On and on it goes. The discovery isn't done through some meta document but rather as you traverse the API, much like you would traverse a web page. (I know plenty of people who would take issue with Larry's ruling out HTML as a good media-type for REST APIs. ; ) You go to the front page of d.o and see links to collections of resources ranging from documentation pages to project pages. On each step of the way, you know to look for search boxes, HTML links, issue creation forms, etc. A REST API would have some way of identifying these things to the consuming application.

That said, you won't have to do this traversal every single time you use the API. You can cache links along the way, creating the equivalent of bookmarked pages in your API. Just gotta make sure we respect the cache header when it's sent from the API server.

I think a better approach to

bendiy's picture

I think a better approach to WSDL 2.0 (Which is XML based) is Google's API Discovery Service (Which is all JSON):
https://developers.google.com/discovery/
http://youtu.be/lQbT1NrxpUo

It also gives you a HATEOAS API that is truely RESTful.

A separate API you call to

Grayside's picture

A separate API you call to understand your current API is not very REST-friendly. An extension of self-documenting which I've been exploring in concept lately is to make far richer use of the OPTIONS method.

It would be neat if we had resources annotated in such a way that contrib could respond to OPTIONS requests and render it out based on an Accepts parameter. Then WSDL, WADL, Guzzle Service Description, Swagger service description, etc, could be scraped out of the API itself.

Been writing up a blog post on this, but it's taking longer than this thread will likely be topical :)

Following the "best RESTful

mike503's picture

Following the "best RESTful practices" presentation[1] and (IMHO) excellent guidelines, there should be no verbs in the URL. so technically /node/add/{type} should be a POST to /node/{type} with no "add" parameter.

1) http://www.oscon.com/oscon2012/public/schedule/detail/23508

side-note: I do appreciate

mike503's picture

side-note: I do appreciate GET/POST only as it is easier to debug, send one line curl requests and such, and has key/value pair semantics. in which case, you would need the "action" verb (add, update....) in the URL or as a parameter.

URLs identify resources not actions

lanthaler's picture

there should be no verbs in the URL...

Right, URLs identify resources, not actions. So, even though URLs are considered as being opaque, i.e., they have no meaning other being an identifier, it is considered a bad practice to include verbs.

side-note: I do appreciate GET/POST only as it is easier to debug, send one line curl requests and such, and has key/value pair semantics. in which case, you would need the "action" verb (add, update....) in the URL or as a parameter.

As Drupal is going to use Symfony's HttpFoundation you can use the _method parameter with a post to simulate the other request methods from a browser.

Markus Lanthaler
@markuslanthaler

PUT /node/{node} Why should

Owen Barton's picture

PUT /node/{node}
Why should this not be supported when future revisions are not enabled (i.e. this is a tracking branch in git-speak). Having future revisions is a pretty rare use case, but needing to update node content is an extremely common one, and when new revisions are immediately made active then this maps to how clients would expect a PUT to behave. My suggestion was:
- Forward revisions disabled: allow PUT /node/123, create a new revision and reference /node/123/revision/456 as an alternate URL (via rel in the HTML).
- Forward revisions enabled: if we get a PUT /node/123, respond with an access denied 403.3 (probably, although I could see a case for a redirect here instead), indicating that new revisions may be POSTed to /node/123/draft. On POSTing new revision, client is redirected to /node/123/revision/457 (or whatever the ID is of their revision).

I agree with klausi that /node/add/{type} should be replaced with a simple POST to /node (with the content type as part of the submission). I could also go with a POST to /node/{type} I guess, but /add/ is just redundant.

What about collection resources?

rszrama's picture

I didn't read anything in this plan (or I believe klausi's earlier post) about collection resources: i.e. what will we return from /node or /user or /node/{nid}/revision(s), and what hypermedia controls does JSON-LD afford us to provide query templates and forms in these responses?

So perhaps I'm missing the discussion, but is there talk anywhere about listing nodes, paging through the list, filtering the shown results by properties / fields, or dictating the list of fields to return from a list? Or is all that just something we'll need to define ourselves via Views?

I'm implementing these for our resources in the Commerce Services resource pack, with the idea that every "index" request (a GET on the collection resource for an entity type) has a uniform way to list data, page through it, filter the results, and define a trimmed down result set (i.e. other than full entities). It's currently only implemented for product display nodes, and it follows the best practices recommended by Apigee in their Web API Design e-book (linked from the project page).

I think collections and

klausi's picture

I think collections and hypermedia controls are out of range till feature freeze and will be probably D8 contrib stuff. There has not been any discussion yet as far as I know, at least I want to finish CRUD on entities first.

Regarding Commerce Services: did you look at RESTWS? It already has index/querying support for any entity type + filtering + paging + full or reference only listing;-)

I haven't seem much

bendiy's picture

I haven't seem much discussion of it either and I believe it's key to having a truly RESTful API. The REST client should be able to discover all of this metadata from the hypermedia. That is why I keep bringing up Google's API Discovery Service.

Their API supports providing all of the information you are asking about.
Collections, Methods, Filtering results, a template schema for what to expect in a result how to format a response.

Regarding hypermedia

linclark's picture

Regarding hypermedia controls, I think we want to consider the IETF's Web Linking using HTTP Link headers, which I found out about via Crell's twitter stream. It was drafted by one of the same folks as the Atom syndication format. I would prefer that we expose the same hypermedia controls across serialization formats and move them into the HTTP header fields, rather than placing the them in the payload with the serialized entity.

Entity Revision Branches / PATCH method

mitchell's picture

Thank you, Crell. This is very cool stuff :-)

There's some architectural overlap here with Provide a better UX for creating & editing draft revisions, wrt the URL architecture for workflows on entities, as well as, client code capabilities for supporting PATCH.

Out-of-the-box workflow states for branching is also discussed in Support pluggable workflow states. I think entity revision states, as well as routes, both would have a lot more potential if done with pluggablility, but it doesn't seem like pluggability is planned for Replace or improve DX of hook_route_info(). (sad face)

I added an issue for Support PATCH method for entity updates, which AFAICT, would depend on supporting 'diff' on both ends, so Integrating Diff into Core might also need to include a JS library, in addition to a PHP one. (mentioned there too)

Revised

Crell's picture

Wow, you folks really don't like /node/add/{type}, do you. :-)

I've made various changes to the proposal above based on the discussion here and elsewhere. Changelog:

  • Changed /node/add/{type} to just /node, for creating new nodes.
  • Added support for PUT to /node/{node} in some circumstances.
  • Added support for PATCH on /node/{node} to mirror PUT.
  • Clarified implications of field-permissions on PUT.
  • Added recommendation for hypermedia support, using HTTP headers.
  • Added note for resource collections, aka "just use Views".

The numbers in the chart are a bit out of order, but I didn't want to be arsed to reorder them yet while we're still editing. When we're done we can tidy it up for posting elsewhere.

Moar feedback! :-)

As far as collections are

rszrama's picture

As far as collections are concerned, do you suppose the required Views plugins (or changes to existing code as the case may be) would be committable post feature freeze assuming the basic framework as described makes it in before?

Yes, thats our current plan.

moshe weitzman's picture

Yes, thats our current plan. We all really want collections in the Web Services API.

PUT +1

DevElCuy's picture

Thanks for making it truly RESTful!

--
[develCuy](http://steemit.com/@develcuy) on steemit

Webdav contrib

Dustin@PI's picture

Would we be using the same API for "File" Entities?

This is great ... this will make (re-) adding webdav via contrib a lot easier.

Not sure

Crell's picture

That's a tricky one. I'm tempted to say /file/{file}, with the same GET/PUT semantics, but the real GET for that should be the actual path on disk. So, not sure, really. I'm open to suggestions. :-)

We are talking about the file

klausi's picture

We are talking about the file entity here, so a representation of file metadata should be returned on GET (file owner, file URL, created date etc.).

That makes sense but how

Dustin@PI's picture

That makes sense but how would you use the API to update the File?

Potential options:
- multipart mime attachment
- separate URI to Put/Patch i.e. /file/{fileId}/attachment
- doing a put against the path for the file (not a big fan of that option)

I think /file/{file} (or

Dustin@PI's picture

I think /file/{file} (or {fileid}) is the way to go for the API while treating the actual path on disk more like how we treat aliases.

This would make it easy to support stream wrappers with remote storage, or where you save locally but want the actual file to be accessed via CDN.

DevElCuy's picture

Guess we can make it a bit more generic, because all endpoints use the same interface: Entity API, there are many interfaces for each type, when "type" is just a parameter.

What about making the endpoint just "/entity/{id}"?

Then send the type in JSON. Also, what happened to the use of QueryStrings? URL params are part of HTTP. Guys, you turn off your SEO chip when designing web services! Guess you mind about the DELETE and others HTTP operations, so let's do this:

POST => /entity
JSON {type: "node", title: "New node"}
RETURN <= {id: "4", type: "node", title: "New node"}

DELETE => /entity/4?type=node

OR

DELETE => /entity/node/4

Whenever you cant submit DATA (the JSON body), you can use QueryStrings or just add type as a parameter. Just dont start adding more and more parts to the URL, that is why there exists a QueryString.

The reason why REST exists is not to have beautiful clean-URLs, but to provide visibility on what the client is attempting, that way you have fine control over operations, as deep as you can by parsing the URL endpoint AND QueryString (URL params as in HTTP, not params as in the old Drupal). So, when you design the endpoints, keep in mind what you want to see in order to control/debug/track/log/etc it.

My 2c.

--
[develCuy](http://steemit.com/@develcuy) on steemit

I can see where you're

mike503's picture

I can see where you're confused. RESTFul URL structure is not for SEO purposes at all. It's short for "Representational state transfer" and describes an entity (not a Drupal entity, but the concept of "something") It wasn't thought up that way to make it "SEO friendly" it just happens to look like most applications have started to design their URLs. It's not REST services that are trying to look like SEO friendly URLs, it's actually the applications/publishing content systems and such that morphed into SEO friendly URL structure. :)

It might be helpful to have a

Mile23's picture

It might be helpful to have a use-case for people to hang their hat on. What problem is being solved here?

I haven't been following WSCCI very closely, so: Can I just subclass something and roll my own policies for a callable API?

Uniform API

Crell's picture

There's lots of inter-connected goals for WSCCI. In this particular case, the goal is to have a uniform, common API for Drupal sites so that you can read and update resources on one Drupal site from something other than a web browser, which mayor may not be another Drupal site.

Think Android apps; think content staging (deploy module); think content syndication; think single-page applications that call back to the Drupal site in Ajax. Now think of all of those being written once, not once for every separate Drupal site.

The idea is that you wouldn't "roll your own policies" for reading/writing nodes on your Drupal site. These are the policies, defined by HTTP itself already. You'd just twiddle the access controls for your use case; the API itself is universal, which makes it interoperable.

I apologize for not being as

Mile23's picture

I apologize for not being as familiar with WSCCI as I could be, and this being a bit off topic. But judging by what you say here, I guess the answer is no, it's not modular, there will be one Drupal REST API provided by WSCCI, and in order to provide other APIs without re-writing everything you'll need Services.

I'm working on rest.module

klausi's picture

I'm working on rest.module for Drupal 8 core which will allow to easily add resource plugins in contrib. You can find one example of exposing watchdog() log entries: http://drupalcode.org/sandbox/klausi/1815566.git/blob/refs/heads/8.x-1.x...

Just define the plugin class with the annotation and the HTTP methods you want to react on. rest.module will generate routes + permissions + a UI to enable/disable the resource for you.

There is not much left for services.module to do in Drupal 8, maybe someone wants to continue the RPC-style stuff, which will definitely not be in Drupal 8 core (REST only).

Keyspace

Crell's picture

First, as soon as you say "endpoint" you're not talking REST. There is no such thing as an "endpoint" in REST, AFAIK. That's an RPC thing.

Different Resources have their own unique URIs, that are, from an HTTP perspective, opaque strings, including GET query values. The actual structure of the URLs we're using here is for human-friendliness, not machine-friendliness. To HTTP, /entity?type=node&nid=5 and /node/5 are equally meaningful. They are not equally meaningful to humans. Humans seem to prefer hackable URLs to a bunch of query values. Go figure. :-)

That said, we cannot easily fold everything into a single base path for two reasons. One, the keyspace. node 5, term 5, user 5, and comment 5 are all different things, and that cannot change without a massive change of all of Drupal that breaks data backward compatiblity. Two, our new router doesn't do routing based on GET parameters, only on the path itself. So we couldn't actually route properly anyway if that were the case.

Crell, Doesn't using UUIDs

mukhsim's picture

Crell,

Doesn't using UUIDs solve the keyspace problem?

Workaround for backward compatibility issue can be addressed using Pathauto which I believe is making into core.

IMHO, it is a lot more elegant to have a consistent API in the form of

/entity/{uuid}

rather than

/node/{nid}
/term/{tid}
/file/{fid}

Think of all the apps that have to retrieve Drupal "stuff", wouldn't it be easier if their code worked for any entity type?

"Just throw pathauto at it"

Crell's picture

"Just throw pathauto at it" is not a complete workaround, as discussed elsewhere. It also does nothing for the thousands upon thousands of sites that don't use pathauto now and don't want to.

The keyspace problem is also on lookup, however. nodes, users, terms, etc. are all stored in different tables. They could, in D8, even be stored in different databases, or some in SQL and some in Mongo and some in some 3rd party service. Given just a path of /entity/{uuid}, there's no way to know which data store to even look in. We'd need to add a mega-lookup table, stored, uh, somewhere, that has the translation keys for all entities of all types. That's self-defeating.

Really though, this isn't the place for the UUID discussion. There are already a half-dozen threads about it already in the issue queue.

UUIDs instead of NIDs

mukhsim's picture

Apologies if this is off topic, but what would it take to implement UUIDs instead of nids/tids/etc. From content staging/deployment/migration perspective, this could simplify things enormously down the road. Links to /uuid/[id] would work regardless of the order in which content has been created, node/term references would work (think of a view listing things in a specific category), etc.

There is sure to be some overhead, but benefits seem to be very compelling.

Related posts:
UUIDs are already in core: https://drupal.org/node/1637370
Replace serial entity IDs with UUIDs in URLs: https://drupal.org/node/1726734

Mukhsim.

+1 this is more future-proof.

mike503's picture

+1 this is more future-proof.

Disagree

Crell's picture

From a REST perspective, actually, UUIDs don't mean jack. The complete URI is the UUID. To REST, http://www.example.com/node/5 and http://www.example.com/entity/node/1234-5gfhdfg-345-dfgfgh are equally expressive.

There are significant challenges to switching to UUID in exposed URLs already, and I'm opposed to it for a variety of reasons discussed elsewhere. RESTfully, UUIDs would not buy us anything. The main place they'd be useful would be in Entity Reference fields, which is unrelated to this thread.

There are more perspectives

moshe weitzman's picture

There are more perspectives in play here than just REST. Content staging and other use cases are going to need to load by UUID and save by UUID all the time. If we don't let them do that, we are going to have to provide a separate UUID <=> ID lookup service and they will call that over and over and over. Might as well just avoid those chained calls.

Yep, I'd assumed that UUID

adub's picture

Yep, I'd assumed that UUID would always be the externally facing id. Seems like a leaky abstraction otherwise - why would a site's IA get frozen within the URI of a fragment?

I use UUID to keep nodes synced across sites

joshua.boltz's picture

I'm using custom Services resources so that, in addition to deploying the node object from Site A to Site B, I'm also able to migrate all referenced files (images, PDFs) and all referenced nodes (for entity reference fields).

I have everything migrating, and I even have the entity references created on Site B if they do not already exist, but the problem I'm running into that may be related to this topic, is when the node gets saved on Site B via the services PUT/POST, the newly created draft/new revision contains related nodes, such as Node A and Node B. But, the published revision also contains Node A and Node B, whereas it should only contain the references that were previously saved to the published revision when it was saved by the user on Site B.

Any ideas where my issue may be?