API design for a user profile

799 views
Skip to first unread message

erewhon

unread,
Feb 23, 2015, 3:11:04 PM2/23/15
to api-...@googlegroups.com

I thought it might be useful to walk through a simple use case that highlights some common design considerations when writing APIs.  It would be really great to hear feedback and alternative views.  Consider an API that allows the following:
  • a user can view their own profile
  • a user can update their own profile's avatar, email and password
  • a user can view other people's public profile (a public profile of another user is a subset of the fields displayed in your own profile)
  • an administrator can view any user's profile, which includes an additional field "accountStatus" (either "enabled" or "disabled").
  • an administrator can change "accountStatus" in addition to modifying the other profile fields.
Naively a first attempt at modelling the above interactions may involve exposing the following resource:

/users/:userId/profile

You might choose to allow OPTIONS, HEAD, GET and PUT.  On GET requests you would have to:
  • determine who is calling you
  • filter your representation based on who is calling you, including links, if any
  • provide response
On a PUT, you would:
  • determine who is calling you
  • figure out what they're trying to change (which means fetching something from the database)
  • apply rules based on a combination of (user, changes) to decide if what they're doing is allowed
  • apply changes, if any
  • provide response (which may just be a 204)
The implementation might get ugly, particularly step 3.  It also produces design problems when you're trying to separate your API/HTTP code from your business layer because you need to load the resource just to isolate what's being changed.  The GET operations are also applying conditional logic to decide what subset of the profile resource it should return.  I suppose one way of addressing this is that you could write some middleware that maps (user, operation) to a function call specific to that use case.  Does anyone know if this kind of routing pattern exists, btw?

Anyway, rather than having conditional logic in our code, we can use separate resources for each of the distinct use cases we have.  So:
  • GET/PUT /user/profile
    • (read/update *your* profile)
  • GET /users/:userId/profile
    • (read another user's profile)
  • GET/PUT /manage/users/:userId/profile
    • (for admins, read/update a user's profile)
Now your implementation knows who should be calling and does not require as much branching logic when providing a response.  This seems cleaner and less prone to change (or at least you're better positioned for change by addition).  It also seems to encourage an API that is more focused on specific use cases and thus better at guiding clients.

I would even consider breaking this API down further.  Consider the administrative task of enabling/disabling an account.  This is likely a separate activity from other kinds of user profile changes and we may want to do some internal things when this happens (send e-mails, events, audit logs, etc).  So maybe it is better to do something like:

GET /manage/users/:userId/profile
PUT /manage/users/:userId/profile (for updates to a user's profile, except the status)
PUT /manage/users/:userId/profile/status


A lot of this comes down to API granularity and what I guess you could call "use case orientation".  It would be really interesting to hear feedback on what factors are important for breaking down a seemingly simple API like this one.

mca

unread,
Feb 23, 2015, 3:23:21 PM2/23/15
to api-...@googlegroups.com
i recommend using a pattern that leads you to *first* expose all the actions and data elements *then* look into which protocol and message model you want to use to implement those actions.  Leonard Richardson and I covered one way to do this in the "RESTful Web APIs" book.

IME, by keeping HTTP and URLs and such *out of the picture* while you come up w/ the use cases and document all data and actions needed, you'll end up w/ a clearer *design* and then have a lot more options when it comes to turning that design into a tangible implementation.

your narration includes a few fields (avatar, email, password, accountStatus) but i'm sure there are more. you also doc a couple actions (view my profile, edit my profile, view a public profile, view anyone's profile, set status of anyone's profile) but these likely are not all of them and some can be conflated when you consider context (user session) will clear up which profiles you can work with, etc. (which you allude to later in your post).

finally, we recommend putting all this captured knowledge into a "profile" that can be used as a guide when implementing both clients and servers. ALPS is the format we use for that.

of course, this is all just one way to approach it. i suspect others here have other advice worth checking out, too. 

cheers.


--
You received this message because you are subscribed to the Google Groups "API Craft" group.
To unsubscribe from this group and stop receiving emails from it, send an email to api-craft+...@googlegroups.com.
Visit this group at http://groups.google.com/group/api-craft.
For more options, visit https://groups.google.com/d/optout.

erewhon

unread,
Feb 23, 2015, 4:17:31 PM2/23/15
to api-...@googlegroups.com
Great suggestions, thanks -- I've actually read that book, along with your Building Hypermedia APIs with HTML5/node book (which I really enjoyed).

I think one of the issues I was trying to tease out of the example above was API granularity and whether other API builders have exposed different resources on the basis of user roles.  I've been writing APIs for a number of years and still find that achieving the correct level of granularity can be difficult.  Is there ever a benefit to retaining less granular APIs (e.g. a single /user/:userId/profile) endpoint over the more granular case I described in the second part of my initial post?

Looking forward to other input on this.  Thanks again for the feedback!

mca

unread,
Feb 23, 2015, 4:26:32 PM2/23/15
to api-...@googlegroups.com

I try to get everything on my action list covered by the most coarse-grained api I can get away with while still supporting a good UX.

Is not a science, takes lots of getting and feedback, and usually means I will be ready to make backwards compatible changes as I learn more about the needed actions introduced over time.

Cheers.

wiki1000

unread,
Feb 25, 2015, 12:13:04 AM2/25/15
to api-...@googlegroups.com
Hello.
Seems easy: your 3 types of information have to be exposed by 3 different resources controlled by 3 different links.
Your client, according to the rights it acquired, will be presented the resources it may see. Thank you, REST. 
Note: any considérations about the number of request/responses in this area shall consider the use of  HTTP2, (it will be this year), which makes all relations between resource modelling and interaction efficiency much more tractable.
Reply all
Reply to author
Forward
0 new messages