Re: Non-trivial trading flows with schema.org

Hi,

Am 20. Februar 2015 11:41:26 schrieb Kev Kirkland <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/",
  "@type": "CafeOrCoffeeShop",
  "@id": "http://example.com/store",
  "name": "Kaffeehaus Hagen",
  "makesOffer": [
  {
    "@type": "Offer",
    "@id": "http://example.com/offers/1234",
    "itemOffered":
    {
      "@type": "Product",
      "@id": "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#",
    "hydra:property": { "@type": "@vocab"}
  },
  "@type": "Product",
  "@id": "http://example.com/products/latte",  
  "name": "Latte Macchiato",
  "order": {
      "@id": "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"
and use the following body:

{
  "@context": "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/",
    "hydra": "http://www.w3.org/ns/hydra/core#",
    "hydra:property": { "@type": "@vocab"}
  },
  "@type": "Order",
  "@id": "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/",
  "@type": "CafeOrCoffeeShop",
  "@id": "http://example.com/store",
  "name": "Kaffeehaus Hagen",
  "hydra:collection": {
    "@id": "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",
    "hydra": "http://www.w3.org/ns/hydra/core#",
    "hydra:property": { "@type": "@vocab"}
  }
  "@type": "CafeOrCoffeeShop",
  "@id": "http://example.com/store",
  "name": "Kaffeehaus Hagen",
  "makesOffer": [
  {
    "@type": "Offer",
    "@id": "http://example.com/offers/1234",
    "itemOffered":
    {
      "@type": "Product",
      "@id": "http://example.com/products/latte",
      "name": "Latte Macchiato",
      "hydra:collection": {
        "@id": "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"), 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") 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 15:18:53 UTC