Richardson成熟度模型 – RESTful API
Leonard Richardson开发了Richardson成熟度模型,根据API对REST约束的遵守程度来评分。具有高REST合规性评分的API被认为性能更优。
在确定服务的成熟度时,Richardson强调了三个主要因素。它们包括:
- URI
- HTTP方法
- HATEOAS(超媒体)
URI: 统一资源标识符(URI)是Web技术用于标识Web上的资源的唯一字符序列。
HTTP METHODS: 超文本传输协议(HTTP)是一种用于传输超媒体文档的协议。HTTP客户端通过请求消息向服务器发送请求。HTTP定义了一组请求方法来指定对给定资源采取的操作。
- GET: GET方法检索指定资源的表示。
- POST: POST请求向服务器传输数据。
- PUT: PUT方法替换资源的所有现有表示。
- PATCH: PATCH请求对资源进行部分更改。
- DELETE: DELETE方法删除指定的资源。
HATEOAS(超媒体作为应用状态引擎)指的是可发现性。客户端可以仅通过服务器的响应与REST API进行交互。它是自说明的超媒体。客户端无需引用任何文档即可与新API进行交互。
REST服务根据Richardson成熟度模型分为不同的成熟度级别。
- Level 0
- Level 1
- Level 2
- Level 3
LEVEL 0: POX沼泽
Level 0也经常被称为POX(Plain Old XML)。在0级成熟度上,HTTP只用作传输协议。对于0级成熟度服务,我们使用单个URL和单个HTTP方法。我们发送请求到相同的URI来获取和发布数据。只能使用POST方法。例如,一个公司可能有很多客户或用户。我们只有一个端点用于所有的客户。所有的操作都通过POST方法进行。
- 获得数据的请求:POST http://localhost:8080/users
- 发布数据的请求:POST http://localhost:8080/users
LEVEL 1: 基于多个URI的资源和单个动词
在Level 1中,每个资源映射到一个特定的URI。但是,只有一种HTTP方法(POST)用于检索和创建数据。例如,我们需要访问在公司工作的员工。
- 将员工添加到特定部门:
POST/department/<department-id>/employee
- 访问特定员工:
POST/department/<department-id>/employee/<employee-id>
LEVEL 2: 基于多个URI的资源和HTTP动词
在Level 2中,使用正确的HTTP动词发送请求。每个请求都返回一个正确的HTTP响应码。
例如:要获取公司的用户,我们发送一个包含URI的请求
http://localhost:8080/users 服务器会发送正确的响应200 OK。
LEVEL 3: HATEOS
Level 3是最高级别的。它结合了Level 2和HATEOS。它有助于自我文档化。HATEOS指引可以找到新的资源。想象一家中国餐馆作为类比。你点了面条,服务员给你带来了你点的菜,并解释你刚刚点的是什么以及你可以找到其他可供选择的菜品。因此,我们可以将所需的菜品视为JSON数据,而其他菜品则是超媒体。
当API达到级别4时,我们认为它是RESTful的。其他级别只是成为RESTful的阶梯。让我们按照Richardson的成熟模型创建一个RESTFUL API
方法: 我们将创建一个名为gfg-wiki的RESTFUL API。我们将插入文章并发送HTTP请求。在这个过程中,我们将获取、修改和删除文章。将使用Robo3T来进行数据库操作。将使用Postman发送请求。
为了在 node.js 中创建一个RESTFUL API,需要安装:
node: 一个JavaScript运行环境
- 下载链接:https://nodejs.org/en/download/
Robo3t: MongoDB的GUI。我们将使用Robo3T来创建数据库。
- 下载链接:https://robomongo.org/
Postman: 一个API开发和测试平台。
- 下载链接:https://www.postman.com/
Visual Studio Code 或者(其他任何代码编辑器)
- 下载链接:https://code.visualstudio.com/download
JSON 查看器的Chrome扩展
- 下载链接:https://chrome.google.com/webstore/detail/json-viewer-pro/eifflpmocdbdmepbjaopkkhbfmdg
步骤1: 创建一个新的目录,进入终端,并通过以下命令初始化NPM。
npm init -y
步骤2: 安装 body-parser, mongoose, express
- body-parser: 一个在处理请求之前负责解析传入请求体的中间件。
- express: node.js 框架。
- mongoose : Mongoose 将 MongoDB 连接到 Express Web 应用程序。
npm i body-parser mongoose express
步骤3: 在当前目录中创建一个 app.js文件 并设置服务器。我们将导入包到我们的项目中并配置服务器。
app.js
// app.js file
// Setting up the server
// jshint esversion:6
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require('mongoose');
const app = express();
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(express.static("public"));
app.listen(3000, function() {
console.log("Server started on port 3000");
});
步骤4: 在Robo3T上创建一个数据库。考虑一个包含标题和内容的文章数据库。
{
“title” : “gfg”,
“content” : “GeeksforGeeks是一个针对计算机科学极客的门户网站。”
}
{
“title” : “REST”,
“content” : “REST代表REpresentational State Transfer。”
{
“title” : “API”,
“content” : “应用程序编程接口”
}
{
“title” : “richardson-model”,
“content” : ”根据REST约束对API进行评分”
}
{
“title” : “Http”,
“content” : “超文本传输协议(HTTP)是一种用于传输超媒体文档的协议。”
}
- 进入Robo3t并创建一个新的连接。
- 通过点击新连接按钮创建一个名为 gfg-wiki 的数据库。
- 将创建一个名为 ‘gfg-wiki’ 的数据库。现在点击它并创建一个名为 ‘articles’ 的新集合。
- 要插入文档,请点击articles并选择插入文档。
- 逐个复制上述每个文档并插入。
- 要查看所有文档,请点击articles。
如您所见,数据库的结构如下所示:
下面展示如何创建数据库并插入文档。
步骤5: 设置MongoDB并编写文章的模式以创建模型。要设置MongoDB,我们将使用mongoose。我们将连接我们的应用程序到MongoDB的位置,并将数据库名称添加到URL字符串中。默认情况下,MongoDB使用端口27017。
mongoose.connect("mongodb://localhost:27017/gfg-wiki", {useNewUrlParser: true});
模式定义了我们的集合的结构。我们将创建一个名为articleSchema的模式,其中包含两个字段-文章的标题和内容。
const articleSchema = {
title: String,
content: String
};
现在我们将从articleSchema创建一个模型
const Article = mongoose.model("Article", articleSchema);
将以下代码添加到app.js文件中现有的代码中。
app.js
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require('mongoose');
const app = express();
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(express.static("public"));
// connecting gfg-wiki database to our express application
mongoose.connect("mongodb://localhost:27017/gfg-wiki",
{ useNewUrlParser: true });
// Writing schema for articles collection
const articleSchema = {
title: String,
content: String
};
//creating a model around articleSchema
const Article = mongoose.model("Article", articleSchema);
app.listen(3000, function() {
console.log("Server started on port 3000");
});
步骤6: 通过GET方法访问所有文章。我们可以通过发送GET请求,指定资源的路径和处理请求的回调函数来获取所有文章。
app.get(route, (req,res)=>{
})
要检索所有文章,我们必须从数据库中查找并阅读文章。
<ModelName>.find({conditions},function(err,results){
//using the result
});
将以下代码添加到您现有的 app.js 文件中。
// Fetching all the articles
app.get("/articles", (req, res) => {
Article.find((err, foundArticles) => {
if (!err) {
res.send(foundArticles)
} else {
res.send(err);
}
})
})
通过运行以下命令开始您的应用程序
node app.js
输出: 我们可以在localhost:3000/articles访问文章。
步骤7: 使用POST方法创建一个新的文章。我们将创建一个将添加到数据库中的新文章。在这里,客户端向服务器发送数据。
我们还没有前端界面,但是我们有一个可以访问数据库的服务器。我们将使用Postman来测试我们的API,而不是创建一个表单或前端界面。我们的目标是向服务器发送POST请求。
我们将使用POST方法:
app.post(route,(req,res)=>{
...
})
一旦客户端发送了POST请求,我们需要通过req.body获取该数据。
转到Postman,向localhost:3000/articles发送一个POST请求。在body选项卡下,更改编码为form-url编码,并在键中添加标题和内容,以及表示我们想要与请求一起发送的数据的值。
键 | 值 |
---|---|
标题 | HTTP动词 |
内容 | 最常见的HTTP动词是POST,GET,PUT,PATCH和DELETE。 |
我们需要将这篇文章保存在我们的数据库中。
const <constantName>=new <ModelName>({
<fieldName>:<fielddata>,..
});
将以下代码添加到app.js文件中的先前代码中
app.js
// Posting a new article
app.post("/articles", (req, res) => {
const newArticle = new Article({
title: req.body.title,
content: req.body.content
});
// Saving the article
newArticle.save(function(err) {
if (!err) {
res.send("Successfully added a new article.");
} else {
res.send(err);
}
});
})
重新启动您的服务器并使用postman发送POST请求。
输出: 转到Robo3T并刷新您的集合以查看添加的文章。现在我们有一个额外的条目。
步骤8: 获取特定文章。
我们将使用findOne方法从数据库中读取特定文章。
<ModelName>.findone({conditions},(req,res)=>{
});
在这里,我们将使用标题为REST的文章。
在你的app.js文件中添加以下代码。
app.js
// Fetching a specific article
app.get("/articles/:articleTitle", function(req, res) {
Article.findOne({ title: req.params.articleTitle },
function(err, foundArticle) {
if (foundArticle) {
res.send(foundArticle);
} else {
res.send("No articles matching that title was found.");
}
});
})
结果 :我们将在URL中指定文章标题,标题匹配的文章将被显示。
步骤9 :使用PUT方法覆盖文章。
我们希望提交一篇文章的新版本。为了替换现有的文章,我们将发送一个PUT请求。
app.put(route ,(req,res)=>{
...
});
我们将使用Mongoose的更新方法更新文章。
覆盖指定我们想要替换整篇文章。
<ModelName>.update(
{conditions},
{updates},
{overwrite:true}
(err,results)=>{
})
将以下代码添加到您的app.js文件中:
app.js
// Replacing a specific article
app.put("/articles/:articleTitle", (req, res) => {
Article.updateOne({ title: req.params.articleTitle },
{ title: req.body.title, content: req.body.content },
{ overwrite: true },
function(err) {
if (!err) {
res.send("Successfully updated the selected article.");
}
}
);
})
在这种情况下,我们将把标题从API更改为Postman,将其内容从应用程序编程接口更改为Postman是一个API平台。
title: Postman
content: Postman is an API platform
通过向路由localhost:3000/articles/API发送一个put请求
如果服务器找到一个标题为API的参数,它将用新标题替换标题,并用新内容替换内容。
步骤9: 使用PATCH方法更新文章。
我们将使用标题来发送一个PATCH请求来更新现有的文章。要更新文章,我们必须在body标签中提供要更改的字段。
现在我们只更改文章的一个字段而不是整个文章,当我们调用更新方法来更新数据库时,不需要使用覆盖方法。要更新文章,我们必须在body标签中提供要更改的字段。
在你的app.js文件中添加以下代码来修改文章。
app.js
// Updating an article
app.patch("/articles/:articleTitle", function(req, res) {
Article.update({ title: req.params.articleTitle },
{ $set: req.body },
function(err) {
if (!err) {
res.send("Successfully updated article.");
} else {
res.send(err);
}
}
);
})
输出 :它仅更新我们提供的字段。文章标题REST已更新为Restful。
步骤10: 使用DELETE方法删除所有文章。
要从我们的数据库中删除所有文章,我们将使用deleteMany mongoose方法,并从postman发送一个删除请求。
将以下代码添加到您的app.js文件中
app.js
// Deleting all the articles
app.delete("/articles", function(req, res) {
Article.deleteMany(function(err) {
if (!err) {
res.send("Successfully deleted all articles.");
} else {
res.send(err);
}
});
});
输出: 我们将发送一个删除请求到 localhost:3000/articles,以删除我们所有的文章。访问 Robo3T 并刷新您的集合。如果我们从 Postman 发送一个删除请求,我们将不会看到任何文章。
最终的 app.js 文件:
app.js
// app.js file
// Setting up the server
// jshint esversion:6
const express = require("express");
const bodyParser = require("body-parser");
const mongoose = require('mongoose');
const app = express();
app.use(bodyParser.urlencoded({
extended: true
}));
app.use(express.static("public"));
//Connecting gfg-wiki database to our express application
mongoose.connect("mongodb://localhost:27017/gfg-wiki",
{ useNewUrlParser: true });
// Writing schema for articles collection
const articleSchema = {
title: String,
content: String
};
// Creating a model around articleSchema
const Article = mongoose.model("Article", articleSchema);
// Fetching all the articles
app.get("/articles", (req, res) => {
Article.find((err, foundArticles) => {
if (!err) {
res.send(foundArticles)
} else {
res.send(err);
}
})
})
// Posting a new article
app.post("/articles", (req, res) => {
const newArticle = new Article({
title: req.body.title,
content: req.body.content
});
// Saving the article
newArticle.save(function(err) {
if (!err) {
res.send("Successfully added a new article.");
} else {
res.send(err);
}
});
})
// Fetching a specific article
app.get("/articles/:articleTitle", function(req, res) {
Article.findOne({ title: req.params.articleTitle },
function(err, foundArticle) {
if (foundArticle) {
res.send(foundArticle);
} else {
res.send("No articles matching that title was found.");
}
});
})
// Replacing a specific article
app.put("/articles/:articleTitle", function(req, res) {
Article.update({ title: req.params.articleTitle },
{ title: req.body.title, content: req.body.content },
{ overwrite: true },
function(err) {
if (!err) {
res.send("Successfully updated the selected article.");
}
}
);
})
// Modifying an article
app.patch("/articles/:articleTitle", function(req, res) {
Article.update({ title: req.params.articleTitle },
{ $set: req.body },
function(err) {
if (!err) {
res.send("Successfully updated article.");
} else {
res.send(err);
}
}
);
})
// Deleting all the articles
app.delete("/articles", function(req, res) {
Article.deleteMany(function(err) {
if (!err) {
res.send("Successfully deleted all articles.");
} else {
res.send(err);
}
});
});
app.listen(3000, function() {
console.log("Server started on port 3000");
});