⬆️ ⬇️

API creation

A few weeks ago, we publicly released the Gauges API . Despite the already existing Gauges , there was quite a bit of work at the time of writing the API. It is necessary to understand the details in detail.



1. Write documentation while creating an API.


We made a mistake by preparing the documentation after the API was almost ready. The problem is that documenting sucks. Leaving this routine for later, when you’re happy to release the API, makes the work doubly difficult. Fortunately, we had those who went through this before.



2. Be permanent


When writing the documentation of our API, we noticed a lot of inconsistent moments. For example, in some places we returned a hash, and in others an array. Understanding the problem, we began to create some rules.

')

To solve an array / hash problem, we chose to always return a hash as the answer. This is the most flexible solution, focusing on our future tasks. We were able to enter new keys, without need to convert the response from our service or release a new version of the API.



Replacing arrays with hashes meant that we needed a namespace for arrays with keys. We further noted that not everything had its own namespace. And again, we came up with the rule. In this case, all top-level objects must have a namespace, but children of these objects or sets of several objects are not required namespaces.

{users:[{user:{...}}, {user:{...}}]} //  {users:[{...}, {...}]} //  {username: 'jnunemaker'} //  {user: {username:'jnunemaker'}} //  


Well, you understand. Persistence is important. Always follow the same format.





3. Provide URLs


Most of my work with open source software has been wrapping various APIs. And one thing annoyed me a lot, it was generating urls. Each resource must know the URLs important to it. For example, users in Gauges have several URLs that can be called to get some data:

 { "user": { "name": "John Doe", "urls": { "self": "https://secure.gaug.es/me", "gauges": "https://secure.gaug.es/gauges", "clients": "https://secure.gaug.es/clients" }, "id": "4e206261e5947c1d38000001", "last_name": "Doe", "email": "john@doe.com", "first_name": "John" } } 


The previous JSON is the response from the / me resource. It returns data about the authenticated user as well as URLs for updating itself (self), getting all the statistics (/ gauges), and getting all the API clients (/ clients). Suppose your next query / gauges. Each gauge (statistics) returns a few more addresses for more information.

 { "gauges": [ { // various attributes "urls": { "self":"https://secure.gaug.es/gauges/4ea97a8be5947ccda1000001", "referrers":"https://secure.gaug.es/gauges/4ea97a8be5947ccda1000001/referrers", "technology":"https://secure.gaug.es/gauges/4ea97a8be5947ccda1000001/technology", // ... etc }, } ] } 


We think it will prove its usefulness. And see in the future whether this will work well.



4. Presentation of data


And finally, never use to_json and his friends from the controller or from get / post / put synatra blocks. After you start calling to_json with: methods,: except,: only, or with other options, you might want to move it all to a separate class.



For Gauges, we call these classes presenters. For example, here is a simplified version of UserPresenter.

 class UserPresenter def initialize(user) @user = user end def as_json(*) { 'id' => @user.id, 'email' => @user.email, 'name' => @user.name, 'first_name' => @user.first_name, 'last_name' => @user.last_name, 'urls' => { 'self' => "#{Gauges.api_url}/me", 'gauges' => "#{Gauges.api_url}/gauges", 'clients' => "#{Gauges.api_url}/clients", } } end end 


Nothing wrong. Simple ruby ​​class located in the app / presenters. Here is an example of how the route for / me looks in our Sinatra app.

 get('/me') do content_type(:json) sign_in_required {:user => UserPresenter.new(current_user)}.to_json end 


This is a simple presentation layer that makes it easy to test answers in detail using unit testing. And we also have one integration test, which confirms that everything works correctly. I think this small layer is a breath of fresh air.



I am sure that none of the above was shocking or super-inspiring, but I hope the article will save some time for your next public API.

Source: https://habr.com/ru/post/133821/



All Articles