本篇将介绍六种最流行的 API 架构风格,分别是 SOAP、RESTful、GraphQL、gRPC、WebSocket 和 Webhook。对于每种 API 架构风格,我们将深入探讨其优点、缺点以及适用场景,并提供相应的 DEMO 以帮助读者更好地理解每种 API 架构的实现方法和运作原理。
API 在现代软件开发中扮演着重要的角色,它们是不同应用程序之间的桥梁,使得这些应用程序可以相互交互。
以下是六种最流行的 API 架构风格:
SOAP(Simple Object Access Protocol) (opens new window) 是一种轻量级协议,用于在去中心化、分布式环境中交换信息。
它是一种基于 XML 的协议,一条 SOAP 消息就是一个普通的 XML 文档,包含下面元素:
Envelope
:定义消息的开始和结束Header
:包含头部信息Body
:包含消息主体Fault
:包含错误信息SOAP 可以与多种其他协议结合使用。
server.js
const soap = require("soap");
const express = require("express");
const fs = require("fs");
const port = 8000;
const xml = fs.readFileSync("service.wsdl", "utf8");
const app = express();
app.get("/", (req, res) => {
res.send("Hello SOAP!");
});
const service = {
MyService: {
MyServicePort: {
sayHello(args) {
return {
msg: `Hello ${args.name}!`,
};
},
},
},
};
app.listen(port, () => {
const soapServer = soap.listen(app, "/wsdl", service, xml);
console.log(`SOAP service listening at http://localhost:${port}/wsdl?wsdl`);
soapServer.log = (type, data) => {
console.log(type, data);
};
});
service.wsdl
<?xml version="1.0" encoding="UTF-8"?>
<!-- <definitions> must be the root of the WSDL document -->
<wsdl:definitions targetNamespace="http://tempuri.org/"
xmlns:s="http://www.w3.org/2001/XMLSchema"
xmlns:soap12="http://schemas.xmlsoap.org/wsdl/soap12/"
xmlns:http="http://schemas.xmlsoap.org/wsdl/http/"
xmlns:mime="http://schemas.xmlsoap.org/wsdl/mime/"
xmlns:tns="http://tempuri.org/"
xmlns:soap="http://schemas.xmlsoap.org/wsdl/soap/"
xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"
xmlns:soapenc="http://schemas.xmlsoap.org/soap/encoding/"
xmlns:wsdl="http://schemas.xmlsoap.org/wsdl/">
<!-- WSDL TYPES: definition of the data types that are used in the web service -->
<wsdl:types>
<s:schema elementFormDefault="qualified" targetNamespace="http://tempuri.org/">
<s:element name="SayHelloRequest">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="1" name="name" type="s:string"/>
</s:sequence>
</s:complexType>
</s:element>
<s:element name="SayHelloResponse">
<s:complexType>
<s:sequence>
<s:element minOccurs="1" maxOccurs="unbounded" name="msg" type="s:string"/>
</s:sequence>
</s:complexType>
</s:element>
</s:schema>
</wsdl:types>
<!-- MESSAGES: defines the data being exchanged between the service and client -->
<wsdl:message name="MyServiceIn">
<wsdl:part name="parameters" element="tns:SayHelloRequest"/>
</wsdl:message>
<wsdl:message name="MyServiceOut">
<wsdl:part name="parameters" element="tns:SayHelloResponse"/>
</wsdl:message>
<!-- PORT TYPES: defines the complete communication operation (one way/round trip) -->
<wsdl:portType name="MyServicePort">
<!-- The operation name must be the same as the one specified in the service object -->
<wsdl:operation name="sayHello">
<wsdl:input message="tns:MyServiceIn"/>
<wsdl:output message="tns:MyServiceOut"/>
</wsdl:operation>
</wsdl:portType>
<!-- BINDING: provides details on how a portType operation will actually be transmitted -->
<wsdl:binding name="MyServiceBinding" type="tns:MyServicePort">
<soap:binding transport="http://schemas.xmlsoap.org/soap/http"/>
<wsdl:operation name="sayHello">
<soap:operation soapAction="sayHello" style="document"/>
<wsdl:input>
<soap:body use="literal"/>
</wsdl:input>
<wsdl:output>
<soap:body use="literal"/>
</wsdl:output>
</wsdl:operation>
</wsdl:binding>
<!-- SERVICE: -->
<wsdl:service name="MyService">
<wsdl:port name="MyServicePort" binding="tns:MyServiceBinding">
<soap:address location="http://localhost:8000/wsdl"/>
</wsdl:port>
</wsdl:service>
</wsdl:definitions>
client.js
const soap = require("soap");
const url = "http://localhost:8000/wsdl?wsdl";
soap.createClient(url, function (err, client) {
if (err) {
console.error(err);
} else {
client.sayHello({ name: "Cell" }, function (err, result) {
if (err) {
console.error(err);
} else {
console.log(result);
}
});
}
});
启动服务:
$ node server.js
SOAP service listening at http://localhost:8000/wsdl?wsdl
运行客户端脚本,可以看到输出:
$ node client.js
{ msg: [ 'Hello Cell!' ] }
服务端输出:
info Handling GET on /wsdl?wsdl
info Wants the WSDL
info Handling POST on /wsdl
received <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tns="http://tempuri.org/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"><soap:Body><SayHelloRequest xmlns="http://tempuri.org/"><name>Cell</name></SayHelloRequest></soap:Body></soap:Envelope>
info Attempting to bind to /wsdl
info Trying MyServicePort from path /wsdl
replied <?xml version="1.0" encoding="utf-8"?><soap:Envelope xmlns:soap="http://schemas.xmlsoap.org/soap/envelope/" xmlns:tns="http://tempuri.org/" xmlns:tm="http://microsoft.com/wsdl/mime/textMatching/"><soap:Body><SayHelloResponse xmlns="http://tempuri.org/"><msg>Hello Cell!</msg></SayHelloResponse></soap:Body></soap:Envelope>
SOAP API 适用于需要高安全性和复杂数据交换的企业级应用程序和 Web 服务场景,但在简单数据交换场景下,可能过于复杂和性能不足。如果需要进行简单的数据交换,可以考虑使用 REST API 或其他更轻量级的协议。
RESTful API (Representational State Transfer API) 是一种基于 HTTP 协议的 Web API,它使用 HTTP 请求来对资源进行操作,如 GET、POST、PUT、DELETE 等,这些操作分别对应着对资源的查询、创建、更新和删除。
server.js
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: true }));
const port = process.env.PORT || 3000;
let users = [
{ id: 1, name: "Alice" },
{ id: 2, name: "Bob" },
{ id: 3, name: "Charlie" },
];
// GET /api/users
app.get("/api/users", (req, res) => {
res.json(users);
});
// GET /api/users/:id
app.get("/api/users/:id", (req, res) => {
const id = parseInt(req.params.id);
const user = users.find((u) => u.id === id);
if (user) {
res.json(user);
} else {
res.status(404).send("User not found");
}
});
// POST /api/users
app.post("/api/users", (req, res) => {
const user = req.body;
user.id = users.length + 1;
users.push(user);
res.json(user);
});
// PUT /api/users/:id
app.put("/api/users/:id", (req, res) => {
const id = parseInt(req.params.id);
const user = users.find((u) => u.id === id);
if (user) {
user.name = req.body.name;
res.json(user);
} else {
res.status(404).send("User not found");
}
});
// DELETE /api/users/:id
app.delete("/api/users/:id", (req, res) => {
const id = parseInt(req.params.id);
const index = users.findIndex((u) => u.id === id);
if (index >= 0) {
users.splice(index, 1);
res.json({ message: "User deleted successfully" });
} else {
res.status(404).send("User not found");
}
});
app.listen(port, () => {
console.log(`Server is listening on port ${port}`);
});
client.js
const axios = require("axios");
const apiUrl = "http://localhost:3000/api";
// 获取所有用户列表
axios
.get(`${apiUrl}/users`)
.then((response) => {
console.log("All users:", response.data);
})
.catch((error) => {
console.error("Failed to get users:", error.message);
});
// 获取指定用户信息
axios
.get(`${apiUrl}/users/1`)
.then((response) => {
console.log("User 1:", response.data);
})
.catch((error) => {
console.error("Failed to get user 1:", error.message);
});
// 创建一个新用户
axios
.post(`${apiUrl}/users`, { name: "David" })
.then((response) => {
console.log("New user:", response.data);
})
.catch((error) => {
console.error("Failed to create user:", error.message);
});
// 更新指定用户信息
axios
.put(`${apiUrl}/users/1`, { name: "Alice Smith" })
.then((response) => {
console.log("Updated user 1:", response.data);
})
.catch((error) => {
console.error("Failed to update user 1:", error.message);
});
// 删除指定用户
axios
.delete(`${apiUrl}/users/1`)
.then((response) => {
console.log("Deleted user 1:", response.data);
})
.catch((error) => {
console.error("Failed to delete user 1:", error.message);
});
启动服务
$ node server.js
Server is listening on port 3000
运行客户端
$ node client.js
All users: [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Charlie' }
]
User 1: { id: 1, name: 'Alice' }
New user: { name: 'David', id: 4 }
Updated user 1: { id: 1, name: 'Alice Smith' }
Deleted user 1: { message: 'User deleted successfully' }
对于大部分的应用程序,RESTful API 是一种非常合适的选择,它具有简单、灵活、可扩展等优点,可以用于开发大部分的 Web 服务和移动应用程序。但对于需要实时性要求较高的应用程序,或者需要处理复杂的业务逻辑和数据处理的应用程序,可能需要使用其他技术或协议。
GraphQL 是一种用于 API 开发的查询语言和运行时环境。它由 Facebook 于 2012 年开发,于 2015 年作为开源项目发布。
GraphQL 可以让客户端精确地指定其需要的数据,而不必取回 API 提供的全部数据。这样可以减少不必要的网络请求和数据传输,提高应用程序的性能和可扩展性。
server.js
const express = require("express");
const { graphqlHTTP } = require("express-graphql");
const { buildSchema } = require("graphql");
// 定义 schema
const schema = buildSchema(`
type Product {
id: ID!
name: String!
description: String!
price: Float!
}
type Order {
id: ID!
products: [Product!]!
total: Float!
}
type Query {
product(id: ID!): Product
products: [Product!]!
order(id: ID!): Order
orders: [Order!]!
}
type Mutation {
createOrder(productIds: [ID!]!): Order
cancelOrder(id: ID!): Order
}
`);
// 模拟数据
const products = [
{ id: "1", name: "iPhone", description: "Apple iPhone", price: 999 },
{ id: "2", name: "iPad", description: "Apple iPad", price: 799 },
{ id: "3", name: "MacBook", description: "Apple MacBook", price: 1299 },
];
const orders = [];
// 定义 resolver
const root = {
// 查询单个商品
product: ({ id }) => {
return products.find((product) => product.id === id);
},
// 查询所有商品
products: () => {
return products;
},
// 查询单个订单
order: ({ id }) => {
return orders.find((order) => order.id === id);
},
// 查询所有订单
orders: () => {
return orders;
},
// 创建订单
createOrder: ({ productIds }) => {
const selectedProducts = products.filter((product) => productIds.includes(product.id));
const total = selectedProducts.reduce((acc, product) => acc + product.price, 0);
const order = { id: orders.length + 1, products: selectedProducts, total };
orders.push(order);
return order;
},
// 取消订单
cancelOrder: ({ id }) => {
const index = orders.findIndex((order) => order.id === id);
if (index === -1) {
throw new Error(`Order with id ${id} not found`);
}
const order = orders[index];
orders.splice(index, 1);
return order;
},
};
// 创建 express app
const app = express();
// 添加 graphql 中间件
app.use(
"/graphql",
graphqlHTTP({
schema: schema,
rootValue: root,
graphiql: true,
})
);
// 监听端口
app.listen(3000, () => {
console.log("GraphQL server running at http://localhost:3000/graphql");
});
启动服务
$ node server.js
GraphQL server running at http://localhost:3000/graphql
服务启动后,可以在浏览器中访问 http://localhost:3000/graphql
,打开 GraphQL Playground,可以在 Playground 中进行 GraphQL 查询和操作。
如,查询所有商品
{
products {
id
name
description
price
}
}
这个查询会返回所有商品的列表,并包含每个商品的 ID、名称、描述和价格。
也可以通过以下查询来获取单个商品的信息:
{
product(id: "1") {
id
name
description
price
}
}
也可以使用以下查询来创建一个新订单:
mutation {
createOrder(productIds: ["1", "2"]) {
id
products {
id
name
price
}
total
}
}
gRPC 是一个高性能、开源的远程过程调用(RPC)框架,由 Google 开发。
该框架使用 Protocol Buffers 作为接口定义语言(IDL),并支持多种编程语言,例如 C++、Java、Python、Go 等。gRPC 提供了基于 HTTP/2 的通信协议,可以实现双向流、请求/响应语义和头部压缩等功能。
sercer1.js
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
// 加载Proto文件
const packageDefinition = protoLoader.loadSync("./calculator.proto", {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
// 加载服务定义
const calculatorProto = grpc.loadPackageDefinition(packageDefinition).calculator;
// 实现服务端方法
function add(call, callback) {
const { a, b } = call.request;
const result = a + b;
callback(null, { result });
}
function subtract(call, callback) {
const { a, b } = call.request;
const result = a - b;
callback(null, { result });
}
// 创建gRPC服务器
const server = new grpc.Server();
// 添加服务
server.addService(calculatorProto.Calculator.service, {
Add: add,
Subtract: subtract,
});
// 启动服务器
server.bindAsync("localhost:50051", grpc.ServerCredentials.createInsecure(), () => {
server.start();
console.log("gRPC server running at http://localhost:50051");
});
calculator.proto
syntax = "proto3";
package calculator;
service Calculator {
rpc Add(AddRequest) returns (AddResponse) {}
rpc Subtract(SubtractRequest) returns (SubtractResponse) {}
}
message AddRequest {
int32 a = 1;
int32 b = 2;
}
message AddResponse {
int32 result = 1;
}
message SubtractRequest {
int32 a = 1;
int32 b = 2;
}
message SubtractResponse {
int32 result = 1;
}
sercer2.js
const grpc = require("@grpc/grpc-js");
const protoLoader = require("@grpc/proto-loader");
const packageDefinition = protoLoader.loadSync("./calculator.proto", {
keepCase: true,
longs: String,
enums: String,
defaults: true,
oneofs: true,
});
const calculatorProto = grpc.loadPackageDefinition(packageDefinition).calculator;
const client = new calculatorProto.Calculator("localhost:50051", grpc.credentials.createInsecure());
function runCalculator() {
const a = 10;
const b = 5;
// 调用 Add 方法
client.Add({ a, b }, (err, response) => {
if (err) {
console.error(err);
return;
}
console.log(`Add Result: ${response.result}`);
});
// 调用 Subtract 方法
client.Subtract({ a, b }, (err, response) => {
if (err) {
console.error(err);
return;
}
console.log(`Subtract Result: ${response.result}`);
});
}
runCalculator();
启动服务 1
$ node server1.js
gRPC server running at http://localhost:50051
启动服务 2
$ node server2.js
Add Result: 15
Subtract Result: 5
举例来说,Google 的基础设施中广泛使用 gRPC,例如 Google Cloud、YouTube、Google 搜索等。另外,Uber 也使用了 gRPC 来构建其微服务架构,通过 gRPC 实现服务间通信,提高了系统的性能和可扩展性。
WebSocket 是一种在客户端和服务器之间建立双向通信的协议,它基于 TCP 协议实现,可以在单个 TCP 连接上提供全双工通信功能,使得客户端和服务器可以实时地交换数据。
server.js
// 引入 ws 模块
const WebSocket = require("ws");
// 创建 WebSocket 服务器
const server = new WebSocket.Server({ port: 8080 });
// 监听连接事件
server.on("connection", (socket) => {
console.log("客户端已连接");
// 向客户端发送消息
socket.send("欢迎连接 WebSocket 服务器");
// 监听客户端发来的消息
socket.on("message", (message) => {
console.log(`收到客户端消息:${message}`);
// 回复客户端消息
socket.send(`服务器已收到消息:${message}`);
});
});
// 输出服务器启动信息
console.log("WebSocket 服务器已启动,监听端口 8080");
client.js
// 引入 ws 模块
const WebSocket = require("ws");
// 创建 WebSocket 客户端
const socket = new WebSocket("ws://localhost:8080");
// 监听连接成功事件
socket.addEventListener("open", (event) => {
console.log("已连接到 WebSocket 服务器");
// 向服务器发送消息
socket.send("你好,WebSocket 服务器");
});
// 监听收到服务器消息事件
socket.addEventListener("message", (event) => {
console.log(`收到服务器消息:${event.data}`);
});
// 监听连接关闭事件
socket.addEventListener("close", (event) => {
console.log("WebSocket 连接已关闭");
});
// 监听连接错误事件
socket.addEventListener("error", (event) => {
console.log("WebSocket 连接发生错误");
});
需要注意的是,WebSocket 对于一些非实时通信的场景可能不太适用,因为它需要建立长连接,并且需要保持连接状态,这可能会占用较多的服务器资源。此外,WebSocket 也需要客户端和服务器端都支持该协议,因此在一些老旧的浏览器或服务器上可能无法正常使用。因此,在选择使用 WebSocket 时需要根据具体的应用场景进行评估和选择。
Webhook 是一种 HTTP 回调机制,它允许应用程序之间实时通信,以便在特定事件发生时自动触发某些操作。具体来说,Webhook 允许应用程序将 HTTP POST 请求发送到指定的 URL,以通知接收方某个事件已发生。Webhook 通常用于自动化工作流程、实时数据同步、实时通知等场景。
典型的应用场景,如在 Github 中 Webhook 可以用于执行自动化测试、自动化部署等操作,当代码仓库中的代码发生变更时,可以自动触发 Webhook,从而执行相应的操作。
假设我们有一个在线商店,当有新订单时,我们需要将订单数据同步到第三方财务系统中。我们可以使用 Node.js 实现一个 Webhook 应用程序来实现这个功能。
server.js
const express = require("express");
const bodyParser = require("body-parser");
const app = express();
// 使用body-parser中间件解析POST请求体
app.use(bodyParser.json());
// 处理Webhook请求
app.post("/webhook", (req, res) => {
const orderData = req.body;
// 将订单数据同步到第三方财务系统中
syncOrderDataToFinanceSystem(orderData);
// 响应Webhook请求
res.send("Webhook received");
});
// 启动服务器
app.listen(3000, () => {
console.log("Webhook server started on port 3000");
});
// 将订单数据同步到第三方财务系统中
function syncOrderDataToFinanceSystem(orderData) {
// 在这里编写将订单数据同步到第三方财务系统的代码
console.log("Order data synced to finance system:", orderData);
}
client.js
const axios = require("axios");
const postData = {
orderId: "123456",
customerName: "John Doe",
totalPrice: 100.0,
};
// 发送Webhook请求
axios
.post("http://localhost:3000/webhook", postData)
.then((response) => {
console.log(`Webhook server responded with status code: ${response.status}`);
console.log(`Response from webhook server: ${response.data}`);
})
.catch((error) => {
console.error(`Error while sending webhook request: ${error}`);
});
Webhook 最适合的场景是需要实时响应的场景,比如需要立即处理某些事件或发送实时通知的场景。此外,Webhook 还适用于需要在应用程序之间自动化触发某些操作的场景,例如将数据同步到不同的系统中。但是,如果安全性是一个问题,或者需要扩展到大量的事件和接收方,可能需要考虑其他方案。
exploring-the-top-6-most-popular-api-architecture-styles (opens new window)