0

I'm migrating some API endpoints to a more concise way. But I'm having some issues about how to handle nested objects.

For example:

I have an object Foo and a Bar.

Foo v1.0

{
  "field_one": "String",
  "field_two": "String"
}

Foo v1.1

{
  "field_one": "String",
  "field_two": "String",
  "field_three": "String"
}

Bar v1.0

{
  "foo": "Foo",
  "field_one": "String",
  "field_two": "String"
}

For an endpoint to get Foo the version is pretty straightforward, is v1.0 or v1.1, but how do I handle an endpoint for Bar? Every change to a child should "generate" a new version for the parent? How to handle if the parent have more than one versioned child? If Bar has another child Baz with two different versions, the versioning of Bar will keep going with the iterations of the children?

Bar v1.0 -> Foo v1.0
Bar v1.1 -> Foo v1.1
Bar v2.0 -> Foo v1.1 + Baz v1.0

How to make it straightforward so, if the consumer want to use the Foo v1.1 on his whole application, he knows which version of Bar should he get? Just documentation or there are some pattern behind it?

augustoccesar
  • 658
  • 9
  • 30
  • how does Bar know which Foo to get before returning the response? Does 'something' create the objects then wire them together? How does it know which versions to use? If there is a Foo.1.1 something must know about it internally. What rules does it use to determine whether to use Foo.1.0 or Foo.1.1? Sounds like you want to expose those rules in the url for clients – codebrane Oct 05 '18 at 10:13
  • How does the client know what data it is dealing with when all you return is JSON to start with? You should avoid [typed resources](http://soabits.blogspot.com/2012/04/restful-resources-are-not-typed.html) and instead define some media-types that define how a resource representation may look like. Media types usually define syntax and semantic of the payload as well as define valid link-relations and valid operations that can be invoked on such relations. Versioning on media types can happen in different way. Either backward compatible like HTML or an own media type per version – Roman Vottner Oct 05 '18 at 11:44
  • @codebrane the idea is to have a header with, for example if I do `/api/bars/{id}` with `Accept: application/com.vnd.v1.0+json`, I'll get the `Bar v1.0 -> Foo v1.0`. But at I can do the requests as `/api/foo/{id}` with `Accept: application/com.vnd.v1.1+json`. The controller for said endpoints know how to build each version. If in two parts of the application I do these two requests, the `Foo` that I got on the second and the `Foo` inside the `Bar` on the first are going to be different. I'm trying to think of a way to make the versions across these parents and children to be more "clean". – augustoccesar Oct 05 '18 at 11:54
  • @RomanVottner as it is right now, the whole API is documented and the client is other developers on the company. My idea is that I'm trying to simplify the versioning and avoid the amount of replication of endpoints since right now every exists in all versions despite it has changed or not ('/v1', '/v2' for some entities generate the same result, for others is different (the ones that actually generated the new version)). I'm trying to put the versioning on the `Accept` header, but I'm having issue to think about how to version the children entities (since they have their own version too). – augustoccesar Oct 05 '18 at 12:02

1 Answers1

0

All of the comments have some good feedback on possible solutions. The question you have to ask is whether your nested resource is contained. Containment doesn't allow a resource to be addressed and, thus, it's versioned according to its contained parent. For example, consider an Order and its related Line Items. A line item typically should not be addressable on its own.

If you have related, but different, addressable resources, any one resource should not directly provide a related resource. This introduces coupling. The way to solve this in REST is to use HATEOAS. There are many ways that HATEOAS can be achieved and only a few standards that provide any guidance. There is no one right answer on how to implement HATEOAS, but it might look something like:

Bar v1.1

{
  "field_one": "String",
  "field_two": "String",
  "links": [
    { "name": "Foo", "href": "http://localhost/foo/123" }
  ]
}

This enables Bar to link to a Foo without having a hard dependency on it. This is same way that websites are composed using hyperlinks to related pages.

Here's a few more notes that you should consider when implementing HATEOAS:

  • In accordance with the Uniform Interface constraint, the URL is the identifier (and not 123 as a human being might infer)
  • You should use absolute link URLs because the related resource may not be at the same host and the client should not have to figure that out.
  • Though common, versioning by URL segment causes a problem here because how does the server know how to generate links with an appropriate API version? Other methods such as media type, query string, or even header do not suffer from this. Ultimately, the client is responsible for knowing which API version to ask for. There is no way for the server to ever know what the client wants. Unless the server guarantees version symmetry, it's quite possible that clients will be bound to incongruent API versions (ex: Foo 1.0 and Bar 1.1). Enforcing version symmetry can be difficult or slow if all APIs have to have the same supported versions, even when there are no changes. I've also seen URL templates used to solve the URL segment issue, but - again - the client shouldn't have to know or understand a template syntax.

May this be useful as a starting point to your designs.

Chris Martinez
  • 3,185
  • 12
  • 28