3

I'm working on a service that forwards/unifies our API calls to external platforms/services.

For example, let's say that I don't want to handle all possible actions (create post, retrieve post, edit post, list posts) for Facebook, Twitter, LinkedIn, Google+ at each user-facing service. So the user-facing services calls this API forwarder with a message, somehow specify the service to post to and on behalf of which user is the request coming from.

To forward the call successfully, I need to identify:

  1. the calling service, for which I use the Authorization header.

  2. the service to which to forward.

  3. the user who initiated the request. I shouldn't store end user external service credentials (e.g. facebook tokens) in the calling service but in the forwarding service.

I have a few options on how to pass this data, and any input would be appreciated:

  1. Specifiy service and user as sub-resouces in URL /service/{externalServiceName}/user/{userReference}/Object/{objectId} E.g. GET /service/facebook/user/uid123/posts/post345 This feels quite verbose to me.

  2. Use query parameters. /Object/{objectId}?service={externalServiceName}&user={userReference} E.g. POST /posts?service=facebook&user=uid123

  3. Overload the Authorization header with: Bearer: <OAuth token>, Service: <serviceName>, User: <userReference> Resulting in nice URLs (GET /posts/). This seems totally out of spec though. Turns out specifying multiple values for a header separated by comma is in the spec.

  4. Use custom headers: X-APIUnify-Service: <serviceName> X-APIUnify-User: <userReference> Again, with nice, short URLs (POST /posts/post345).

  5. Use a mix of custom headers and resources in URL: GET Authorization: Bearer: <OAuth token>, User: <userReference> /{externalServiceName}/Object/{objectId} This makes sense to me because: 1. the userReference is actually a second layer of authorization anyway. 2. The service is the resource that owns the Object. I.e. the same objectId can point to different resources in different services.

  6. Use JWT to pass the service and user, signed with the calling service secret. JWT.payload = { "callingServiceId": "5aafcd909c9a25054c3019e3", "externalServiceName: "facebook", "userReference": "uid123", "iat": 1521638763 } Authorization: Bearer: <JWT>

Thank you!

  • 2
    /service/facebook/user/uid123/posts/post345 This feels quite verbose to me Who is supposed to read this URI over and over. You or the http client? URIs are not required to be "nice" or "readable, just unique. On the other hand, if you are building a gateway, why don't you send the service and user into the POST request body? POST /services/posts/ – Laiv Mar 27 '18 at 13:17
  • Why would you not use the Body for some or all of this? Seems like you wouldn't want to use a GET anyway since you're modifying a resource. With POST you can stuff the data for forwarding into the Body and your URLs can be super short. – Paul Mar 27 '18 at 15:02
  • Both GET and POST requests should be supported. @Laiv shouldn't the POST body be the object being created at /services/posts (by convention or spec, I'm not sure)? – Slav Mar 27 '18 at 15:33
  • @Paul I'd like to handle the HTTP verbs in a Restful way (i.e. keep GET idempotent). Also GET, POST and the other verbs should be supported. – Slav Mar 27 '18 at 15:36
  • GET /posts/?q=service:facebook,user:xxxxxxx,from:ts,to:ts – Laiv Mar 27 '18 at 16:01
0

We need to think in the best design API starting from the API objective.

Your API objective is:

service that forwards/unifies our API calls to external platforms/services

So, you start with posts endpoint:

POST /posts/

Body
{
    title: "Hello",
    text: "Hello World"
}

But this post can posted to Facebook, LinkedIn, etc and you need the user reference also.

You can pass this informations on the body, of course. But:

  • Mix exclusive platform attributes on same body. The Twitter, per example, does not have a title or some platforms has support for tags. This attributes will be ignored for platforms that not support them. The server will need to figure out at runtime what kind of platform he is receiving and looking if all required fields are there for that platform. The code could easily be a mess.
  • Client will handle the API complexity. As some attributes are exclusive for some platforms, the client needs to control what group of attributes he needs to send for each platform for the same endpoint.
  • Document this API will be a difficult thing to do. Imagine put a Swagger on that. Probably the client will have a hard time trying to figure out what informations is supported for each platform.
  • Support for new platforms will be harder. Maybe you would like to add a new platform that needs a new information to be passed together with the user reference (see my Slack example).
  • Versioning is not possible for each platform. You will not able to version each platform individually, what could be a nice feature to follow the changes of each platform.

And etc. You can think in a lot of possibilities and particularities of each platform.

As you can see, use a single endpoint is a risk.

So seems good to me separate in different endpoints:

/platforms/facebook/
/platforms/twitter/
/platforms/google-plus/

And the URL endpoint and body of each one can have his particularities. Divide and conquer.

You can understand this as /platforms/{platform}, but be careful. Threat this as generic can bring some problems if you need more flexibility on the URL endpoint. Per example, to create a post on Slack you need the platform, user and a channel:

/platforms/slack/users/slav/channels/rest-questions/

Thinking in this way, you can separate the platforms and can change each one without modify the others that are working. The GET and another HTTP methods are natural to use in this case too.

About the verbosity, is not a problem if this brings clarity about the use of your API.

  • 1
    Maybe you would like to add a new platform that needs a new information to be passed together with the user reference. Transpiring the services' details that you are trying to hide just make the facade useless. Body request inputs can be nullified and ignored on the server side. It's a matter of mappings. In other words, how you turn one "post" into a 3rd party post is an implementation detail. You said use a single endpoint is a risk. Not really. It's the safest and more scalable approach in the long run since clients don't to be aware of the "needs" of each platform. – Laiv Mar 28 '18 at 6:32
  • On the other hand, seems to me that you are putting too much emphasis on communicating intention through the URI. URI are meant to be meaningless. The simpler are the URI's path, the better. As I commented, simple URIs are less prone to change and as we know, in REST, these identifiers are as good as their faculty to remain unchanged over the time. – Laiv Mar 28 '18 at 6:40
  • @Laiv, Ignore some informations is alright in simplest cases, but can be a problem to document how your API works (imagine a Swagger on that) and even understand, because we will create different kinds of information groups that wont play together (like channel and title for a twitter post). Also the versioning only affect one platform and is easier to maintain the code (divide to conquer). I will add this informations on the post. – Dherik Mar 28 '18 at 10:32
  • 1
    Versioning in REST could be really counterproductive. Overall when the API doesn't implement Hateoas and the consumers are out of your hand to keep update. For instance, mobile apps. May the forest does not prevent you from seeing the trees, URIs could be something like /8asdd8ad-3343-ada9dd-asdads99asdand still make things works. Extract all the meaning possible from the identifier and place it where it should be. in the contract. Whether the contract is reliable or not is a matter of design. – Laiv Mar 28 '18 at 11:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.