- From: Andrew Hacking <ahacking@gmail.com>
- Date: Sun, 22 Feb 2015 09:55:10 +1000
- To: Dietrich Schulten <ds@escalon.de>
- Cc: public-hydra@w3.org, Kev Kirkland <kev@dataunity.org>
- Message-ID: <CAMAVcL-nT7NMusjgm0iq6kaAp=i35L+Vp=KnucSVdaekDisBng@mail.gmail.com>
Dietrich, I think this is really useful scenario for working through the nuances of exactly what is needed for a generic client to actually be able to do something semi intelligent. One thing I found troubling was that to place an order why would you transpose the product name to the order and not just reference the offered product? Other inevitabilities I see is that to place an order you will additionally need the the quantity, perhaps you also need to vary the order (add sugar, lactose free milk), and maybe also order a coffee for your colleague and actually pay with some payment method accepted by the shop, and are you having your coffee take away or on the premises? How would you express that there transitions which can only be taken which may have more complex requirements or rules? Imagine ordering a sandwich with many possibilities of ingredients that are tied to the shop. The shop may sell ham, cheese, avocado, lettuce and mayo on various bread types but within the context of various sandwich like constructions, meaning there are rules about what makes sense, eg you may be able to order extra cheese but double bread isn't sensible, and it may not be possible to have chicken, ham, tuna and meatballs on a single sandwich because that's just too difficult for the shop to process and fit on a single sandwich. The client can't submit the order without composing a sensible list of ingredients first which may have many parts and is dependent on the selection of many ingredient resources with rules. Whilst all those ingredient resources could transition to an order would you express that possibility and all the rules on every single ingredient? Would you prescribe building a sandwich in steps (ie a workflow starting with the bread followed by ingredients like an assembly line? ) You could apply the same analogy to ordering pizza. Oh, and I also want it to support group ordering eg for 10 people in my office. (See what that does to a workflow approach) How far can you really get with a generic client being practical for end users? I think that is an important question because the further you go with this the more you bake UI into the state exchange and the more you assume the resource knows the application state (which is not true in modern web applications). It seems there is a tension between exchanging state and baking in user interface and workflows with the state. At what point do you accept UI is better expressed through other means? When you come from the application development space you care more about querying and updating resource state with your api (CRUD) and less about workflow/transitions since that is a UI concern and something you layer above the data access layer. I again see embedding UI concerns into REST as the tail wagging the dog. When your app is composed of state from many different resources and services, it makes little sense for an endpoint to tell your app what to do or to prescribe a user interface workflow. Despite my views I am still interested in how far you get with this and how the concerns are expressed in hydra. On 22 Feb 2015 01:19, "Dietrich Schulten" <ds@escalon.de> wrote: > Hi, > Am 20. Februar 2015 11:41:26 schrieb Kev Kirkland <kev@dataunity.org> > <kev@dataunity.org>: > > > I'm no expert, but it looks like the schema.org example you have is > doing > > a different thing to a REST API. The link above is more like data > modelling > > of what is on offer, whereas REST (Representational *State Transfer*) > > should model the current state and how to change it. > > That is exactly what I am after :) > The need to document attributes and state transitions somewhere in an > online documentation has driven me crazy, that is why I am on this list. > It should not be that way. If you look at the way generic clients work > with newsfeeds and blog posts, you quickly realize that it should not be > necessary to code a special client for every single rest api out there. > > The example I gave at > https://lists.w3.org/Archives/Public/public-vocabs/2015Feb/0092.html > shows what is on offer, but not the state transitions, because it seems > pretty unclear to me how to attach the necessary links in a semantically > correct way, so that every client would understand what to do (with > http://schema.org/potentialAction, see also [1]). > > The question is a very practical one: exactly how do I express the state > transitions the client can use, without introducing new attributes, but > solely based on schema.org for product descriptions and offerings and > hydra for interactions. And, *most important*: in a self-describing way, so > that clients can GET a cup of coffee without a human who programs every > single interaction. > > > If you imagine being at the coffee counter. The first state might be > coffee > > choices, so your API would return something like: > > > > CoffeeChoice: > > Link: Choose Americano > > Link: Choose Latte > > So let's do it. I have the following as entrypoint. Where do I attach the > link to post to /orders? > > { > > "@context": "http://schema.org/" <http://schema.org/>, > "@type": "CafeOrCoffeeShop", > "@id": "http://example.com/store" <http://example.com/store>, > "name": "Kaffeehaus Hagen", > "makesOffer": [ > { > "@type": "Offer", > "@id": "http://example.com/offers/1234" <http://example.com/offers/1234>, > "itemOffered": > { > "@type": "Product", > "@id": "http://example.com/products/latte" <http://example.com/products/latte>, > "name": "Latte Macchiato" > }, > "price": 2.8, > "priceCurrency": "EUR", > }] > > } > > > In hydra I describe a possible http method as a hydra:Operation of a > Resource. That description could be found in the ApiDocumentation where I > could define a supportedClass with supportedOperation. Or I may describe it > right here as a hydra:operation property. The target url of the operation > is the @id of the subject which has the :operation property. > > That means, in the object above I can define an operation on the > CafeOrCoffeeShop, the Offer or the Product, because they have an @id. We > want to POST somewhere. But to what resource? > > We cannot POST to the shop, the offer or the product resource to create an > order. We need to POST to some resource which will create an order for us, > and we want to describe the body of the POST. > > We can try to enhance the product in the object above like this: > > ... surrounding Offer ... > { > "@context": { > "hydra": "http://www.w3.org/ns/hydra/core#" <http://www.w3.org/ns/hydra/core#>, > "hydra:property": { "@type": "@vocab"} > }, > "@type": "Product", > "@id": "http://example.com/products/latte" <http://example.com/products/latte>, > "name": "Latte Macchiato", > "order": { > "@id": "http://example.com/orders" <http://example.com/orders> > "hydra:operation" : { > "hydra:method": "POST", > "@type": "OrderAction", > "hydra:expects": > { > "@type": "Product", > "hydra:supportedProperty": [ > { > "hydra:property": "name", > "hydra:required": true > } > ] > }, > "hydra:returns": "Order" > } > }, > } > ... surrounding Offer continued. > > > Note that I had to add a property "order" to express the target URI of the > POST. > > This tells the client that it may POST to "http://example.com/orders" > <http://example.com/orders> and use the following body: > > > { > "@context": "http://schema.org/" <http://schema.org/> > "@type": "Product", > "name": <a product name> > } > > > If name=Latte Macchiato then the response could be: > > > 201 Created > Location: http://example.com/orders/1234 > > { > "@context": { > "@vocab": "http://schema.org/" <http://schema.org/>, > "hydra": "http://www.w3.org/ns/hydra/core#" <http://www.w3.org/ns/hydra/core#>, > "hydra:property": { "@type": "@vocab"} > }, > "@type": "Order", > "@id": "http://example.com/orders/1234" <http://example.com/orders/1234> > "orderedItem": [ > { > "@type": "Product", > "name": "Latte Macchiato" > }] > ... > } > > Now we could add links to the response for follow-up transitions. > > Not so bad at first sight, but there are issues both in schema.org and in > hydra: > > 1.) There is no http://schema.org/order property, neither on > schema:Product nor could I find anything else like it. So what do I do? > Coming from an ordinary REST background, I need some rel the client can use > to look up the next transition. If the rel is not publicly defined (like > http://schema.org/order) how does the client achieve its goal? > 2.) I can say with hydra that the POST supports a product name property, > however what I really want to say is that the product name to POST is > "Latte Macchiato". In an html form I would use a readonly input. I could > use schema:readOnlyValue=true with schema:defaultValue="Latte Macchiato", > but my feeling is, we should be explicit how these things may be expressed > in hydra. Without a defaultValue the client needs to know how to apply the > product name from the surrounding offer for a latte macchiato to the nested > operation, which seems a bit too much. > > That is, the above won't work, really. We aren't quite there yet. > > Another possibility would be to add a hydra:Collection without 'manages > block' to the shop: > > { > "@context": "http://schema.org/" <http://schema.org/>, > "@type": "CafeOrCoffeeShop", > "@id": "http://example.com/store" <http://example.com/store>, > "name": "Kaffeehaus Hagen", > "hydra:collection": { > "@id": "http://example.com/orders" <http://example.com/orders>, > "hydra:operation": [ > { > "hydra:httpMethod": "POST", > "@type": "OrderAction", > "hydra:expects": > { > "@type": "Product", > "hydra:supportedProperty": [ > { > "hydra:property": "name", > "hydra:required": true > }] > }, > "hydra:returns": "Order" > }] > }, > "makesOffer": [.. Offers with nested products ..] > } > > > I start to like some aspects of the new collection design :) > Omitting the manages block solves the problem of the missing schema:order > property, but not the problem that a generic client must be awfully smart > to apply the latte macchiato which is on offer when posting to the /orders > resource. > > Ah yes, and finally it occurred to me that I could use hydra:collection on > the Product and describe a POST which orders a Latte Macchiato without > putting the burden to the client to apply products to the offer. Just > because with hydra, I can :) > > { > "@context": { > "@vocab": "http://schema.org" <http://schema.org>, > "hydra": "http://www.w3.org/ns/hydra/core#" <http://www.w3.org/ns/hydra/core#>, > "hydra:property": { "@type": "@vocab"} > } > "@type": "CafeOrCoffeeShop", > "@id": "http://example.com/store" <http://example.com/store>, > "name": "Kaffeehaus Hagen", > "makesOffer": [ > { > "@type": "Offer", > "@id": "http://example.com/offers/1234" <http://example.com/offers/1234>, > "itemOffered": > { > "@type": "Product", > "@id": "http://example.com/products/latte" <http://example.com/products/latte>, > "name": "Latte Macchiato", > "hydra:collection": { > "@id": "http://example.com/orders" <http://example.com/orders> > "hydra:operation" : { > "hydra:method": "POST", > "@type": "OrderAction", > "hydra:expects": > { > "@type": "Product", > "hydra:supportedProperty": [ > { > "hydra:property": "name", > "hydra:required": true, > "defaultValue": "Latte Macchiato", > "readOnlyValue": true > } > ] > }, > "hydra:returns": "Order" > } > } > }, > "price": 2.8, > "priceCurrency": "EUR", > }] > } > > Comments? Is it a recommendable practice to use hydra:collection in case > there is no dedicated extension rel (aka linked data property) which > precisely matches the interaction needs - such as the missing > http://schema.org/order property in the example above? > Should we have a construct in hydra which is equivalent to a read-only or > hidden input field? In the example above above I had to borrow from > schema.org. > > Right now there seem to be many (too many?) ways how the typical > transitions in an application may be designed. My feeling is, we need to > document a robust way how servers may add arbitrary transitions in the > application state. > > The approach shown last has interesting consequences for a generic hydra > client. > While in REST we normally assume that a client simply looks for a rel > which tells it about possible transitions (rel="order", href= > "http://example.com/orders" <http://example.com/orders>), a hydra client > might be required to look for objects with hydra:operation properties in > order to find transitions which change resource states. I.e. a hydra client > would not look for a particular rel or attribute ( > "http://schema.org/order" <http://schema.org/order>) to learn how to > order, but has to look for a hydra:operation nested inside any property of > the desired schema:Product which matches its goal. To identify the goal, > the operation @type becomes vital (here: OrderAction). > In this case: if http://schema.org/order existed, servers could use both > schema:order and hydra:collection to attach a transition which creates an > order. Clients must look inside both. > > We will have a hard time to explain why this is not an attempt to > re-introduce RPC (client looks up the order() procedure on the product > object). My main argument is right now, that to perform an OrderAction, > multiple http method invocations may be involved. But I have yet to show > how that would look like. > > Based on the latest design above, I'll continue with the next steps: > - pay for the order: POST payment to /payments. Where do I attach the > payment link? > - continue ordering: PUT an extended order with another product to > /order/1234. > - update my latte macchiato order and add an extra shot, not separately in > an extra bowl, but right inside my latte macchiato > - track the delivery > > After so much talk about digital coffee, I'll now go and get one in real > life :o) > > Best regards, > Dietrich > > [1] http://schema.org/docs/actions.html >
Received on Saturday, 21 February 2015 23:55:38 UTC