Allowing add-ons (was: Non-trivial trading flows with schema.org)

Hi,

Am 21.02.2015 um 16:18 schrieb Dietrich Schulten:
> 
> 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.

Assuming that the initial order has been created, the server can make
additional offerings. Let's say we want to allow an additional shot of
Espresso and we want to offer a breakfast special.

There is an :addOn property on :Offer, but we are past the initial
offer. We now have an order with a product inside. Unfortunately, :addOn
is not defined for either of them.
However, schema:Product has an :offers property which holds "an offer to
provide this item" which in turn can hold :addOn offers. It might be a
creative use, but we can certainly attach an :offers property with an
:Offer having only :addOn items.

{
  "@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",
      "@id": "http://example.com/orders/1234/items/1",
      "offers": {
        "@type": "Offer",
        "addOn": [
          {
            "@type": "Offer",
            "itemOffered": {
              "@type": "Product",
              "@id": "http://example.com/orders/1234/items/1/addOns",
              "name": "shot",
              "hydra:operation": {
                "hydra:httpMethod": "POST",
                "@type": "OrderAction",
                "hydra:expects": {
                  "@type": "Product",
                  "hydra:supportedProperty": [
                    {
                      "hydra:property": "name",
                      "hydra:required": true,
                      "defaultValue": "shot",
                      "readOnlyValue": true
                    }
                  ]
                }
              }
            },
            "price": 0.2,
            "priceCurrency": "EUR"
          },
          {
            "@type": "Offer",
            "itemOffered": {
              "@type": "Product",
              "@id": "http://example.com/orders/1234/items/1/addOns",
              "name": "Brioche con crema",
              "description": "Breakfast special",
              "hydra:operation": [
                {
                  "hydra:httpMethod": "POST",
                  "@type": "OrderAction",
                  "hydra:expects": {
                    "@type": "Product",
                    "hydra:supportedProperty": [
                      {
                        "hydra:property": "name",
                        "hydra:required": true,
                        "defaultValue": "Brioche con crema",
                        "readOnlyValue": true
                      }
                    ]
                  }
                }
              ]
            },
            "price": 1.00,
            "priceCurrency": "EUR"
          }
        ]
      }
    }
  ]
}

The response to the possible POSTs would be an updated order. The client
orders an additional shot:

POST /orders/1234/items/1/addOns HTTP/1.1
...

{
  "@context": "http://schema.org/"
  "@type": "Product",
  "name": "shot"
}

The server returns an :Order containing the latte macchiato with extra
shot. In the updated order below you can find the extra shot at
"$.orderedItem[0].extra".
There is an attribute :isAccessoryOrSparePartFor which normally belongs
on the accessory, but with @reverse we can define an "extra" property in
the @context from it.

Of course, the customer can remove the extra shot again by using the
DELETE method on the shot resource.

And something else has changed. Since Kaffeehaus Hagen only wants to
offer one extra shot of Espresso per Latte Macchiato, the only offer
left is the Brioche con crema. If the client orders that, too, the order
would have no more offers, because the breakfast special only allows for
one brioche per hot beverage at a reduced price.

200 OK

{
  "@context": {
    "@vocab": "http://schema.org/",
    "hydra": "http://www.w3.org/ns/hydra/core#",
    "hydra:property": {"@type": "@vocab"},
    "extra": {"@reverse": "isAccessoryOrSparePartFor"}
  },
  "@type": "Order",
  "@id": "http://example.com/orders/1234",
  "orderedItem": [
    {
      "@type": "Product",
      "name": "Latte Macchiato",
      "@id": "http://example.com/orders/1234/items/1",
      "extra": {
        "@type": "Product",
        "@id": "http://example.com/orders/1234/items/1/addOns/1",
        "name": "shot",
        "hydra:operation": {
          "hydra:httpMethod": "DELETE",
          "@type": "DeleteAction"
        }
      },
      "offers": {
        "@type": "Offer",
        "addOn": [
          {
            "@type": "Offer",
            "itemOffered": {
              "@type": "Product",
              "name": "Brioche con crema",
              "description": "Breakfast special",
              "hydra:operation": [
                {
                  "hydra:httpMethod": "POST",
                  "@type": "OrderAction",
                  "hydra:expects": {
                    "@type": "Product",
                    "hydra:supportedProperty": [
                      {
                        "hydra:property": "name",
                        "hydra:required": true,
                        "defaultValue": "shot",
                        "readOnlyValue": true
                      }
                    ]
                  }
                }
              ]
            },
            "price": 1.00,
            "priceCurrency": "EUR"
          }
        ]
      }
    }
  ]
}

Since we cannot express nested supported properties yet (as requested in
Issue #26 [1]), I cannot use a PUT or PATCH to /orders/1234/items/1
because I cannot say that I want to update the nested property
$.orderedItem.extra. We have no issue voting system, but #26 is one of
my two main pain points right now (the other being #82).

The good part about this is: once we support nested properties, the
server could change the operation from

POST /orders/1234/items/1/addOns/1

to a

PUT /orders/1234/items/1

without breaking the client. QED :)

At least the flow seems reasonable, although it is a bit like an
assembly line.

Best regards,
Dietrich


[1] https://github.com/HydraCG/Specifications/issues/26

Received on Sunday, 22 February 2015 13:20:11 UTC