Representation of value objects with relationships


#1

I’m trying to represent a model with a value object that contains a relationship to another resource. The theory says that a value object does not contains an identifier by itself but its values are its identifier.
My scenary is something like this, I have a Sales resource that contains a list of Lines, and every Line have a relation to Product.

A request to

GET sales/

will return a list of sales of this way

[
    {
        id: "sale1",
        type: "sale",
        attributes: {
            date: "2015-10-12",
            lines: [
                {
                    price: 20.5
                    quantity: 5
                },
                ...
            ]
        }
    },
    ...
]

How may I link the Products? I was thinking in something like

GET sales/sale1/lines

To list Lines and get the Products as relationships but as I said, Lines does not contains an identifier. How may I represent a model like that?


#2

As I understand it, a value object refers to how two objects are compared by executing code. If the client treats two objects as being equal if (and only if) those two objects have the same fields, then it’s comparing them by value (rather than by whether they reference the same memory location). But, I’m pretty sure that this compare-by-value semantic only exists on the client; I’m not sure why it’d be something that the server needs to signal.

Therefore, unless I’m missing some requirement, I’d suggest just making the lines each their own resource (with an id), after which it will be easy to include them when a sale is requested (the server can do so by default) and for each line item to link to the corresponding product. Then, if your client still wants to compare the line items by value, it can just ignore the "id".

So you’re response would look like this:

GET /sales
{
  "data": [{
    "id": "sale1",
    "type": "sale",
    "attributes": {
      "date": "2015-10-12" 
    },
    "relationships": {
      "lines": {
        "data": [{"type": "line-item", "id": "1"}, /* .... */ ]
      }
    }
  }, {
    //...
  }],
  "included": [{
    "type": "line-item",
    "id": "1",
    "attributes": {
       "price": 20.5,
       "quantity": 5
    },
    "relationships": {
      "product": { 
        "data": {"type": "product", "id": "product32"}
      }
    }
  }]
}

Note that, above, the server has included the line items by default (which it’s allowed to do), without the client having to request them with the ?include= parameter.

By the way, having the id above actually turns out to be quite helpful for allowing the client to update a given line item. That is, if your client wants to update a single line item on a given sale, it can just do PATCH /line-items/xxxxx. If a line item didn’t have an id, it would have to either replace all the line items on the sale (PATCH /sales/sale1 with new lines in the body), or reference the old line item it’s trying to PATCH by it’s index in the lines array—but the latter approach isn’t reliable in a distributed environment, as a different client could have removed a line-item or reordered an item. So the id-based approach ends up being more robust.

If you don’t want clients to be able to retrieve a line-item independently from the sale it’s associated with, simply don’t support GET /line-items and GET /line-items/xxxx—there’s nothing in the spec that says you have to.

Does that work? Or am I missing something?


#3

Well, I opted for a different representation, I will include the “lines” directly in the resource as a complex attribute. I also analyzed this as a possible solution


#4

Ok, but complex attributes can’t (yet) include relationships (see #383), so how did you work around that?


#5

Well, I didn’t. I used a more simple representation, without jsonapi :pensive:, but now I’m returning to my json api tests

Your representation looks good, but I don’t feel comfortable with the “id” property in “Line” as it is a value object, I think I will generate the “id” with the values of the object, because I don’t persist it


#6

That sounds like a good solution!


#7

Is that a good solution though?

I think it’s very misleading for the client of the API. Client gets the impression that these IDs are somehow meaningful even though they have no meaning, other than to make the HTTP payload compatible with JSON API.


#8

If the API doesn’t provide any mechanism for a client to do anything with these IDs, what’s the harm in exposing them? Is it just that client developers might waste time looking for a way to use them?