- From: Melvin Carvalho <melvincarvalho@gmail.com>
- Date: Thu, 29 Oct 2015 17:09:13 +0100
- To: Ryan Shaw <ryanshaw@unc.edu>
- Cc: Hydra <public-hydra@w3.org>
- Message-ID: <CAKaEYhLAiYf_75uKaL9OQFABoFhwA-kceWmCECTqr3+WFdE-QQ@mail.gmail.com>
On 29 October 2015 at 16:32, Ryan Shaw <ryanshaw@unc.edu> wrote: > We have an API for managing scholarly notes. The API is organized as > follows: a scholarly project has a collection of notes associated with > it. Projects may have multiple users, each of which may have different > role-based permissions to do things with the project's notes. The > names and permissions of these roles are project-specific. Users may > belong to multiple projects and have different roles in each. > > One of the main reasons we chose to use Hydra is to help clients > understand what they are permitted to do with the various resources > they have access to. So, our entry point to the API should show an > authenticated user links to the note collections of all the projects > with which the user is affiliated, and the client should be able to > determine, e.g., which of these collections can be written to, and > which can only be read. > > If I understand correctly, there are several approaches we could take. > (The thread on "Specifying operations for instances" [1] was critical > for helping me understand this, by the way---the current version of > the spec does not make these alternatives very clear.) For those > readers that, like me, were unclear on the differences among these > approaches, I've gone into pedantic detail descibing them below, but > we might summarize them as follows: > > 1. The first choice to be made is whether we want to specify supported > operations by describing resource classes, by describing links, or by > describing individual resources. > > 2. If we choose to describe classes or links, we need to further > decide whether we will choose to communicate permissions by keeping > the API documentation relatively stable and varying representations, > or by keeping representations relatively stable and varying API > documentation. > > 3. A third decision to be made is the extent to which we expose or > model the workings of the permissions system: ought we make clients > aware that there are such things as user roles, for example by > defining links based on roles, or should we simply say "these are the > supported operations of this link" and hide the fact that those > operations are being determined by the role of the user? > > Of course these are not mutually exclusive choices---we could take > hybrid approaches---but I think this covers the basic dimensions of > the design space. Currently we have adopted what seems to be the > simplest option: describing individual related resources in our > representations (approach 3 below). This is a good fit for web > frameworks providing functions which will take a user and an object > and give us back a set of permissions the user has with respect to > that object, which can then be serialized as the objects of > `hydra:operation` statements. > > But I am wondering what others' experience has been with doing this. > What approaches have you taken? What do you see as the pros and cons? > Are there possible approaches I haven't identified? One issue that > seems relevant is cacheability. Is it better to have API documentation > that varies infrequently, or specific resource representations that > change infrequently? Are there other considerations? Finally, if there > aren't concrete benefits to one approach over another, do we need so > many different ways to achieve basically the same thing? > > Cheers, > Ryan > > [1] https://lists.w3.org/Archives/Public/public-hydra/2014Jun/0054.html > > ---- > > 1. Describe resource classes > > > 1A. Define instances of `hydra:Class` with different supported > operations, and use these to type resources. So, in our API > documentation we could have: > > ex:Note a hydra:Class . > > ex:NoteCollection > a hydra:Class ; > hydra:supportedOperation [ > a hydra:Operation ; > hydra:method "GET" ; > hydra:returns ex:NoteCollection > ] . > > ex:EditableNoteCollection > a hydra:Class ; > hydra:supportedOperation [ > a hydra:CreateResourceOperation ; > hydra:method "POST" ; > hydra:expects ex:Note ; > hydra:returns ex:Note > ] . > > ex:notes > a hydra:Link ; > rdfs:range ex:NoteCollection . > > Then, the representation of the entry point for a user A who has > permission to add notes to project 1, but not to project 2, would be > something like: > > <> > ex:notes <project/1/notes/> ; > ex:notes <project/2/notes/> . > > <project/1/notes> a ex:EditableNoteCollection . > > <project/2/notes> a ex:NoteCollection . > > Taking this approach, the API documentation will remain relatively > static, and the types assigned to resources in representations will > change dynamically as permissions change. > > > 1B. Define different instances of `hydra:Class` for different > projects, and use these to type resources. This would involve defining > different "sub-APIs" per project, and varying the documentation of > these based on permissions. So, the documentation that user A would > see for the API to project 1 would include: > > ex:Note a hydra:Class . > > ex-project1:NoteCollection > a hydra:Class ; > hydra:supportedOperation [ > a hydra:Operation ; > hydra:method "GET" ; > hydra:returns ex-project1:NoteCollection > ], [ > a hydra:CreateResourceOperation ; > hydra:method "POST" ; > hydra:expects ex:Note ; > hydra:returns ex:Note > ] . > > The documentation that User A would see for the API to project 2 would > include: > > ex-project2:NoteCollection > a hydra:Class ; > hydra:supportedOperation [ > a hydra:Operation ; > hydra:method "GET" ; > hydra:returns ex-project2:NoteCollection > ] . > > And the entry point representation: > > <> > ex:notes <project/1/notes/> ; > ex:notes <project/2/notes/> . > > <project/1/notes> a ex-project1:NoteCollection . > > <project/2/notes> a ex-project2:NoteCollection . > > ex:notes a hydra:Link . > > Taking this approach, the API documentation for individual projects > would change dynamically, while the representation of the entry point > would be relatively static. > > (Incidentally, if we were to take this approach, it might make sense > to publish the API documentation for different projects at different > URIs. Furthermore, there might be higher-level documentation of > classes and links that are common across project-specific URIs. Would > we put links to all of these in the `hydra:apiDocumentation` header of > the entry point?) > > > > 2. Describe links > > > 2A. Define instances of `hydra:Link` with different supported > operations, and use these to type links to resources. So, in our API > documentation we could have: > > ex:Note a hydra:Class . > > ex:NoteCollection a hydra:Class . > > ex:readNotes > a hydra:Link ; > rdfs:range ex:NoteCollection ; > hydra:supportedOperation [ > a hydra:Operation ; > hydra:method "GET" ; > hydra:returns ex:NoteCollection ; > ] . > > ex:addNote > a hydra:Link ; > rdfs:range ex:NoteCollection ; > hydra:supportedOperation [ > a hydra:CreateResourceOperation ; > hydra:method "POST" ; > hydra:expects ex:Note ; > hydra:returns ex:Note > ] . > > Then, the representation of the entry point for user A would be: > > <> > ex:readNotes <project/1/notes/>, <project/2/notes/> ; > ex:addNote <project/1/notes/> . > > This approach is similar to 1A in that the API documentation will > remain relatively static, and the types assigned to links in > representations will change dynamically as permissions change. > > > 2B. A variation on the approach above would be to define a > project-specific link type for each role defined by the project > (remember that each project can define its own roles and assign > permissions to them). So the API documentation for project 1 (which > defines the roles "member" and "guest") could be: > > ex:Note a hydra:Class . > > ex:NoteCollection a hydra:Class . > > ex-project1:notesForMember > a hydra:Link ; > rdfs:range ex:NoteCollection ; > hydra:supportedOperation [ > a hydra:Operation ; > hydra:method "GET" ; > hydra:returns ex:NoteCollection ; > ], [ > a hydra:CreateResourceOperation ; > hydra:method "POST" ; > hydra:expects ex:Note ; > hydra:returns ex:Note > ] . > > ex-project1:notesForGuest > a hydra:Link ; > rdfs:range ex:NoteCollection ; > hydra:supportedOperation [ > a hydra:Operation ; > hydra:method "GET" ; > hydra:returns ex:NoteCollection ; > ] . > > And for project 2 (which defines only the role "contributor" and has a > generic link for anyone who has not been assigned a role): > > ex:Note a hydra:Class . > > ex:NoteCollection a hydra:Class . > > ex-project2:notesForContributor > a hydra:Link ; > rdfs:range ex:NoteCollection ; > hydra:supportedOperation [ > a hydra:Operation ; > hydra:method "GET" ; > hydra:returns ex:NoteCollection ; > ], [ > a hydra:CreateResourceOperation ; > hydra:method "POST" ; > hydra:expects ex:Note ; > hydra:returns ex:Note > ] . > > ex-project2:notes > a hydra:Link ; > rdfs:range ex:NoteCollection ; > hydra:supportedOperation [ > a hydra:Operation ; > hydra:method "GET" ; > hydra:returns ex:NoteCollection ; > ] . > > And the entry point for user A who is a "member" of project 1 but has > no role in project 2: > > <> > ex-project1:notesForMember <project/1/notes/> ; > ex-project2:notes <project/1/notes/> . > > Taking this approach, as in 2A, the API documentation will remain > relatively static (except when role definitions change), and the types > assigned to links in representations will change dynamically. But > rather than having multiple links supporting different operations, > representations could have a single role-based link. > > > 2C. Define different instances of `hydra:Link` for different projects, > and use these to type links to resources. This would, as in 1B, > involve defining different "sub-APIs" per project, and varying the > documentation of these based on permissions. So, the documentation > that user A would see for the API to project 1 would include: > > ex:Note a hydra:Class . > > ex:NoteCollection a hydra:Class . > > ex-project1:notes > a hydra:Link ; > rdfs:range ex:NoteCollection ; > hydra:supportedOperation [ > a hydra:Operation ; > hydra:method "GET" ; > hydra:returns ex:NoteCollection > ], [ > a hydra:CreateResourceOperation ; > hydra:method "POST" ; > hydra:expects ex:Note ; > hydra:returns ex:Note > ] . > > The documentation that User A would see for the API to project 2 would > include: > > ex:NoteCollection a hydra:Class . > > ex-project2:notes > a hydra:Link ; > rdfs:range ex:NoteCollection ; > hydra:supportedOperation [ > a hydra:Operation ; > hydra:method "GET" ; > hydra:returns ex:NoteCollection > ] . > > And the entry point representation for user A: > > <> > ex-project1:notes <project/1/notes/> ; > ex-project2:notes <project/2/notes/> . > > This approach is similar to 1B in that the API documentation for > individual projects would change dynamically, while the representation > of the entry point would be relatively static. > > > > 3. Describe resources directly in representations using `hydra:operation` > > This approach foregoes describing supported operations in the API > documentation entirely, in favor of attaching operations directly to > resources. The representation of the entry point would thus look > something like: > > ex:Note a hydra:Class . > > ex:NoteCollection a hydra:Class . > > ex:notes a hydra:Link . > > <> > ex:notes <project/1/notes/> ; > ex:notes <project/2/notes/> . > > <project/1/notes> > hydra:operation [ > a hydra:Operation ; > hydra:method "GET" ; > hydra:returns ex:NoteCollection > ], [ > a hydra:CreateResourceOperation ; > hydra:method "POST" ; > hydra:expects ex:Note ; > hydra:returns ex:Note > ] . > > <project/2/notes> > hydra:operation [ > a hydra:Operation ; > hydra:method "GET" ; > hydra:returns ex:NoteCollection > ] . > > Interesting approach. In Solid [1] we divide permissions into read,write,control and append. Similar to UNIX permissions. [1] https://github.com/solid/solid-spec
Received on Thursday, 29 October 2015 16:09:44 UTC