7

In the 14th century, the English logician Occam said in his Commentary on the Book of Proverbs, "Don't waste too much, do what can be done with less". This phrase was later reduced to "Occam's Razor", which is: Do not multiply entities unless necessary. Occam's razor has his application in various fields. It is not an axiom, and there is no rigorous derivation process, but it is a very effective means of solving problems that has been proved in practice.

In the programming world, there are so many things that we take for granted, I believe that existence is reasonable, and I also believe that existence has premises, and premises can change or even disappear over time. Now I want to discuss with you the things that should be razor-shaved in our front-end projects.

The service layer in the front-end project

In a front-end project, the following file directories are generally included:

  • containers: page
  • components: components
  • utils: utility methods
  • routes: routes
  • services: data services
  • index.js entry file

Our business code is basically in the container components, utils and routes are also essential, but if we think about it carefully, we will find that there is a services folder, which is called the data service layer, and we deal with the backend . Is this layer really needed?
Let's take a look at how everyone uses the service.

// services  文件夹下的 accoutService.js

import { post } from '@/utils/request';

// 获取账号列表
export const getAccountsList = params => post('/api/accounts.json', params);
// 新增账号
export const insertAccount = params => post('/api/insertAccount.json', params);
// 更新状态
export const updateAccount = params => post('/api/updateAccount.json', params);
// 校验账号查询
export const checkAccount = params => post('/api/checkAccount.json', params);

-------- 使用 ---------

import { getAccountsList } from '@/services/accountService'

const App = () => {
  const [name, setName] = useState('');
    useEffect(() => {
      getAccountsList().then((res) => {
       setName(res.name);
    });
  }, [])
  return <div>{name}</div>;
};

From the above code, we can see that the services file is basically some template code, and occasionally there are some rare data conversions. These contents are all non-business related to our business code. Is it really necessary to write these template control codes?

What is included in the service?

  • Data conversion logic convertHandler
  • Data request tool request
  • Request address definition url
  • global interceptor interceptor
  • Additional function openApi

Data conversion logic convertHandler: It is not universal. Some requests require different conversion logic on different pages. These conversion logics are usually written in the code of the calling location. I also recommend doing this, because data conversion is also a part of this. A container function, and in order to facilitate testing, it is recommended to add handler.js to extract the conversion logic.

Data request tool request: It mainly encapsulates various requests, and this part needs to be unified. Non-business related, can be raised.

Request address definition url: This part is strongly related to business and should not be placed in the service, but as a configuration of the service, input from the outside.

Global interceptor interceptor: handles some common business status codes, such as editing success 10001, this part is also strongly business-related and relatively complex, but it can be described by configuring schame, which will be discussed later.

Additional function openAPI: If the interface of your system wants to be reused by other systems, for example, the interface of the MTEE basic platform needs to be reused to the operation platform, then the front end needs to provide domain materials, and requests will be sent in the domain materials, and the request must be resolved across domains , login, authorization issues, openAPI came into being.

In summary, it can be seen that the service layer only needs some unified logic processing and configuration files to be clearly described, and even we can simplify the service layer to

$$service = request + config$$

my service package

Therefore, I hope to design such a service package, which needs to contain the following functions:

ask

Support common get post jsonp requests, and additional methods for these requests, such as debounce, throttle, caching, loading and other functions. You can also provide your favorite hooks API.

Interface configuration

An interface includes domain name domain, address path path, request method method, parameter params, and switches for some common functions, such as enabling debounce { debounce: true } . In the parameter configuration, you can add the basic attributes of the parameter, such as whether { require: true } is required, so that the necessary parameters can be checked in the package, which can ensure that illegal data is transmitted to the background.

environment switch

Environment switching is a non-business-related function that should not be hard-coded into the code and brought online. It should be just a configuration, as far as possible to be separated from the code, so it is a good way to use a browser plug-in to switch. The service package can be designed to receive a domainMap. This domainMap comes from a variable under window.GlobalConfig. The browser plug-in can dynamically change this variable to switch the environment.
image.png

gateway forwarding

We write code to pursue reuse, from code block reuse to component reuse, to business capability reuse, and one carrier of business capability reuse is domain materials. There are many interface requests in a domain material. If we disassemble the components originally in the business code as domain materials, we have to package the service layer in the project, so that we can send requests and process some unification exception. What I mentioned above is to make the service layer into a package. When others use it, they only need to pass in the configuration, which is also for the scene of domain materials.
After this, we have to solve a problem: the interface cross-domain problem caused by the use of domain materials in different sites. Our current solution is to build a node-based gateway at the front end for interface forwarding and authentication. This process will be integrated in the service package, and external users only need to configure whether to open the gateway or not. He does not need to know how the gateway is forwarded at all, just like writing components under his own site.

interface documentation

When we take over other projects, it is always difficult to find his interface documentation, because the documentation and the code are separated, the maintenance of the documentation is also lagging behind, and even the link to the documentation is gradually lost. So code and documentation should go together, preferably code as documentation. You may think that using comments is enough, but programmers always ask others to write comments, but they don't like to write them themselves. If writing comments can be like writing code, it may be able to standardize the behavior of this part. For example:

{
    name: '获取账号',
    domain: DOMAIN.TAOBAO,
    url: '/api/getAccount.json',
    method: METHOD.GET,
    params: {
      userId: {
        name: '策略包id',
        type: PARAM_TYPE.STRING,
        required: true,
      },
    },
    response: {
        name: '账户名字'
    },
  },

Here, the form of the document is standardized in the form of a configuration file, and it can also be combined with a browser plug-in to view the currently used interface document through the plug-in.

exception interception

Exceptions are divided into server exceptions and business exceptions. Server exceptions are generally represented by http status codes, such as 400, 500, etc.; business exceptions need to be represented by codes in the body. In real business practice, we found that it is easy for us to write a general interceptor to deal with server exceptions, but for business exceptions, it is relatively complicated. There are several problems:

  • Many backends are not used to using code to return the corresponding business code to represent different states.
  • The front-end directly uses the message returned by the back-end to display to the user. There are two problems here. ① The back-end needs to introduce a third-party library to internationalize the message. ② The message defined by the back-end is not the user language, and the user generally cannot understand it. Therefore, the participation of a third-party system is required here. It provides a mapping relationship between business codes and front-end actions. For example, the back-end returns code: 10000, and the front-end should pop up and display the message. The defined json is as follows:
{
  code: 10000,
  message: '编辑失败',
  debug: '后端数据库读写异常,堆栈信息:',
  showType: 'openDialog'
}

The message here can return different languages according to different locales. showType represents the action type of the front end, which is enumerable. There must be an action that does not do any action and transmits it directly. This third-party system can configure actions with different codes, which is conducive to refined management of exceptions and gives users a better experience.

fall to the ground

Practice is the only criterion for testing truth. Based on the above ideals, my service package has also been formed, and it is very simple to use it. Only two steps are required:
① Configuration file
② import package
③ Call in business code

configure

// 配置文件 account.js

import { METHOD, PARAM_TYPE } from '@ali/hulu-service';

export const DOMAIN = {
   TAOBAO: '//taobao.com',
   ALIPAY: '//alipay.com',
};

export default {
  getAccount: {
    name: '获取账号',
    domain: DOMAIN.TAOBAO,
    url: '/api/getAccount.json',
    method: METHOD.GET,
    params: {
      userId: {
        name: '策略包id',
        type: PARAM_TYPE.STRING,
        required: true,
      },
    },
    response: {
        name: '账户名字'
    },
  },
};

import package

import HService from '@ali/hulu-service';
import account from './account';

// 初始化service
const service = HService.init({
  urls: [
      account,
  ]
});

export default service;

call API

import Service from './service';

const App = () => {
  const [name, setName] = useState('');
    useEffect(() => {
      Service.getAccount().then((res) => {
       setName(res.name);
    });
  }, []);
  return <div>{name}</div>;
};

export default App;

At the same time, based on browser plug-ins, you can quickly switch environments, view interface documents, etc.

think about boundaries

At the beginning, we talked about Occam's razor. If it is not necessary, do not add entities. The premise of this is that there are clear and independent entities. If our entities are linked and coupled with each other, how can we shave off unnecessary entities?
In fact, no matter what software architecture is done, it is necessary to clearly distinguish the boundaries, that is, what is the positioning of a module, which functions should be done, and which are not. A very important basis here is whether it is easy to change. Which are business, often changing, which are non-business, generally unchanged. Our code is often bad because the boundaries are not clear, or the boundary principles are not consistent. The engineering code is coupled with business, and the business code is mixed with engineering (such as environmental judgment). The bad smell of code is accumulated little by little, and this bad start is that the initial architecture design boundary is not clear, and the code is not used to define the specification.
Resisting the corruption of code is a long road, there is no silver bullet, but it does improve one's systems thinking.

Author: ES2049 / Blackstone
The article can be reproduced at will, but please keep the original link.
We welcome you with passion to join ES2049 Studio , please send your resume to caijun.hcj@alibaba-inc.com.

ES2049
3.7k 声望3.2k 粉丝