📜 ⬆️ ⬇️

Features of API development: which API is good?

Probably absolutely all readers use the API, working with frameworks, libraries, widgets, as a kind of language of communication between the essence and the main application. And you probably noticed that some APIs are more convenient to use, and some have obvious problems. Vsevolod Shmyrov (@ vsesh ) in his report on Frontend Conf , decoding which you will find under the cut, tried to answer the question which API is good .

The story is based on the Yandex.Maps API development experience, and although it is a JavaScript library, many of the principles and features of its development are applicable to other types of APIs, such as the server API and Standalone libraries. Everything that will be discussed refers specifically to public A PI . If only your colleagues access the API of your library, with whom you can easily tell that something needs to be changed somewhere, then you most likely will not encounter the problems faced by the developers of the public API.

image
')
However, the report will not answer the question whether you need your own API . Hopefully, after reading you weigh the pros and cons and see for yourself whether you need it. Vsevolod will simply tell you what difficulties the API developers face, what problems to solve and what else to do, namely about these four important points:



About the speaker: Vsevolod Shmyrov, senior interface developer at Yandex, directly participated in the development of the Yandex.Maps API and the Yandex map designer.

This is a presentation on the report on Frontend Conf in 2017, some moments, such as the list of supported browser versions, may have changed, but the criteria for a good API are still relevant. Further the story will go on behalf of Vsevolod, pleasant reading.

Keep it simple


The API should be simple , well, preferably logical. It seems that it seemed so easy that it could go wrong. Nevertheless, people put completely different meanings into this word - the degree of simplicity is completely different for everyone. Let's go straight to a negative example so that you understand what I am talking about.

A few years ago, I ran into the API of a single social network. There was a getFriends method.

getFriends( ) => [ ] 

Actually, despite the fact that on the slide I have JS pseudo-code, it was a server endpoint to which you sent a getFriends request with a parameter and received a list of friends. It seems everything is logical, you request friends, you get friends. My code that worked with this API would look something like this:

 var friends = getFriends( ); 

But the developers for some reason decided that this was not enough. They decided to do the following; if you have no friends, you get a Null instead of an empty list.

Well, my code then began to look appropriate:

 var friends = getFriends ( ); If (friends) { // … } else { // … } 

It's okay, in principle, it’s still easy to use. But the developers decided not to stop there and did the following logic: if you have one friend, then this list is not the return of one single friend:

It becomes clear that the API is not easy, illogical. My code has already begun to look like this:

 var friends = getFriends( ); If (isUser(friends)) { /*…*/} elseif (friends) { /*…*/} else { /*…*/} 

In principle, too, nothing terrible, two conditions, no problems. The problem here lies in the other. Absolutely all developers who use the API of this social network will write some additional checks in order to work with their API. Probably somewhere here and you can draw a line and say a bad API and a good API.

A good API is one that does not force you to write too much . You use some method on the API and continue to work with the data, and you do not need to write terrible conditions.

Let's summarize, the interface should be:


By consistency, it means that if the API has methods that work with the same data types, these data types should be described the same way.

 iCanShowYou({x: 1, y: 1}); NoUCant([1, 2]); ButIMust(Point); 

For example, in the Maps API, the data type of the coordinate is often used; they are represented as an array - latitude, longitude. And they are so described everywhere, that is, we have no methods that would accept an XY object, Long Lat objects, or even two objects at all. Coordinates everywhere are represented by an array.

But all of the above, of course, should not go to the detriment of opportunities. It happens that there are some complex tasks that are simply impossible to describe simply, which require many parameters to enter. Or the results of your method in the API depend on a third party. Then each situation should be considered separately, and now I will tell you a little how we solve such problems.

 someAwesomeMethod( elem, /* required */ index, /* = "key" */ startValue, /* = 0 */ endValue /* = 1 */ ); 

In the example above, a method that takes some four arguments. We try to derive the most probable cases and write down the standard values ​​of the parameters so that they solve these cases.

We want the majority of users (in this case, users - developers who use the API) to pass the minimum number of necessary arguments to the method, and to solve complex problems they have already written additional arguments.

 new ymaps.GeoObject( geometry, /* {Object} */ properties, /* {Object} */ options, /* {Object} */ ); 

In this example, we group the parameters by objects. In other languages, it can be a class or an instance of a particular class or interface. That is, a GeoObject can have a specific geometry, it can be a point, a line, a polygon, it has certain properties, such as a name, and it has options, such as color, etc. There is not much difference between all these arguments, but we mathematically divided them to make it more convenient . And frankly, the geometry changes much less frequently than the properties.

 new ymaps.GeoObject(geometry), //  new ymaps.Placemark(coord), //  “Point” 

We also make special software helpers. As I said, an object can be a point, line, polygon. We noticed that, for example, points are used much more often and we have made a special class Placemark, which accepts not abstract geometry, but only a couple of coordinates.

Below is a case of an undetermined result, this is a completely different task. It happens that you still have to crush your crutches , only in the public API everyone will see them, which complicates the matter.

 geolocation.get() .then(function (result) { // success }, function (error) { // error }); 

The geolocation.get method returns a promise, in which will be either the current position of the user or an error. Due to the fact that an error is possible, we had to use the promise in this method, besides this method is asynchronous. Geolocation.get uses browser geolocation by default, which not all browsers support.

It works as follows, the developer writes in the code geolocation.get, this code is executed, and such a window comes out in the browser.



The site user can allow the site to get its location or deny. But in some browsers there were bugs - if a person clicked on a cross or clicked somewhere on the page, this window disappeared and geolocation.get did not return any value, the promise hung without a value. Therefore, we had to twist the time-out here, such a reinforced concrete solution, if within thirty seconds a person didn’t press anything or this window was gone, then we throw a rejection time-out. This is not very beautiful, but nevertheless, it allows us to increase the stability of our code.

Manuscripts do not burn


This section is about backward compatibility . You probably had such a case.
You rest, maybe it's a day off, most likely the night, you are relaxed, suddenly someone calls you and says that nothing works in production, your product has broken. And you somehow remember that the last release was three weeks ago, everything was fine and nothing had to break. Nothing, as they say, foreshadowed trouble.
You urgently connect to the working network, open the debugger, begin to understand what is happening, and suddenly you realize that everything is fine on your part. Some API that you used has broken. More precisely, he did not even break, he just began to work differently, not in the way you expected. This means that backward compatibility has occurred.

backward compatibility


Backward compatibility is a guarantee that the published interface will be supported once in the future . In principle, it would be nice to infinity , but, in fact, in the current major version.

In general, everything is logical, did, it works - do not touch !

To make something new, do it somewhere aside, do not touch the old. But not everything is so simple. Backward compatibility imposes a very strong restriction on the development of any new components , any new interfaces, now I will tell you more about this.

First, when you decide to publish a new interface, you should think about the degree of freedom of the developer .



The more a developer’s degree of freedom, the more support resources are required. Suppose you have one module that can only be used in one way. Then you have a second module, now you can use the first module, you can the second, you can use both modules together. Then you got a hundred modules, and with them a huge number of combinations and unforeseen situations that you can not even imagine. This increases the risk of some unexpected error .

In addition, all this is very expensive to maintain, you have to put additional unit tests, additional integration tests, check everything before each minor version.

If you do decide to publish some entity, you need to think that the interface will be extensible .

Let's take an example. We once had a method, in principle, it still exists, balloon.setPosition. Two coordinates, latitude, longitude could be passed there, which are indicated below by x and y.

 balloon.setPosition( x, y//   ) 

As I said earlier, all coordinates are recorded not by two arguments, but by one, since both of them are a required argument. Therefore, it is better to tie them into one argument [x, y] and mark it as mandatory. Then we thought that it would be nice to allow the projection to be transmitted except for latitude and longitude, we have a default projection, but you can specify some kind of projection of your own. We could have added the Projection option, but then we would probably need to add some more options. Then we decided that we would immediately make the options, specify the projection inside and in the future we will be able to add any other keys. That is, we have made the interface extensible, and it does not block future development.

All that I have just mentioned is needed in order to understand whether your new interface will not become a “blocker” in the future?

Let me go again to the example. Once upon a time we decided to publish the getLayout method in essence overlay. At that time, we had one single Layout, which was the implementations of the HTMLLayout symbol class. We could publish the interface like this:

 overlay.getLayout() // HTMLLayout 

Then in the future we would not be able to create a Layout other than html. Therefore, we have made a method that returns an ILayout interface that will return an HTMLLayout.

 overlay.getLayout() // ILayout // HTMLLayout, CanvasLayout… 

And the developer who uses the API, depending on his needs, can already switch to, for example, CanvasLayout.

Then we thought and added Promise at all, because we realized that we need to do the API component-wise and, if the method becomes asynchronous, we will have the opportunity to add module loading at this point.

Error correction


But nothing is perfect, sooner or later errors leak into any API . It so happens that the wrong architectural decision was made. In our case, it happens that we were guided by some browser API, and it was closed. It happens that they came to the conclusion that some kind of interface is unsuccessful and you need to redo it. In this case, some developers start marking methods as deprecated .

We are not very welcome this practice. You probably encountered when there is an API, where forty major releases for six years, some method appeared in the eleventh version, in the fifteenth became deprecated, and in the twentieth it was completely removed. You have to understand the documentation to understand how this works in general. When deprecated API methods naturally grows very much and the documentation becomes not convenient to use.

We often use aliases , but this is also in principle not a very good solution. For example, we have layouts of icons, and in this case we use both spellings of the word “gray”, as some users write in American, and some in English.

 presets.get('islands#greyicon'); presets.get('islands#grayicon'); 

We did the same with the methods when we found typos.

But the most important thing in backward compatibility is that not everything can be fixed . Periodically, you just have to, as it is sung in my favorite cartoon, release and forget.

Somewhere about two years ago, we were rewriting the backend API from one server technology to another. Developers who use the API, used as follows: by reference

  https://api-maps.yandex.ru/2.1/?lang=ru_RU&mode=debug 

We connected the script to the pages and everything worked fine.

We moved to Node.js and all of a sudden, complaints came from users. It was in an ampersand - some users copied a link with a special “& amp;” symbol, which in html is treated as an ampersand and works well. But some users copied this link as is, that is, such a request was not sent to the server. When this happened the following error, instead of the parameter “mode” came “amp; mode”. The old backend handled this, because it considered the “;” character to be a separator of get parameters along with the ampersand character. That is, there came a lang, mode and a bunch of empty amp.

We looked at how many users made such requests to us and realized that we cannot explain to everyone that doing so is wrong. Therefore, we simply added middleware, which removes the amp from get parameters and only after that processes them. Yes, it is stupid, but you have to live with it.

Major releases


But not everything is so hopeless, an error in backward compatibility will not haunt you for the rest of your life . Only to the end, to the next major version.



Somewhere about three years ago we decided to release a major version of API 2.1 to update the list of browsers we support. API 2.0 version was very old, it supported even IE 6 - you understand what a pain it is. But, unfortunately, in 2.1 we still support IE 8, because three years ago it was a fairly current browser (now IE 8 is already in weak support).
To be honest, I will tell you a secret, only between us, on weekdays the share of IE 8 is somewhere around 4%, and on the weekend it sags less than 1%. If you have an IE in your business, be renewed, enough to endure.
So, updating the list of browsers is also a breakdown of backward compatibility, in older browsers the API will not work, so we called this version 2.1. At the same time, we decided to go for all serious and break backward compatibility even where it was not necessary. We cleaned the code of all those aliases and the crutches that we needed to maintain due to backward compatibility, removed some of the old polyfiles for IE 6.

It is clear that the major version is not a panacea , the release of a new major version is also a stress for developers who use your API. They will have to re-clean the documentation, so still major versions are better not to indulge. The release of the major version will still force them to rewrite the code, which means that your API would not be so good if you did not force the users to do it. A partial solution is a special mode of operation.

"Special mode" work


JavaScript has a strict 'use strict' mode; when you write the same code, it visually looks the same, but is treated a little differently. We have a similar mode of operation, although it serves a completely different purpose.

  https://api-maps.yandex.ru/2.1/?lang=ru_RU&coordorder=longlat 

We have a get coordinate parameter, which can be controlled by the order of coordinates in the array, about which I spoke earlier. By default, we have latitude, longitude. You can convey longitude, latitude (latlong). Different geo-systems use a different coordinate order. Accordingly, by specifying the get parameters, a different operation mode is obtained.

The whole API works a little differently, and in the calculations it needs to be taken into account.

What else can you say about backward compatibility? Backward compatibility affects not only the code, as one might think.



If you are developing a visual API, that is, an API that shows something, then you have backward compatibility to the visual component . Not so long ago, we released a new minor version of the API, which updated the map controls. They have become brighter, flatter, as is now fashionable, but they have practically not changed their size. Why did we do that? We could, in theory, make huge buttons, make them semicircular, change icons, or even remove the icons altogether, but this would violate backward compatibility.

When we created the controls in the API, we thought it would be good to give developers the opportunity to add their own controls, and among ours. That is, not on top, but right in the middle. Accordingly, if we made the standard layout of the buttons more, then the developers on the sites would have gone layout. So even this has to be tolerated and taken into account when creating a new minor version.



I also want to note that very often backward compatibility breaks down in cascade. In our case, something in the browser breaks - something breaks in the API.



We had such a little sad funny case, when in one browser, which already has about 80% share of users, the sign in the MouseEvent event suddenly changed. The scrolling of the mouse wheel began to come not plus, but minus. Naturally, when a person tried to bring the map with the mouse wheel, she began to move away from him. In two days, until the browser fixed it, we received about three thousand emails per day. In order for you to understand, usually even on the most stressful days, about ten letters arrive, that is, people were really uncomfortable with the fact that the map suddenly changed its behavior.

Own among strangers


This section is devoted to the JavaScript API, but nevertheless it is also quite interesting. The API works in a strange context, what does this mean? Suppose there is a first site that uses the maps API, and everything works well there. There is a second site that uses the API, but it also uses the jQuery library - everything also works fine. And there is a third site that uses the maps API and some evil.js, which for some reason redefines the basic methods .



In the example below, the error is that indexOf does not return minus one. Apparently the developer of this method decided to support IE 6, did not verify the existence of a real indexOf, and wrote this:

 Array.prototype.indexOf = function (item) { for (var i = 0; i < this.length; i++) if (item ==this [i]) return i; }; 

API naturally in this case breaks. Or, for example, we came across this code:

 * { transition: 2s all ease; } 

Someone for some reason wrote such a CSS selector that leaves absolutely everything on the page to be animated.

As part of this article, I will not delve into how to deal with it, and in principle this is not always possible, you can find more details here .

But I want to emphasize that this, too, need to spend resources. Usually, this happens with us in the following way: the user's complaint comes in support of the fact that something is not working with the attached link; we open it, we see that something is broken in the API; We start watching CSS, JS and it is not always convenient. It will also have to spend time if you are developing a JS library or an API that is embedded in someone else's context.

You do not know your product


The API, as I said before, is a product, and you don’t know how to use your product.Sometime in version 2.0 we were faced with the fact that so many people created such small cards, but we just didn’t think about this case.



The map is tiny, about 150 * 150 pixels, and on the left map all the controls on each other ran. And we just, when we created 2.0, didn’t think about it, well, who needs such a tiny map. But when we saw it, in 2.1 we worked on it, and in the right picture everything is already placed. We made adaptive elements - this is a very simple solution; the wider the map, the wider the elements.



Accordingly, on large maps, the search is fully displayed, and on small buttons, upon clicking on which the search crawls out. But this became possible only after putting the metrics on the own API methods.

Metrics


If you are developing an API, you need to constantly collect information about using the API. Maybe it seems to you that some module is important, but in fact almost no one uses it, and then you can, for example, devote less time to it. It is also important to understand exactly how your API is used.

In our case, the size of the map was an important parameter. We were faced with the fact that people created both small cards and very large ones - there are values ​​on the order of 5000 * 4000. Apparently, such a size map is displayed on some stand, but I can not even imagine how much it opens.

And, of course, you need to collect environmental information.In our case, these are browsers. You need to know which browsers you need to support, which do not, which can already be translated into weak support.

Think like ...


To better understand how people use your API, you need to use the API yourself. You know, it's not just abstract to know how it works, but to try to come up with a task that a developer would solve using your API. For example, not so many years ago I tried to make an application for Vkontakte - a map on which people could mark where they traveled before.



The idea is simple, you can: put a label, open a map of friends and share it on the wall (it was four years ago). And while I was making the application, I found a lot of bottlenecks in the documentation, where it was not very clear how this could be implemented. Yes, I understand that the API developer himself criticizes the documentation, it sounds a bit strange, but it was just necessary .
I tried to clear my mind, in this state I opened the documentation and tried to think like a developer-user.
Plus, I could find some fairly critical API work bugs inside the Iframe simply because such a case had not occurred to us, and it turned out to be important. Vkontakte opens html applications via Iframe, respectively, the Iframe opens from another domain and features of work, for example, with events, which we, fortunately, have taken into account.

Document it


In any story about the API, it's probably impossible to avoid the topic of documentation, although it is quite tedious. No API can do without documentation.
An API without documentation is not an API.
Documentation is a document that confirms those public methods that should have the property of backward compatibility.



Our documentation is written by the developers themselves via JSDoc. We have, as a guide to all methods, and some introductory articles, for example, how to connect the API or how to create a map.



But experience shows that people are not very interested. Yes, if you wish, they can go into the documentation, but when the developer has not worked with your API yet, he will go to the search engine and type in “create delivery card”, “mark delivery card” or something like that. Accordingly, the documentation on such a request will not be issued, because it tells about more abstract things. Therefore, you need to create special stands.and collect popular cases there, we have such a stand is a sandbox.



There you can look at live examples of using the API: on the left is the code, on the right is the result. You can even correct something, and then export it to JSFiddle. But even here it turns out that everything is not as simple as it seemed.

Once we noticed such cards.



At first glance, everything is in order, but if you look closely, you can see that for some reason there are two elements for changing the scale (on the left is the scale, the buttons on the right). We began to think why it happened, and it turned out that it was in one of our examples. We wanted to make an example with the controls on the map and thought it would be a good idea to add two zoom controls: “look, we have a large zoom control and a small one, it works like this”. The developers simply copied the code as is and pasted themselves onto the page. After that, we decided that in the examples we will make the most lively cases . That is, if there is a search case, then there will only be a search, no additional cool options that most developers may not need.

Call me maybe


Of course, no API yet is possible without feedback . Need to communicate with developers who use your API. We have a club for this, there API developers communicate with developers who use this API. Of course, we have technical support, but nevertheless, some questions are redirected directly to the developers, because who else can answer the questions that arise for programmers, except as programmers. We also have clubs in social networks, and in principle, any feedback is also a very important call .



If you often complain that it is not clear how to use any method, and people cannot always clearly formulate their idea, then you should think that it is possible to change something in the API itself, change it in the documentation or make some new example in the sandbox. It is always a very good indicator.

Sell ​​me that pen


Naturally, besides a sandbox, documentation, and means for getting feedback, you need to make some advertising pages. Often the question of choosing an API is not on the developer himself, but on his manager or manager, who may not be a programmer, all these scary characters scare him, he needs beautiful landing pages on how your API works.



It also needs to spend time.

Even easier


I started the story with the item “Keep it simple”, but you can go even further and not force the developer to program at all . The most popular case can be resolved through some special tool. For example, our popular case is simply displaying a map on a page or displaying a map on a page with some sort of label. Therefore, we made a constructor.



A person opens a map in the browser, clicks labels, draws lines and polygons, sets a description and gets the code to paste into the site.



Everything is logical, but not everything is as simple as it seems at first glance.

Constructor


In essence, the constructor is a simplified version of the API , even non-developers can use it, so we give less “freedom” in it . And this is a deliberate step. Now I will explain.

 <script src=" .../constructor/?um={id}&width=514&height=326 "></script> 

When a user receives a code for insertion into a site, he does not receive the same code that he would program through the API. It receives a script that connects a special module that loads the API itself and, based on the data specified in the constructor, creates a map. In this case, the user can control a small number of parameters, for example, the width and height of the map. If he created a map in the constructor, then he can no longer interact with it on the page, put another center, add a label, and so on.

We did this on purpose, because this is a very good way to get around the limitations of backward compatibility.. If a person can not control anything, as in this case, then we can do anything. For example, in this widget we quietly switched the version from 2.0 to 2.1 and did not receive any complaints at all. If we left the possibility to edit something programmatically, we would give a greater degree of freedom to the developers and, accordingly, this would lead to some uncertainties and errors.

So maybe you don’t need an API, you may need a tool that allows you to do something instead of an API. In our case, if developers need a lightweight case, we recommend the designer. If it's more complicated, let's say you need to make a side menu next to the map and somehow interact with it - then you need an API.

Total


As I already said:


Finally, a couple of interesting links:


The next Frontend Conf soon, on May 28 and 29 , as always, awaits us a lot of interesting and even holivarny, for example, here are some accepted reports:

  • Chris Lilley (W3C). WebFonts in 2018: Everything Changes.
  • Victor Rusakovich (GP Solutions). Why do we rewrite the application to Elm, and who knows?
  • (KeepSolid). , .

, — 4 5 Frontend Conf Moscow , . 3 , ++, .

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


All Articles