能够这么说的是,任何一种非强制性约束同时也没有“标杆”工具支持的开发风格或协议(仅靠文档是远远不够的),最终的实现上都会被程序员冠上“务实”的名头,而无论成型了多少个版本,与最初的设计有什么区别。DDD 是如此,微服务是如此,REST 也是如此。html
虽然这也不难理解,风格从一开始被创造出来后,便再也不属于做者了。因此仍然把你的符合如下标准git
的“不标准” Web API 看做是 RESTful 的,也何尝不可。毕竟,谁在意呢?程序员
更深层次的讨论参见Why Some Web APIs Are Not RESTful and What Can Be Done About It。什么才是真正的 REST Api 并非本文的重点(Github Rest API v3),笔者在后文讨论的具体实现,也只是符合目前流行的“RESTful”直觉设计。github
谓词 | 释义 | 幂等性 | 安全性 |
---|---|---|---|
HEAD | 用于获取资源的 HTTP Header 信息 | 是 | 是 |
GET | 用于检索信息 | 是 | 是 |
POST | 用于建立资源 | 否 | 否 |
PUT | 用于更新或替换完整资源或批量更新集合。对于没有 Body 的 PUT 动做,请将 Content-Length 设置为 0 |
是 | 否 |
DELETE | 用于删除资源 | 是 | 否 |
PATCH | 用于使用部分 JSON 数据更新资源信息(在一个请求里可搭载多个动做)。PATCH 是一个相对较新的 HTTP 谓词,在客户端或服务器不支持 PATCH 动做时,也可使用 Post/Put 更新资源 | 否 | 否 |
结合上述 HTTP 谓词,一般状况下,更新部分资源的部分数据时,有如下四种作法:web
application/merge-patch+json
来表示)。application/json-patch+json
来表示)我相信大部分系统中,采起的都是第1种和第4种作法,而本文的主题则是第3种作法。mongodb
在 RFC 5789(PATCH method for HTTP) 中,有一个关于 PATCH 请求的小例子:docker
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?和 RFC JSON Patch。json
示例程序引入了swagger
,MongoDB
,docker-compose
等功能,关于 JsonPatch 的部分则使用微软官方的 JsonPatch 编写,该库支持add
,remove
,replace
,move
,copy
方法,实现并不困难。实际使用时,直接以JsonPatchDocument<T>
做为包装便可。api
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,好比如下代码将没法编译:
environment: - ASPNETCORE_ENVIRONMENT=Development - ASPNETCORE_URLS=http://0.0.0.0:80 - ConnectionString=${MONGODB:-mongodb://mongodb} - Database=ExampleDb