If you happen to develop API for non-trivial app with complex business logic beyond CRUD directly mapped to the database tables (i.e. typical Active Record pattern) you were probably wondering many times how to handle these cases and what's the best way to do it. There are quite a few solutions to this problem: you could add another endpoint for handling given use case, add non-RESTful action to already existing endpoint or you can add a magic param to the payload that would force non-standard scenario in the API. Let's take a closer look at the these solutions and discuss some advantages and disadvantages of each of them.

Our use case: creating drafts and articles

Let's start with some feature that will be simple enough for the blog post, but still interesting enough that we will have several ways to solve some the problem. Creating drafts and published articles (both referring to some Article model) sounds good enough: for drafts and published articles we are going to have different business validations and we will also need to have a possibility to transit from one state to another. Let's assume that the drafts don't have any required attributes at all and published articles require presence of: title, content and author_id. Besides creating both type of articles, we will also need to update both type of articles while maintaining current state and have a possibility to transit from drafts to published articles. To add some extra logic articles will also have published_at attribute, which is not directly settable via API, but will be automatically set server-side when making a transition from draft to published state or directly creating published article.

Magic param

Let's start with the simplest strategy that I call magic param or virtual param. Basically, to force a different scenario we simply send some extra param, let's name it published. In such case we will only have a single endpoint: Articles with create and update actions and depending on the value of published param we will either run a logic for creating / updating drafts or published articles.

Making transition from draft to published state is going to be the same, we simply need to send published param with true value and make sure other requirements are met (title's, content's and author's presence).

Obviously, this approach looks pretty simple from the client perspective, everything is driven by sending a magic param and this solution is pretty easy to integrate with some client-side data layers like Ember Data. But personally I don't find this solution really clean server-side. Having one interface (endpoint) for multiple purposes (where the logic is conditionally driven by a specific value of some param) adds some extra complexity on the design. Expecially in this case where the client knows exactly that we want to handle either drafts or published articles. For some features it may be good to hide some internal details server-side, but here it's not the case. So the solution to this problem might be...

Adding extra endpoint

With extra endpoint we clearly separate interfaces between both types of articles. Clearly, the logic on server is now much less complex. If we want to create a draft, we just use Drafts endpoint. If we need to update a published article, we simply use (surprise, surprise) Articles endpoint.

As a side-effect of such design decision we can actually do some cool things, e.g. in index actions we could return only one type of article, either drafts or published ones, depending on the endpoint.

The disadvantage of this approach is that it may not be that easy to handle it client-side with frameworks' data layers, which in most cases are kind of equivalents of Active Record pattern, but in API world.

Adding extra endpoint solved one of the issues: having the same interface for different kind of logic. But we still need to have a possibility to make a transition from draft to published article. One way would be to simply use update action from Published Articles endpoint, but this would mean using endpoint for actually different resource, which looks really weird and not proper, especially after separating Articles to two different endpoints. We could still handle it with magic published param, but this time only in Drafts endpoint. Or we could consider doing yet another thing, which is...

Adding extra action

This way doesn't sound really RESTful, but I find it pretty elegant. To handle transition from draft to published we can simply add publish action to Drafts endpoint and let the API handle all the necessary logic.

What I like about combining different endpoints with extra actions is that everything is explicitly defined: every part of API has its' own interface and there's no magic and no implicit assumptions. It's also more flexible and removes some complexity server-side. The disadvantage of this approach is that it actually moves complexity to the client - having extra actions, again, may not be that easy to handle neatly by data layers (if you happen to use Ember and Ember Data, you can play with adapters and buildURL function to make it smooth).

Wrapping up

There are couple of different ways of handling complex logic in non typical CRUD scenarios in your API: using magic param, adding extra endpoints, adding extra method and each of them has its' own advantages and disadvatages on both server-side and client-side.

posted in: API, Rails, Ruby on Rails, Ruby, Architecture