personal experience! How much hatred do front-end students have for back-end interfaces?

Author: Li Yi

Source: juejin.im/post/5cfbe8c7e51d4556da53d07f

Foreword

Sometime last year, I wanted to write a complaint about interfaces. At that time, the backend proposed an interface solution, which was very uncomfortable for me to call, but I couldn’t explain why and there was no argument, so I gave up. Recently, because of writing the full stack, the team also encountered some problems about interface design, so it began to think about the best practices for implementing interfaces. After referring to a lot of information, I gradually gained my own understanding of this issue. At the same time, I recalled my past experiences and finally realized where my pain points were at that time.

Since this is just a rant, please forgive me for my unkind attitude next. All examples listed in this article are from my personal experience.

Who should lead the design of the interface

Or to be more straightforward, should the consumer or provider of the interface decide the design of the interface?

“Of course it is the consumer of the interface”

The most paradoxical thing about the “interface” is that the provider went to great lengths to implement it, but it itself (almost) never uses it again. So it is very easy to fall into a self-pleasure situation, because he does not know the quality of the interface at all. It’s like a cook who never tastes his own food. You expect his food to be as good as his cooking skills. The implicit premise above is (in my opinion) that interfaces are absolutely good or bad. Bad interfaces make it difficult for consumers to call and difficult for providers to maintain. They also lead to awkward product behavior and poor user experience.

However, what does the quality of the interface have to do with who leads the design? One of the reasons for bad interfaces is that the provider only solves the problem from the perspective of developers:

Example 1 (Chatty API)

One time it is necessary to implement a function that allows users to create dashboard pages (if you are unfamiliar with dashboard pages, you can imagine that it is a page that combines different charts, such as bar charts, line charts, pie charts, etc. Users can Add the charts you want to the page and manually adjust their size and position. Dashboards are often used to provide an overview of the running status of a product or service). The preliminary design of the back-end interface is that after the user fills in the basic information, adds the chart, and clicks the create button, I need to call the interface twice in succession to complete the creation of the dashboard:

  1. Create an empty dashboard with basic information filled in by the user and the size and position of the chart

  2. Then fill in the specific information of the chart in the dashboard, such as chart type, dimensions and indicators used, etc.

It is obvious that he designed the interface based on his own back-end storage structure. Not only the storage structure, but also the stored procedures are clearly visible. Imagine an extreme situation. Instead of just providing some interfaces for updating database tables, the front-end itself inserts data into the database through the interfaces.

Faced with such low-level interfaces, consumers need to consider the calling steps of the interface and understand the principles behind it when integrating. If the underlying structure of the backend changes, the interface will most likely need to change, and the calling code of the frontend will also need to change accordingly.

Back-end R&D may argue: The back-end uses microservices, and different types of data are stored in different services, so you need to communicate with different services to achieve complete storage. What they never understand is that the back-end implementation leads to the fragmentation of the interface. That is your problem, and this burden should not be transferred to the front-end developers. In fact, it is also indirectly transferred to the users. Don’t bully me for not knowing the backend. At least I understand that adding an Orchestration Layer similar to BFF can solve this problem.

Netflix engineer Daniel Jacobson pointed out in his article The future of API design: The orchestration layer that APIs are nothing more than facing two types of audiences:

  1. LSUD: Large set of unknown developers

  2. SSKD: Small set of known developers

With the trend of product service-oriented, it is very likely that it will be necessary to expose interfaces to public developers, that is, LSUD, like AWS or Github. Not to mention whether the interface solution in the above example will be drowned in spittle, it is very dangerous to expose the details of internal services so obviously.

Therefore, consumers should take the lead when designing interfaces. If consumers fail to give good advice, then providers should at least put themselves in the consumer’s shoes when designing. Or, at least think about it, would you be happy to use an interface you designed yourself?

Using back-end thinking to design the interface is not only reflected in the design of the URI, but may also be reflected in the request parameters and return body structure:

Example 2

Suppose you now need an interface for requesting batches of articles. The interface returns the content of multiple articles at the same time, including the content of these articles, author information, comment information, etc.

Ideally, we expect the data returned to be in articles, such as

articles: [
 {
    id: ,
        author: {},
        comments: []
 },
    {
     ID:
        author: {},
        comments: []
    }
]

However, the return result from the backend may be in entity units:

{
    articles: [],
    authors: [],
    comments: []
}

comments contains comments from different articles. I have to perform a group by operation on them through a field similar to articleId to separate the comments belonging to different articles. Do the same operation for other entities, and finally manually splice them into the articles data structure required by the front-end code.

Obviously this is the result returned according to the back-end library table relationship. Strictly speaking, this is not an anti-pattern. Normalizing data is also encouraged in redux. “But if the original data is not used by the front end, please do not return the original data”. For example, I need to display data in percentage format on the page. Unless the user needs to dynamically adjust the data format, such as thousandths, decimals, or switch precision, etc., otherwise just return me the percentage string directly. Don’t return me the raw floating point data. “The secondary processing of data by the front end will also cause interference to problem troubleshooting.” If any data requires secondary processing by the front end, then all troubleshooting must be initiated from the front end and confirmed by the front end. Then enter the back-end troubleshooting process, which will always occupy the manpower of both ends and delay the progress of the troubleshooting.

About meta information

Example 3:

The backend interface usually returns with meta information. The meta information includes the status of the interface and the reason for failure if it fails, which is convenient for debugging. The data structure of the meta information of the interface provided by the backend is as follows:

{
    meta: {
      code: 0,
      error: null,
      host: "127.0.0.1"
    },
    result: []
}

In my opinion, there are two problems with the above data structure

meta information contains independent status information

In the design of meta information interface that contains status code, a default hidden logic is: the HTTP status code returned by the interface must be 200. Whether the data is really obtained successfully needs to be judged by the custom status code in meta (in other words , the interfaces you see above are actually “interfaces of interfaces”). In the end, in the front-end code, there is no need to judge whether the return is normal through the HTTP code. You only need to judge the meta.code returned in the interface.

But who gives you the confidence to guarantee that the back-end interface will not hang? ! No matter how the backend ensures the robustness of the interface, the frontend still needs to first determine whether the HTTP code is 200, and then determine whether meta.code is consistent with expectations. This has nothing to do with trust and everything to do with the robustness of my program.

Since the interface needs to be evaluated twice anyway, why not combine meta.code and HTTP code into one? What’s more, I still need to maintain an enumeration value of the custom code locally, and I also need to ensure synchronization with the backend. This leads to the next question:

The storage location of meta information

There is nothing wrong with us needing meta information, but we don’t need meta information that much. This is reflected in several points:

  1. Do we really need a field to display meta information parallel to the returned results?

  2. Do we need meta information for every request?

  3. Does meta information have to be in the meta field?

Take the error message of a failed request as an example. The error message will only appear when the interface returns abnormally, but should we always reserve a place for it in a field in the return body?

Regarding the question of where meta information exists, I would prefer to integrate it into the HTTP header. For example, meta.code can be completely replaced by HTTP code. I don’t see the point of always ensuring 200 return and custom code.

As for other meta information, it can be passed through a custom HTTP Header starting with X-. For example

Information about usage frequency limits in the Github API is placed in the HTTP Header:

Status: 200 OK
X-RateLimit-Limit: 5000
X-RateLimit-Remaining: 4999
X-RateLimit-Reset: 1372700873

Design for today

Example 4

We need to design a query interface for the line chart of a certain indicator. The query is in days, which means that the interface will only return query results for the specified date based on the query date. The return data structure provided by the backend is as follows:

{
    data: [
        {
            date: "2019-06-08",
            result: [
                  
            ]
        }
    ]
}

Although the requirement clearly stated that only query results for a certain day would be returned, the backend decided to return an array to me. The reason for this design is to prevent the need to return query results for multiple days if requirements change in the future.

This seems like a very smart decision: “Look, I foreseeably covered a future need!”, but in fact it is extremely stupid: you did cover a need, but it is a need that does not currently exist and may not exist in the future. requirements that occur; and if you really want to write future-proof code, there are thousands of requirements in the future waiting for you to realize them.

The problem is that no one knows whether it will really allow querying of multi-day data at the same time in the future. Even if it is necessary to support querying of multi-day data at the same time one day, the data structure does not necessarily have to be like this. In the field of data analysis, the query requirements we face are not linear from single to multiple. The same is true in other business fields.

The consequence of this is that you spend extra time implementing unnecessary code, and the front end also needs to be implemented with such a data structure. And in future maintenance, everyone who sees that the return body is an array will wonder why there is only one returned result and it needs to be encapsulated in an array. Am I missing something? So I had to put in the effort to verify whether it was really possible to return more data. “APIs and code should be precise, expressing exactly what you want to achieve without ambiguity”

Some people may say, isn’t it just an extra layer of encapsulation? It doesn’t take much effort to implement, so there’s no fuss. Sorry, I am not focusing on this case, but I am emphasizing that in any scenario, no matter how difficult it is to implement, you should not add meaningless code. This is the principle of “don’t do evil for small things”

“Focus on the present” has another dimension:

Example 5

At present, we already have an interface for creating a single article, and now we need to support batch creation of articles. The suggestion given by the backend is: why not adjust a single interface multiple times?

Example 6

There is already an interface that can obtain article-related data, such as content, comments, authors, etc. Now we need to add a new page to display user information. The suggestion given by the backend is: it is better to use the article data interface, which already contains author information, so there is no need to develop a new interface.

The above examples seem to be about reusing interfaces, but in fact they achieve half the result with twice the effort.

In Example 5, although semantically “create five articles” and “create one article five times in a row” are equivalent, this is not the case at the implementation and operational levels. Not to mention that the performance of calling five times is very different from calling it once. The five articles created in batches may have a sequential relationship and may require transaction operations.

Although the effect we achieved in Example 6 can be achieved, this cannot be regarded as reuse of the interface, but can only be regarded as a hack of the interface (the difference between hack and reuse lies in whether the original function of the item is used to do things). And hack interfaces are risky. For interface providers, they are more concerned about the interface serving “orthodox” consumers. In this case, the interface exists to display complete article information. If one day “article information” Changes in requirements are likely to cause author information to change at the same time, reducing fields or even canceling fields. Then they have no obligation to these hack users. “An interface should focus on one thing”

So the ideal thing is to develop independent interfaces for the business you are currently focusing on. In the example of Example 6, we may realize that the code is completely copied from the implementation of another interface when developing an interface that independently requests the author’s information, but the isolation of the interface can bring greater convenience to the maintenance of the function in the long run.

Not limited to REST API

“Interface” is a concept. Under the concept we have many options on how to implement it. At present, it seems that most of the methods are achieved through REST API. There is nothing that REST API cannot do, but in fact, the advancement of technology in recent years has given us more choices. If we choose, it will be more Targeted implementation solutions will achieve better results

For example, in the scenario of real-time data, in theory, the backend (when there is data update) drives the update of the front-end view, which should be a push operation. But in traditional implementation, we have to still implement functions through passive waiting and polling.

For event-driven needs, using WebSocket or Streaming seems to be a better choice. If it is interaction between backends, you can also use WebHook. I’m generally reserved about new technologies, but I have to admit that GraphQL can handle certain needs better than REST APIs. And the support of most vendors for the GraphQL interface shows that it is feasible.

I understand that implementing the API is only a small part of the back-end function implementation. Behind the interface are more modifications to the business logic and changes to the library table structure. It is even said that half of the interface part is implemented by the framework. However, even if there is only a small chance, this link should be perfected.

Conclusion

I can continue to complain endlessly about the poor interface design, but suddenly I feel that there seems to be little point in continuing to write eloquently. To be honest, I am not here to complain, I just want to express that interface design is also very important. It is painful to see at work that many problems can be solved with some very basic skills, but everyone turns a blind eye to them, resulting in a lose-lose situation. The above are some principles and considerations that I think need to be followed in interface design. I believe it can solve most pain points and avoid some problems.

Back-end students, if you are interested in making the interface better, listen more to the feedback from “consumers”. If you have tried to develop applications using third-party interfaces, such as Slack and Github, you will find that their interfaces are constantly iterating. Old interfaces are constantly being eliminated and new interfaces are put into use. This kind of iteration is not based on idle work, but on actual user voices and needs.

Finally, I recommend the books I recently read on API design, which have been of great benefit:

  • Web API design and development

  • Designing Web APIs

  • APIs A Strategy Guide

757d6b929dd33efe5f824be915247dbe.jpeg

Reply [idea activation]to get the activation method of the idea

Reply [Java]Get java related video tutorials and information

Reply [SpringCloud]Get more learning materials related to SpringCloud

Reply [python] Get a complete set of 0 basic Python knowledge manual

Reply [2020]Get a tutorial on 2020java related interview questions

Reply [Join Group]You can join the technical exchange group related to the terminal R&D department

read more

Before using Spring’s BeanUtils, it is recommended that you understand these pitfalls first!

lazy-mock, a lazy tool for generating backend mock data

Try out Huawei Hongmeng OS, my first “hello world”, take off!

ByteDance side: Is i++ thread-safe?

An incident caused by an SQL statement caused a colleague to be fired directly! !

So heartbreaking! Troubleshooting Alibaba Cloud ECS CPU actually reaches 100%

A powerful swagger-ui written in Vue, a bit showy (with open source address)

Believe in yourself, there is nothing impossible, only the unexpected

You get more than just technology here!

c6622332a36b522ead182d01ae3959fa.png

0323342dfc4d7c0d24f3856732721664.gif

If you like it, please leave a comment “Looking” gif”>