Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Polymorphic types #28

Closed
mahmoudimus opened this issue May 6, 2013 · 19 comments
Closed

Polymorphic types #28

mahmoudimus opened this issue May 6, 2013 · 19 comments
Labels

Comments

@mahmoudimus
Copy link

At @balanced, we have an endpoint, transactions, that returns polymorphic types of Credit, Debit, Hold, Refund, and Reversal . The transactions endpoint returns these objects in order of creation.

How would the payload structure look like?

{
   'transactions': [
       {
          'debit': {
               'id': 'WDxxxxx',
               'uri': '/v1/holds/WDxxxxx',
               'created_at': '2013-04-02',
          },
       },
       {
          'hold': {
               'id': 'HLxxxxx',
               'uri': '/v1/holds/HLxxxxx',
               'created_at': '2013-04-01',
          },
       },
   ]
}

Is this the right way?

@mateomurphy
Copy link

I think using a type attribute is the best way, especially since you might want ordering within the results.

@lukfugl
Copy link

lukfugl commented May 6, 2013

I agree with @mateomurphy; I think what you're looking for here is to make sure each transaction subtype's representation is an extension of a base "transaction" representation, then return them as is. A client that doesn't know (or care) about the subtypes can still view them as a list of transactions, ignoring the subtype extension fields it doesn't recognize.

Although maybe a meta (#29) would be a good place for the type info. E.g.:

{
  'transactions': [{
    'meta': { '_type': 'debit' },
    'id': 'WDxxxxx',
    'uri': '/v1/holds/WDxxxxx',
    'created_at': '2013-04-02',
    'debit_specific_field': '...'
  }, {
    'meta': { '_type': 'hold' },
    'id': 'HLxxxxx',
    'uri': '/v1/holds/HLxxxxx',
    'created_at': '2013-04-01',
    'hold_specific_field': '...'
  }]
}

@dacort
Copy link

dacort commented May 6, 2013

👍 Using types as the actual key names can be difficult to work with in some languages.

@paddycarver
Copy link

I'd vote emphatically against polymorphic types. Not every language supports them, and when they're not supported, you need to introspect the request to figure out what's going on and how to parse it. It's brutal.

How I solved this, personally, is as follows:

{
  "transactions": {
    "debits": [
      {....}, {....}
    ],
    "holds": [
      {...}, {...}
    ]
  }
}

It has been a pretty flexible solution for me thus far.

@mahmoudimus
Copy link
Author

@paddyforan - how are you handling a "running balance" of temporal objects?

Are you leaving the sorting on the client side? That's, imo, a bad idea.

@paddycarver
Copy link

I'm not entirely sure what you mean by a "a 'running balance' of temporal objects". Could you explain a bit more?

And no, I do sorting on the server side, because otherwise the server would have to pass the entire body of data to the client when the client is really only interested in a slice of it. Which is bad.

What I think you're asking is how to interleave debits and holds to get one list that is sorted and contains all sub-types. And in that case, I'd do the sort on the server-side, split them apart by type, pass them down to the client, then do the sort on the client-side and weave them back together. That's just hypothetical, though, because I've never found a situation in which I needed to sort over multiple types of resources at the same time.

@mahmoudimus
Copy link
Author

@paddyforan - apologies for the hasty comment. You're right, I definitely mean interleaving debits and holds to get one list and sorting them by created at time is a very common operation in a our API. This essentially gives our customers a timeline of activity on their accounts. This is a very common pattern, think of @facebook's timeline of events (photo, video, etc).

And in that case, I'd do the sort on the server-side, split them apart by type, pass them down to the client, then do the sort on the client-side and weave them back together.

Are you suggesting pushing sorting logic into every client that consumes the API to construct this timeline? That seems quite counter-intuitive to me.

@paddycarver
Copy link

Well, my first objection would be with the timeline example: I'd contend that those are all resources of the same type (Event, for example) that refer to other resources of a different type. This is, I believe, how Github's API is structured as well. Not polymorphic at all.

And yes, the practice of sorting on the client side is not ideal. But I'd say the need to introspect a request to be able to decode it is even less ideal. If we can find a way to avoid both those things, I'm all for it.

@mateomurphy
Copy link

No, github's Activity API is polymorphic, and includes a type attribute, as well as a payload attribute that varies according to type:

[
    {
        "id": "1729529601",
        "type": "PushEvent",
        "actor": {... },
        "repo": {...},
        "payload": {
            "push_id": 173618532,
            "size": 0,
            "distinct_size": 0,
            "ref": "refs/heads/staging",
            "head": "72250d38042c1f3890b52bdc64b91d1ecc8839f8",
            "before": "4f409c8fbd439b651d7f0f6bc78b702f55909163",
            "commits": []
        },
        "public": true,
        "created_at": "2013-05-07T03:52:54Z",
        "org": {...}
    }
]

Other APIs I've used take a similar approach, and having written client code to access them, parsing them has not been an issue at all.

@lukfugl
Copy link

lukfugl commented May 7, 2013

Not to quibble, but looking at your example from github's API, I see a uniform stream item type with a related resource (the PushEvent) embedded rather than linked. Slightly different from a polymorphic type where all the attributes of the payload would be siblings of the attributes of the stream item.

But I concede it's a pretty fine hair to split. :)

@SphtKr
Copy link
Contributor

SphtKr commented May 7, 2013

I mostly agree with @lukfugl, and I'd interpret the Github sample as he did. But to get a bit "meta", really that bit of JSON doesn't inherently require one interpretation or the other: a client (or a person) could interpret the "type" field as either an attribute or a class name, and either could be right. Really, the same could be said for @mahmoudimus 's sample data as well, though it seems to imply a class-based representation more strongly. But in either case, you could interpret the "class" name as simply a qualifying string property.

This reminds me of looking at the similarities and differences between different polymorphism implementations (strict single-inheritance, multiple-inheritance, interfaces/protocols, mixins...)--it's just turning the same core concept inside-out and back again: different classes of objects with some subset of common properties that can be interchanged in the right circumstances. Really, all of the different JSON examples here could be deserialized into any of those polymorphic paradigms.

Nevertheless, I'm not sure what the best answer is here. So far, it appears the firm assumption is that the class of the serialized object should be able to be inferred solely from the JSON key in which it appears. If that is an overarching design decision, I don't see how--really--any polymorphism can be supported directly in the serialization format. You could work around it fairly easily for most purposes by having a uniform proxy object "wrap" each subtype when you needed to return them all in a single array, for example (as @lukfugl interpreted the Github data to intend). Then each proxy object could have a URL referencing the related resource--but you almost couldn't do this with an ID reference, because the ID alone wouldn't imply the subtype in which to find the ID (you could have a proxy object with a different property for each subtype and only populate one property, but now it's getting more complex).

Maybe we need the type information in the yet-to-be-fleshed-out meta key?

@steveklabnik
Copy link
Contributor

The 'type' key isn't meant to be a direct expression of your type hierarchy; exposing these kinds of details is brittle and fraught with problems.

From http://roy.gbiv.com/untangled/2008/rest-apis-must-be-hypertext-driven

A REST API should never have “typed” resources that are significant to the client. Specification authors may use resource types for describing server implementation behind the interface, but those types must be irrelevant and invisible to the client. The only types that are significant to a client are the current representation’s media type and standardized relation names.

@SphtKr
Copy link
Contributor

SphtKr commented May 9, 2013

@steveklabnik Okay, all well and good...but then is all the by-convention direct inference of client-side classes from JSON key names in the existing Ember Data going to be removed, and we should expect it to change? According to your source, the current Ember Data convention would be considered harmful.

@steveklabnik
Copy link
Contributor

The quote is about crossing the boundary of a client/server interaction. Once you've crossed the boundary, do whatever you want. The Ember feature you're talking about isn't contrary to my quote.

@steveklabnik
Copy link
Contributor

... but expecting your Rails models to map exactly 100% always to your Ember ones would be.

@SphtKr
Copy link
Contributor

SphtKr commented May 9, 2013

Agreed. Incidentally, I won't be using Rails in my backend...this discussion is primarily about the spec, not the reference implementation, right?

Point being, I don't think the discussion was necessarily about explicitly mirroring the server side's class hierarchy into the client (resource model != data model and all that), It's about how the JSON sent (which does not necessarily reflect the underlying server-side object model) should imply mappings to classes in the client side.

In that context, I fail to see how wanting to imply (to the client) a polymorphic class model for your resources is fundamentally different than the current convention, which implies which classes to instantiate on the client side 1-to-1?

@steveklabnik
Copy link
Contributor

Yes, this is intended to be 100% agnostic on both ends. I mentioned Rails because you mentioned Ember, figured I'd make the example 100% concrete.

Sent from Mailbox for iPhone

On Thu, May 9, 2013 at 12:15 PM, SphtKr notifications@github.com wrote:

Agreed. Incidentally, I won't be using Rails in my backend...this discussion is primarily about the spec, not the reference implementation, right?
Point being, I don't think the discussion was necessarily about explicitly mirroring the server side's class hierarchy into the client (resource model != data model and all that), It's about how the JSON sent (which does not necessarily reflect the underlying server-side object model) should imply mappings to classes in the client side.

In that context, I fail to see how wanting to imply (to the client) a polymorphic class model for your resources is fundamentally different than the current convention, which implies which classes to instantiate on the client side 1-to-1?

Reply to this email directly or view it on GitHub:
#28 (comment)

@andrewsardone
Copy link
Contributor

It looks like this ticket never came to any resolution. @mahmoudimus, how did you end up solving your problem?

@diosney
Copy link
Contributor

diosney commented Feb 17, 2014

I already got hit by this.

What I'm looking for is to accomodate the following structure in json-api terms:

  • a generic interfaces:

GET /interfaces

It will gaves a list of all system interface but with a normalized attributes (the common ones).

  • a specific interface type, which will answer with the list or specific interface with all its data:

GET /interfaces/ethernets
GET /interfaces/ethernets/:ethernet

GET /interfaces/loopbacks
GET /interfaces/loopbacks/:loopback

According to above, would be a solution a 1-1 relationship between interfaces and the specific one: Interface <-> Ethernet, Interface <-> Loopback, and so on?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Projects
None yet
Development

No branches or pull requests

9 participants