1. 前言
可以这么说的是, 任何一种非强制性约束同时也没有 "标杆" 工具支持的开发风格或协议 (仅靠文档是远远不够的), 最终的实现上都会被程序员冠上 "务实" 的名头, 而不管成型了多少个版本, 与最初的设计有什么区别. DDD 是如此, 微服务是如此, REST 也是如此.
虽然这也不难理解, 风格从一开始被创造出来后, 便不再属于作者了. 所以仍然把你的符合以下标准
满足以资源形式定义定义 Uri
满足以 HTTP 谓词语义增删改查资源
符合命名要求
......
的 "不标准" web API 看作是 RESTful 的, 也未尝不可. 毕竟, 谁在乎呢?
更深层次的讨论参见 Why Some Web APIs Are Not RESTful and What Can Be Done About It https://www.infoq.com/articles/web-api-rest . 什么才是真正的 REST Api 并不是本文的重点 (Github Rest API v3 https://developer.github.com/v3/ ), 笔者在后文讨论的具体实现, 也只是符合目前流行的 "RESTful" 直觉设计.
2. HTTP 谓词
谓词 | 释义 | 幂等性 | 安全性 |
---|---|---|---|
HEAD | 用于获取资源的 HTTP Header 信息 | 是 | 是 |
GET | 用于检索信息 | 是 | 是 |
POST | 用于创建资源 | 否 | 否 |
PUT | 用于更新或替换完整资源或批量更新集合。对于没有 Body 的 PUT 动作,请将 Content-Length 设置为 0 | 是 | 否 |
DELETE | 用于删除资源 | 是 | 否 |
PATCH | 用于使用部分 JSON 数据更新资源信息(在一个请求里可搭载多个动作)。PATCH 是一个相对较新的 HTTP 谓词,在客户端或服务器不支持 PATCH 动作时,也可以使用 Post/Put 更新资源 | 否 | 否 |
3. PATCH & JSON Patch
结合上述 HTTP 谓词, 通常情况下, 更新部分资源的部分数据时, 有以下四种做法:
使用 PUT 谓词, 尽可能使用完整对象来更新资源 (即根本不使用 PATCH ).
使用 JSON Merge Patch https://tools.ietf.org/html/rfc7396 更新部分资源的部分数据 (需要使用指定 MIME
application/merge-patch+json
来表示).
使用 PATCH 谓词和 JSON Patch https://tools.ietf.org/html/rfc6902 (需要使用指定 MIME
application/json-patch+json
来表示)
如果请求不以 MIME 的语义定义的方式修改资源, 使用具有合理描述的 POST 谓词.
我相信大部分系统中, 采取的都是第 1 种和第 4 种做法, 而本文的主题则是第 3 种做法.
在 RFC 5789 https://tools.ietf.org/html/rfc5789 (PATCH method for HTTP) 中, 有一个关于 PATCH 请求的小例子:
- PATCH /file.txt HTTP/1.1
- Host: www.example.com
- Content-Type: application/example
- If-Match: "e0023aa4e"
- Content-Length: 100
- [description of changes]
- [description of changes]
代表对目标资源的一系列操作, 而 JSON Patch 则是描述操作的文档格式.
- // 示例 json 文档
- {
- "a":{
- "b":{
- "c":"foo"
- }
- }
- }
- // JSON Patch 操作
- [
- { "op": "test", "path": "/a/b/c", "value": "foo" },
- { "op": "remove", "path": "/a/b/c" },
- { "op": "add", "path": "/a/b/c", "value": [ "foo", "bar" ] },
- { "op": "replace", "path": "/a/b/c", "value": 42 },
- { "op": "move", "from": "/a/b/c", "path": "/a/b/d" },
- { "op": "copy", "from": "/a/b/d", "path": "/a/b/e" }
- ]
在这个 JSON Patch 的例子中, op 代表操作类型, from 和 path 代表目标 json 的层级路径, value 代表操作值. 相关语义想必大家都能直接读出来, 更多的信息请参考 What is JSON Patch? http://jsonpatch.com/ 和 RFC JSON Patch https://tools.ietf.org/html/rfc6902 .
示例应用
示例程序 https://github.com/wddpct/articles/tree/master/JsonPatch/AspNetCoreJsonPatch 引入了 swagger,MongoDB,docker-compose 等功能, 关于 JsonPatch 的部分则使用微软官方的 https://github.com/aspnet/JsonPatch 编写, 该库支持 add,remove,replace,move,copy 方法, 实现并不困难. 实际使用时, 直接以
JsonPatchDocument<T>
作为包装即可.
MongoDB 客户端推荐注册为单例.
- public interface IMongoDatabaseProvider
- {
- IMongoDatabase Database { get; }
- }
- public class MongoDatabaseProvider : IMongoDatabaseProvider
- {
- private readonly IOptions<Settings> _settings;
- public MongoDatabaseProvider(IOptions<Settings> settings)
- {
- _settings = settings;
- }
- public IMongoDatabase Database
- {
- get
- {
- var client = new MongoClient(_settings.Value.ConnectionString);
- return client.GetDatabase(_settings.Value.Database);
- }
- }
- }
- /* Startup/ConfigureServices.cs */
- public void ConfigureServices(IServiceCollection services)
- {
- ...
- services.AddSingleton<IMongoDatabaseProvider, MongoDatabaseProvider>();
- ...
- }
appsettings.json 文件中的数据库配置部分则为:
- {
- "ConnectionString": "mongodb://mongodb",
- "Database": "ExampleDb"
- }
docker-compose.yml 对 web 应用和 MongoDB 的配置如下:
- version: '3.4'
- services:
- aspnetcorejsonpatch:
- image: aspnetcorejsonpatch
- build:
context: .
- dockerfile: AspNetCoreJsonPatch/Dockerfile
- depends_on:
- - mongodb
- ports:
- - "8080:80"
- mongodb:
- image: mongo
- ports:
- - "27017:27017"
启动时, 定位到 docker-compose.yml 所在文件夹, 运行 docker-compose up, 然后在浏览器访问
localhost:8080/swagger
, 应用在启动后会自动创建 ExampleDb 数据库并插入一条数据. 笔者也写了一个获取信息的接口 / api/Persons, 返回值如下:
- [
- {
- "name": "LeBron James",
- "oId": "5af995a5b8ea8500018d54b7"
- }
- ]
然后再使用返回的 oId 请求 / api/Persons/{id}(
UpdateThenAddThenRemoveAsync
) 接口, body 的 JsonPatch 描述则用:
- /* body */
- [
- {
- "value": "Daby",
- "path": "FirstName",
- "op": "replace"
- },
- {
- "value": "Example Address",
- "path": "Address",
- "op": "add"
- },
- {
- "path": "Mail",
- "op": "remove"
- }
- ]
- /* PersonsController.cs */
- [HttpPatch("{id}")]
- public async Task<PersonDto> UpdateThenAddThenRemoveAsync(string id,
- [FromBody] JsonPatchDocument<Person> personPatch)
- {
- var objectId = new ObjectId(id);
- var person = await _personRepository.GetAsync(objectId);
- personPatch.ApplyTo(person);
- await _personRepository.UpdateAsync(person);
- return new PersonDto
- {
- OId = person.Id.ToString(),
- Name = $"{person.FirstName} {person.LastName}"
- };
- }
其他相关代码另请查阅. 不过需要再提一点的是, Visual Studio 15.7 版本对 docker-compose.yml 的文本语法解析有些问题, 详见 MSBuild failing to parse a valid compose file https://github.com/Microsoft/DockerTools/issues/98 , 比如以下代码将无法编译:
- environment:
- - ASPNETCORE_ENVIRONMENT=Development
- - ASPNETCORE_URLS=http://0.0.0.0:80
- - ConnectionString=${MONGODB:-mongodb://mongodb}
- - Database=ExampleDb
参考文献
JSON Patch http://jsonpatch.com/
Github v3 API https://developer.github.com/v3/
来源: https://www.cnblogs.com/Wddpct/p/9039466.html