2

I have a problem with proper API design. So there's an app where users can track their sport Activities. User can later access and view them. It can be done by making GET /api/activities/{id} request, where id is an id of the activity. Internally I am searching db by user id and activity id, so that another user cannot view another user's Activity.

But then I need to access and process that Activity in another app service. So I need to have access but with that endpoint I won't get it because of not matching user id.

So I can see these solutions:

  1. Check permissions of the requester in the controller and if it's service then query db by just activity id (I don't like it because of possible code complication – need to pass roles to services or provide separate functions for each one and call the right one from controller).
  2. Create separate endpoint(s) for inter-service communication (of course properly secured).
  3. Fetch activity by id always and decide later wheter to return it or not depending on requester (simpler than 1. but there's unnecessary deserialization of objects from db).

What is the best approach and best practises?

2 Answers 2

3

It Depends.

I've worked on projects that go each of these ways.

Having independent end-points (even if they are duplicates) keeps each communication channel independent and stream lined. The con is that you must maintain multiple interfaces that potentially share code paths.

Having a single set of end-points keeps the interface simple. But it complicates Authorisation checking, it cannot be simply folded into Authentication. If you follow this path never have the API behave differently based on the user. It is painful to test, and brittle. Testors assign themselves as a "service" to ping the api via a http tool, they forget then use the website and it errors. Errors are logged as defects, defects are investigated, sometimes for days only to discover it was human error.

The last one is handy when the repository interface is simple (ie CRUD). Sometimes it really is faster/simpler to load the resource then determine if you should return it. On a modern database (sql/nosql) its often faster to have the query perform a check, and only return if it passes.

My personal opinion is to go with option 1 or 2 depending on how much overlap there is. Option 3 is more of an implementation detail, useful but its clouding the waters here.

3

If these were really identical APIs with different auth, there might be a clean way to separate this concern out within the implementation. But I suspect the admin API will have different or additional features from the public API, so I would strongly recommend creating two separate APIs.

When I've done this with a single API, it ends up being complicated and riddled with if/then statements (or similar). It becomes hard to reason about, because the reader of the code isn't sure who's calling each endpoint. I've had new users come to the code and not realize it was also the admin interface, or vice versa.

Also consider:

  • The public API and needs to be documented clearly and publically, and the private API needs a different, private, and perhaps more technical documentation.
  • The auth is different, as you have pointed out. This may evolve and become more complex. What if you want to keep the private API behind a firewall or in a VPN?
  • They will scale and throttle differently. This may be okay at first, but I've had to put different throttling policies dependending on the user-- and it gets complicated fast.
  • Since these have different users, they naturally want to evolve at different rates. The public API wants to be stable, or have clear major version changes; but a private API can evolve faster and more organically.

It will be clearer to everyone involved if these are just separate APIs and named as such. If there is shared functionality-- and it sounds like there is-- you can share code internally, creating a shared service for shared functionality.

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