完善的 API 的 4 个基本特征
API

> 
> 创建一个 API（应用程序接口），我们所要做的远远不止是让它能“正常工作”。
> 
> 
> 


![](/data/attachment/album/202106/14/155547zs3mffismifqqi59.jpg "Looking at a map")


如果你正在构建基于 C/S 模型的应用程序，那么你需要一个应用程序接口（API）。API 就是一种非常清晰而又明确的定义，它是一个<ruby> 进程 <rt>  process </rt></ruby>与另一个进程之间明确定义的边界。Web 应用中我们常见的边界定义就是 REST/JSON API。


虽然很多开发者可能主要关注在如何让 API 正常工作（或功能正常），但却还有一些“非功能性”的要求也是需要他们注意的。所有的 API *必须具备* 的 4 个非功能性的要求是：


* 安全
* 文档
* 验证
* 测试


### 安全


在软件开发中，安全是最基本的要求。对于 API 开发者来说，API 的安全性主要包含以下 4 个方面：


1. HTTPS/SSL 证书
2. 跨域资源共享
3. 身份认证与 JSON Web 令牌
4. 授权与作用域


#### 1、HTTPS/SSL 证书


Web 应用的黄金标准是使用 SSL 证书的 HTTPS 协议。[Let's Encrypt](https://letsencrypt.org/) 可以帮你达到这一目的。Let's Encrypt 来自于非营利性的互联网安全研究小组（ISRG），它是一个免费的、自动化的、开放的证书颁发机构。


Let's Encrypt 的软件会为你的域（LCTT 译注：包含域名、IP 等信息）生成中央授权证书。而正是这些证书确保了从你的 API 到客户端的数据载荷是点对点加密的。


Let's Encrypt 支持证书管理的多种部署方案。我们可以通过查看 [文档](https://letsencrypt.org/docs/) 来找出最适合自己需要的方案。


#### 2、跨域资源共享


<ruby> 跨域资源共享 <rt>  Cross-origin resource sharing </rt></ruby>（CORS）是一个针对浏览器的安全策略预检。如果你的 API 服务器与发出请求的客户端不在同一个域中，那么你就要处理 CORS。例如，如果你的服务器运行在 `api.domain-a.com` 并且接到一个来自 `domain-b.com` 的客户端的请求，那么 CORS 就会（让浏览器）发送一个 HTTP 预检请求，以便查看你的 API 服务是否会接受来自此客户域的客户端请求。


[来自 MDN 的定义](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)：



> 
> “跨域资源共享（CORS）是一种基于 HTTP 头的机制，这种机制允许服务器标记除自身源外的其他任何来源（域、方案或端口）。而对于这些被服务器标识的源，浏览器应该允许我们从它们加载资源。”
> 
> 
> 


![CORS 原理](/data/attachment/album/202106/14/155608k4ys6c7llzme4fye.png "CORS principles")


*([MDN文档](https://developer.mozilla.org/en-US/docs/Web/HTTP/CORS)， [CC-BY-SA 2.5](https://creativecommons.org/licenses/by-sa/2.5/))*


另外，有很多用于 [Node.js](https://nodejs.org) 的辅助库来 [帮助 API 开发者处理 CORS](https://www.npmjs.com/search?q=CORS)。


#### 3、身份认证与 JSON Web 令牌


有多种方法可以验证你的 API 中的认证用户，但最好的方法之一是使用 JSON Web 令牌（JWT），而这些令牌使用各种知名的加密库进行签名。


当客户端登录时，身份管理服务会向客户端提供一个 JWT。然后，客户端可以使用这个令牌向 API 发出请求，API 收到请求后，从服务器读取公钥或私密信息来验证这个令牌。


有一些现有的库，可以帮助我们对令牌进行验证，包括 [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken)。关于 JWT 的更多信息，以及各种语言中对其的支持库，请查看 [JWT.io](https://jwt.io)。



```
import jwt from 'jsonwebtoken'

export default function (req, res, next) {
    // req.headers.authorization Bearer token
    const token = extractToken(req)
    jwt.verify(token, SECRET, { algorithms: ['HS256'] }, (err, decoded) => {
        if (err) { next(err) }
        req.session = decoded
        next()
    })
}

```

#### 4、授权与作用域


认证（或身份验证）很重要，但授权同样很重要。也就是说，经过验证的客户端是否有权限让服务器执行某个请求呢？这就是作用域的价值所在。当身份管理服务器对客户端进行身份认证，且创建 JWT 令牌时，身份管理服务会给当前客户提供一个作用域，这个作用域将会决定当前经过验证的客户的 API 请求能否被服务器执行。这样也就免去了服务器对访问控制列表的一些不必要的查询。


作用域是一个文本块（通常以空格分隔），用于描述一个 API 端点的访问能力。一般来说，作用域被分为资源与动作。这种模式对 REST/JSON API 很有效，因为它们有相似的 `RESOURCE:ACTION` 结构。（例如，`ARTICLE:WRITE` 或 `ARTICLE:READ`，其中 `ARTICLE` 是资源，`READ` 和 `WRITE` 是动作）。


作用域的划分让我们的 API 能够专注于功能的实现，而不是去考虑各种角色和用户。身份访问管理服务可以将不同的角色和用户分配不同的权限范围，然后再将这些不同的作用域提供给不同的 JWT 验证中的客户。


#### 总结


当我们开发和部署 API 时，安全应该一直是最重要的要求之一。虽然安全性是一个比较宽泛的话题，但如果能解决上面四个方面的问题，这对于你的 API 来说，在生产环境中将会表现得更好。


### 文档


*有什么能比没有文档更糟糕？过期的文档。*


开发者对文档真的是又爱又恨。尽管如此，文档仍然是 API 定义是否完善的一个关键部分。开发者需要从文档中知道如何使用这些 API，且你创建的文档对于开发者如何更好地使用 API 也有着非常巨大的作用。


创建 API 文档，我们需要关注下面三个方面：


1. 开发者入门文档（自述文件/基本介绍）
2. 技术参考（规范/说明书）
3. 使用方法（入门和其他指南）


#### 1、入门文档


在构建 API 服务的时候，你需要明确一些事情，比如：这个 API 是做什么的？如何建立开发者环境？如何测试该服务？如何提交问题？如何部署它？


通常我们可以通过自述（`README`）文件来回答上面的这些问题，自述文件一般放在你的代码库中，用于为开发者提供使用你项目的最基本的起点和说明。


自述文件应该包含：


* API 的描述
* 技术参考与指南的链接
* 如何以开发者的身份设置你的项目
* 如何测试这个项目
* 如何部署这个项目
* 依赖管理
* 代码贡献指南
* 行为准则
* 许可证
* 致谢


你的自述文件应该简明扼要；你不必解释每一个方面，但要提供足够的信息，以便开发者在熟悉你的项目后可以进一步深入研究。


#### 2、技术参考


在 REST/JSON API 中, 每一个具体的<ruby> 端点 <rt>  endpoint </rt></ruby>都对应一个特定的功能，都需要一个具体的说明文档，这非常重要。文档中会定义 API 的描述，输入和可能的输出，并为各种客户端提供使用示例。


[OpenAPI](https://spec.openapis.org/oas/v3.1.0) 是一个创建 REST/JSON 文档的标准, 它可以指导你完成编写 API 文档所需的各种细节。OpenAPI 还可以为你的 API 生成演示文档。


#### 3、使用方法


对于 API 的用户来说，仅仅只有技术说明是不够的。他们还需要知道如何在一些特定的情况和场景下来使用这些 API，而大多数的潜在用户可能希望通过你的 API 来解决他们所遇到的问题。


向用户介绍 API 的一个好的方法是利用一个“开始”页面。“开始”页面可以通过一个简单的用例引导用户，让他们迅速了解你的 API 能给他们能带来的益处。


#### 总结


对于任何完善的 API，文档都是一个很关键的组成部分。当你在创建文档时，你需要关注 API 文档中的如何入门、技术参考以及如何快速开始等三个方面，这样你的 API 才算是一个完善的 API。


### 验证


API 开发过程中经常被忽视的一个点就是验证。它是一个验证来自外部来源的输入的过程。这些来源可以是客户端发送过来的 JSON 数据，或者是你请求别人的服务收到的响应数据。我们不仅仅要检查这些数据的类型，还要确保这些数据确实是我们要的数据，这样可以消除很多潜在的问题。了解你的边界以及你能控制的和不能控制的东西，对于 API 的数据验证来说是一个很重要的方面。


最好的策略是在进入数据逻辑处理之前，在你能控制的边界的边缘处进行数据的验证。当客户端向你的 API 发送数据时，你需要对该数据做出任何处理之前应用你的验证，比如：确保 Email 是真实的邮件地址、日期数据有正确的格式、字符串符合长度要求。


这种简单的检查可以为你的应用增加安全性和一致性。还有，当你从某个服务接收数据时，比如数据库或缓存，你需要重新验证这些数据，以确保返回的结果符合你的数据检查。


你可以自己手写这些校验逻辑，当然也可以用像 [Lodash](https://lodash.com) 或 [Ramda](https://ramdajs.com/) 这样的函数库，它们对于一些小的数据对象非常有用。像 [Joi](https://joi.dev/)、[Yup](https://github.com/jquense/yup) 或 [Zod](https://github.com/colinhacks/zod/tree/v3) 这样的验证库效果会更好，因为它们包含了一些常见的验证方法，可以节省你的时间和精力。除此，它们还能创建一个可读性强的模式。如果你需要看看与语言无关的东西，请看 [JSON Schema](https://json-schema.org/)。


#### 总结


数据验证虽然并不显眼和突出（LCTT 译注：跟 API 的功能实现以及其他几个方面比），但它可以帮你节省大量的时间。如果不做验证，这些时间将可能被用于故障排除和编写数据迁移脚本。真的不要相信你的客户端会发送干净的数据给你，也不要让验证不通过的数据渗入到你的业务逻辑或持久数据存储中去。花点时间验证你的 API 收到的数据和请求到的响应数据，虽然在前期你可能会感到一些挫折和不适，但这总比你在后期花大量时间去做各种数据收紧管制、故障排除等要容易得多。


### 测试


测试是软件开发中的最佳实践，它应该是最主要的非功能性的要求。对于包括 API 在内的任何项目，确定测试策略都是一个挑战，因为你自始至终都要掌握各种约束，以便相应的来制定你的测试策略。


<ruby> 集成测试 <rt>  Integration testing </rt></ruby>是测试 API 的最有效的方法之一。在集成测试模式中，开发团队会创建一个测试集用来覆盖应用流程中的某些部分，从一个点到另一个点。一个好的集成测试流程包括测试 API 的入口点以及模拟请求到服务端的响应。搞定这两点，你就覆盖了整个逻辑，包括从 API 请求的开始到模拟服务器的响应，并返回数据给 API。


虽然使用的是模拟，但这种方法让能我们专注于代码逻辑层，而不需要去依赖后端服务和展示逻辑来进行测试。没有依赖的测试会更加可靠、更容易实现自动化，且更容易被接入持续集成管道流。


集成测试的实施中，我会用 [Tape](https://github.com/substack/tape)、[Test-server](https://github.com/twilson63/test-server) 和 [Fetch-mock](http://www.wheresrhys.co.uk/fetch-mock/)。这些库让我们能够从 API 的请求到数据的响应进行隔离测试，使用 Fetch-mock 还可以将出站请求捕获到持久层。


#### 总结


虽然其他的各种测试和类型检查对 API 也都有很好的益处，但集成测试在流程效率、构建和管理时间方面却有着更大的优势。使用 Fetch-mock 这样的工具，可以在服务边界提供一个干净的模拟场景。


### 专注于基础


不管是设计和构建应用程序还是 API，都要确保包含上面的四个基本要素。它们并不是我们唯一需要考虑的非功能性需求，其他的还包括应用监控、日志和 API 管理等。即便如此，安全、文档、验证和测试这四个基本点，对于构建任何使用场景下的完善 API 都是至关重要的关注点。




---


via: <https://opensource.com/article/21/5/successful-apis>


作者：[Tom Wilson](https://opensource.com/users/tomwillson4014) 选题：[lujun9972](https://github.com/lujun9972) 译者：[ywxgod](https://github.com/ywxgod) 校对：[wxy](https://github.com/wxy)


本文由 [LCTT](https://github.com/LCTT/TranslateProject) 原创编译，[Linux中国](https://linux.cn/) 荣誉推出


OK
If you are building an application that uses some variation of a client/server model, you need an application programming interface (API). An API is a clearly defined boundary between one process and another. A common boundary in web applications is a REST/JSON API.

While developers may be mainly focused on making the API work (or function), there are some "non-functional" requirements that need their attention. Four *must-have* non-functional requirements for all APIs are:

- Security
- Documentation
- Validation
- Testing

## Security

Security is an essential requirement in software development. There are four areas for API developers to include regarding security:

- HTTPS/SSL certificates
- Cross-origin resource sharing
- Authentication and JSON Web Tokens
- Authorizations and scopes

### 1. HTTPS/SSL certificates


The gold standard for the web is HTTPS using SSL certificates, and [Let's Encrypt](https://letsencrypt.org/) can help you achieve this. It is a free, automated, and open certificate authority from the non-profit Internet Security Research Group (ISRG).

Let's Encrypt's software generates central authority certificates for your domain. These certificates ensure payloads of data from your API to the client are encrypted from point to point.

Let's Encrypt supports several deployment options for certificate management; check out its [documentation](https://letsencrypt.org/docs/) to find the right solution for your needs.

### 2. Cross-origin resource sharing

CORS is a browser-specific security policy preflight check. If your API server is not in the same domain as the requesting client's domain, you will need to deal with CORS. For example, if your server is running on **api.domain-a.com **and gets a client request from **domain-b.com**, CORS sends an HTTP precheck request to see if your API service will accept client-side requests from the client's domain.

"Cross-origin resource sharing (CORS) is an HTTP-header based mechanism that allows a server to indicate any other origins (domain, scheme, or port) than its own from which a browser should permit loading of resources."


![CORS principles CORS principles](https://opensource.com/sites/default/files/uploads/cors_principle_1.png)

There are many helper libraries for [Node.js](https://nodejs.org) to [help API developers with CORS](https://www.npmjs.com/search?q=CORS).

### 3. Authentication and JSON Web Tokens

There are several approaches to validate an authenticated user in your API, but one of the best ways is to use JSON Web Tokens (JWT). These tokens are signed using various types of well-known cryptographic libraries.

When a client logs in, an identity-management service provides the client with a JWT. The client can then use this token to make requests to the API. The API has access to a public key or a secret that it uses to verify the token.

There are several libraries available to help verify tokens, including [jsonwebtoken](https://github.com/auth0/node-jsonwebtoken). For more information about JWT and the libraries that support it in every language, check out [JWT.io](https://jwt.io).

```
import jwt from 'jsonwebtoken'
export default function (req, res, next) {
// req.headers.authorization Bearer token
const token = extractToken(req)
jwt.verify(token, SECRET, { algorithms: ['HS256'] }, (err, decoded) => {
if (err) { next(err) }
req.session = decoded
next()
})
}
```


### 4. Authorizations and scopes

Authentication (or identity verification) is important, but so is authorization, i.e., *does the verified client have the privilege to execute this request?* This is where **scopes** are valuable. When the client authenticates with the identity management server and a JWT token is created, having the identity management service provide the scopes for the given authenticated client can enable the API service to determine if this verified client request can be performed without having to perform an additional costly lookup to an access control list.

A scope is a text block (usually space-delimited) that describes the access capability of an API endpoint. Normally, scopes are broken down between Resources and Actions. This pattern works well for REST/JSON APIs since they are very similarly structured in a RESOURCE:ACTION format (e.g., ARTICLE:WRITE or ARTICLE:READ, where ARTICLE is the resource and READ and WRITE are the actions).

This allows the API to focus on function and not roles or users. The identity access management service can relate roles and users to scopes, then provide the scopes to the client in a verified JWT.

### Summary

When building and deploying APIs, security should always be one of the most important requirements. While security is a broad topic, addressing these four areas will position your API well for production environments.

## Documentation

*What's worse than no documentation? Outdated documentation.*

Developers have a love–hate relationship with documentation. Still, documentation is a crucial part of an API's definition of success. Developers need to know how to use the API, and the documentation you create plays a huge role in educating developers on how to best use it.

There are three areas to focus on in API documentation:

- Developer onboarding (READMEs)
- Technical reference (Specifications)
- Usage (Getting started and other guides)

### 1. Developer onboarding

When building an API service, you need to specify things like: What does the API do? How do you set up a developer environment? How do you test the service? How do you submit an issue? How do you deploy it?

The usual way to answer these questions is with a README file. It is the file in your code repository that gives developers a starting point for working with your project.

A README should contain:

- A description of the API
- Links to technical references and guides
- How to set up the project as a developer
- How to test the project
- How to deploy the project
- Dependency management
- Contribution guide
- Code of conduct
- License
- Gratitude

Be concise in your README; you do not have to explain every aspect but give enough information so that developers can drill deeper as they become familiar with your project.

### 2. Technical reference

In a REST/JSON API, every endpoint is a specific function with a purpose. It is important to have technical documentation that specifies each endpoint; defines the description, inputs, and outputs that can occur; and provides examples for various clients.

REST/JSON has a specification standard called [OpenAPI](https://spec.openapis.org/oas/v3.1.0), which can guide you through the details required to document an API. OpenAPI can also generate presentation documentation for your API.

### 3. Usage

Your API's users want more than just technical specifications. They want to know how to use your API in specific situations or cases. Most potential users have a problem and they are looking to your API to solve it.

A great way to introduce users to your API is with a "getting started" page. This can walk the user through a simple use case that gets them up to speed quickly on the benefits of your API.

### Summary

Documentation is a key component of any successful API. When creating documentation, think about the three areas of focus—onboarding, technical, and usage—cover those bases, and you will have a well-documented API.

## Validation

One of the most often overlooked aspects of API development is validation. Validation is the process of verifying input from external sources. These sources might be a client sending JSON or a service responding to your request. More than just checking types, ensuring that the data is what it is supposed to be can eliminate many potential problems. Understanding your boundaries and what you do and don't have control over is an important aspect of validation.

The best strategy is to validate at the edges before your logic takes place. When a client sends your API some data, apply validation before you do anything else with that data. Make sure an email is an actual email address, a date is properly formatted, a string meets length requirements.

This simple check will add safety and consistency to your application. Also, when you receive data from a service, like a database or a cache, revalidate it to make sure the returned result meets your data checks.

You can always validate by hand or use utility function libraries like [Lodash](https://lodash.com) or [Ramda](https://ramdajs.com/). These work great for small data objects. Validation libraries like [Joi](https://joi.dev/), [Yup](https://github.com/jquense/yup), or [Zod](https://github.com/colinhacks/zod/tree/v3) work even better, as they contain common validations that can save time and effort and create a very readable schema. If you need something language-agnostic, look at [JSON Schema](https://json-schema.org/).

### Summary

Validation is not sexy, but it can save a ton of time that would otherwise be spent troubleshooting and writing data migration scripts. Don't make the mistake of trusting your client to send clean data; you don't want bad data leaked into your business logic or persistent data store. Take the time and validate your API endpoints and service responses. While it may cause some frustration upfront, it is much easier to loosen the reigns than to tighten them later.

## Testing

Testing is a best practice for software development and should be a primary non-functional requirement. Defining a test strategy can be a challenge for any project, including APIs. Always understand your constraints and define your strategy accordingly.

Integration testing is one of the most effective methods for testing APIs. In this pattern, the development team creates a test to cover some part of the application flow, from one specific point to another. A great integration test flow includes testing the API's entry point and mocking the request point to the service. By picking those two points, you cover the entire logic, from the beginning of the API request to the service request, and the mock service gives you a response to hand back to the API response.

Although it uses mocks, this method allows you to focus on the code in the logic layer and not depend on back-end services or presentation logic to run the test. Having no dependencies makes running the test much more reliable, easier to automate, and simpler to include in your continuous integration pipeline.

One setup I use for integration testing uses [Tape](https://github.com/substack/tape), [Test-server](https://github.com/twilson63/test-server), and [Fetch-mock](http://www.wheresrhys.co.uk/fetch-mock/). These libraries enable me to run isolated tests against API endpoints from the request to the response, with Fetch-mock catching the outbound request to the persistence layer.

### Summary

While all types of testing and type checking are beneficial to APIs, integration testing offers the largest benefit in terms of effectiveness vs. time to build and manage. Using tools like Fetch-mock can provide clean mocking scenarios at the service boundary.

## Focus on the fundamentals

As you design and build your application and API, make sure to include these four fundamentals. These are not the only non-functional requirements to consider; others include application monitoring, logging, and API management. Even so, security, documentation, validation, and testing are crucial focus points for designing and building a successful API for any use case.

## Comments are closed.