Skip to content

Commit e8f32e5

Browse files
authored
Merge pull request ch3rub1m#6 from ch3rub1m/design-patterns
fix a bunch of error in Design_Patterns.md
2 parents e45a6f7 + 50773ed commit e8f32e5

File tree

1 file changed

+51
-41
lines changed

1 file changed

+51
-41
lines changed

Design_Patterns.md

+51-41
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
表示范围的字段应该使用符合命名规范的半开半闭区间`[start_xxx, end_xxx)`,例如`[start_key, end_key)`或者`[start_time, end_time)`。半开半闭区间通常在C++ STL和Java的标准库里面被用到。API应该避免使用其他方法去代表范围,例如`(index, count)`,或者`[first, last]`
77

88
## 资源标签
9-
在面向资源的API里面,资源的结构通常由API定义。为了让客户端在资源上带上少量且简单的元数据,APIs应该使用在`google.api.LabelDescriptor`里面描述的`资源标签设计模式`(resource label design pattern)。
9+
在面向资源的API里面,资源的结构通常由API定义。为了让客户端在资源上带上少量简单的元数据(举个例子,标记一台虚拟机资源为数据库服务器),APIs应该使用在`google.api.LabelDescriptor`里面描述的`资源标签设计模式`(resource label design pattern)。
1010

1111
为了实现这一点,API设计应该在资源定义里面添加一个`map<string, string>`字段。
1212

@@ -31,9 +31,9 @@ message Book {
3131

3232
为了支持`List`的分页操作,API应该:
3333

34-
* 在方法的请求信息里面定义一个叫做`page_token``string`类型的字段。客户端用来向服务器请求关于集合某一页的内容。
35-
* 在方法的请求信息里面定义一个叫做`page_size``int32`类型的字段。客户端用来指定服务器给其返回集合的最大资源个数。服务端可能会自己判断单页里面应该返回的资源个数,例如,当`page_size`的值为0时,服务端将会自己决定返回结果的最大资源数是多少。
36-
*`List`方法的响应信息中定义一个叫做`next_page_token``string`类型字段。这个字段用来指明用于接受下一页结果的token。如果这个字段的值是`""`,这表明没有更多的请求结果了。
34+
* 在方法的请求信息里面定义一个叫做`page_token``string`类型的字段。客户端用来向服务器请求关于集合某一页的内容。
35+
* 在方法的请求信息里面定义一个叫做`page_size``int32`类型的字段。客户端用来指定服务器给其返回集合的最大资源个数。服务端可能会自己判断单页里面应该返回的资源个数,例如,当`page_size`的值为0时,服务端将会自己决定返回结果的最大资源数是多少。
36+
* `List`方法的响应信息中定义一个叫做`next_page_token``string`类型字段。这个字段用来指明用于接受下一页结果的token。如果这个字段的值是`""`,这表明没有更多的请求结果了。
3737

3838
为了获取下一页的结果,客户端应该在下一个`List`请求时带上上一个请求的`next_page_token`
3939

@@ -52,24 +52,34 @@ message ListBooksResponse {
5252
}
5353
```
5454

55-
在客户端在请求里面携带了除page token外的请求参数(query parameters)的时候,如果参数与page token不一致,服务必须拒绝此请求。
55+
当客户端在请求里面携带了除page token以外的请求参数(query parameters),如果参数与page token不一致,服务必须拒绝此请求。
5656

57-
page token的内容应该是对web安全的BASE64编码后的protocol buffer,这样就不会有兼容性问题。page token中存在敏感信息时,应该将其加密。服务端必须通过以下方法来防止通过篡改page token来获取敏感信息:
57+
page token的内容应该是基于一个url安全的protocol buffer进行base64编码后的结果。这样在内容变动的时候就不会有兼容性问题。page token中存在敏感信息时,应该将其加密。服务端必须通过以下方法来防止通过篡改page token来获取敏感信息:
5858

59-
* 后续请求要重新指定请求参数
60-
* 在page token中仅引用服务端的状态
61-
* 在page token中加密并签名请求参数,并且在每次调用中对这些参数进行验证和鉴权
59+
* 后续请求要重新指定请求参数
60+
* 在page token中仅引用服务端的会话状态
61+
* 在page token中加密并签名请求参数,并且在每次调用中对这些参数进行反复验证和鉴权
6262

6363
分页的实现可能会在一个叫做`total_size``int32`类型的字段里面标明资源的总数。
6464

6565
## 列出子集合
66-
某些时候,API需要让客户端可以在子资源里面进行`List/Search`。例如,API库可能有一个书架(shelves)集合,每个书架里面有一个书本(books)集合,假如某个客户想要在所有的书架里面搜索一本书,在这种情形下,最推荐的做法是在子资源里面使用标准的`List`方法,在父级集合里面使用集合的通配符`-`。对于这个API库的例子,我们可以使用以下的REST API请求:
66+
某些时候,API需要让客户端对子集合进行`List/Search`操作。例如,图书馆(Library)API可能有一个书架(shelves)集合,每个书架里面有一个书本(books)集合,假如某个客户想要在所有的书架里面搜索一本书,在这种情形下,最推荐的做法是在子资源里面使用标准的`List`方法并为父级集合指定通配集合id`"-"`。对于这个API库的例子,我们可以使用以下的REST API请求:
6767

6868
```
6969
GET https://library.googleapis.com/v1/shelves/-/books/{id}
7070
```
7171

72-
这个请求的响应资源名称一定要使用规范的名称,一定要是实际的父集合名称而不是一个`-`通配符。例如,以上请求应该返回一个类似于`shelves/shelf713/books/book8141`,而不是`shelves/-/books.book8141`的名称。
72+
提示:选择`"-"`替代`"*"`的原因是为了避免URL escaping。
73+
74+
## 从子集合中获取唯一资源
75+
76+
某些时候,有些子集合中的资源具有包括其父集合在内也唯一的标识符。在不知道哪一个父集合包含它的时候,允许通过`Get`来获取这个资源可能是有用的。在这种情况下,建议在该资源上使用标准`Get`,并为所有父集合指定通配集合id`"-"`。举个例子,在图书馆API中,我们可以使用以下的REST API请求,如果这本书在所有的书架上的所有书中是唯一的:
77+
78+
```
79+
GET https://library.googleapis.com/v1/shelves/-/books/{id}
80+
```
81+
82+
在这个请求的响应中使用的资源名称一定要使用该资源的官方名称,为每一层父级集合使用实际的父级集合id而不是`"-"`。举个例子,以上请求返回的资源名称应该形如`shelves/shelf713/books/book8141`,而非`shelves/-/books.book8141`
7383

7484
## 排列顺序
7585
如果某一个API方法允许客户端指明列表结果的排序方式,请求消息应该包含以下字段:
@@ -78,20 +88,20 @@ GET https://library.googleapis.com/v1/shelves/-/books/{id}
7888
string order_by = ...;
7989
```
8090

81-
字符串的值应该遵循SQL语法:用逗号分隔的字段列表。例如:`foo,bar`。每个字段默认按照升序排列,如果要指明某一个字段值按照降序排列,应该给这个字段添加`desc`后缀。例如:`foo desc,bar"`
91+
字符串的值应该遵循SQL语法:用逗号分隔的字段列表。例如:`"foo,bar"`。每个字段默认按照升序排列,如果要指明某一个字段值按照降序排列,应该给这个字段添加`" desc"`后缀。例如:`"foo desc,bar"`
8292

83-
多余的空格可以忽略,`foo,bar desc``foo , bar desc "`是相等的
93+
多余的空格可以忽略,`"foo,bar desc"``" foo , bar desc "`是等价的
8494

8595
## 请求校验
86-
如果某个API方法有副作用,并且需要对请求进行校验来防止副作用的产生,这样的请求消息应该包含以下字段
96+
如果某个API方法有副作用,并且有必要在不产生副作用的情况下对请求进行校验,请求消息应该包含以下字段
8797

8898
```
8999
bool validate_only = ...;
90100
```
91101

92-
当此字段设置为`true`时,服务端一定不能执行任何有副作用的操作,而是对请求进行校验
102+
当此字段设置为`true`时,服务端一定不能执行任何有副作用的操作,并且只执行与完整请求一致的针对实现的校验
93103

94-
校验成功时必须返回`google.rpc.Code.OK`并且使用相同请求信息的完整请求不应该返回`google.rpc.Code.INVALID_ARGUMENT`。注意,此请求可能还是会因为其他错误(比如`google.rpc.Code.ALREADY_EXISTS`或竞态条件)而失败。
104+
如果校验成功,必须返回`google.rpc.Code.OK`并且使用相同请求信息的完整请求都不应该返回`google.rpc.Code.INVALID_ARGUMENT`。注意,此请求可能还是会因为其他错误(比如`google.rpc.Code.ALREADY_EXISTS`或竞态条件)而失败。
95105

96106
## 重复请求
97107
对于网络API,幂等性是很重要的,因为当网络异常时它们能够安全地进行重试。然而一些API并不容易实现幂等性,例如需要避免不必要重复的创建资源操作。对于这类情况,请求信息应该包含一个唯一ID(例如UUID),这样服务端能够通过此ID来检测请求是否重复,保证请求只被处理一次。
@@ -105,11 +115,11 @@ string request_id = ...;
105115
如果服务端检测到重复的请求,它应该给客户端返回之前成功的响应,因为之前那个响应客户端很有可能没有接收到。
106116

107117
## 枚举默认值
108-
每个枚举必须从`0`开始定义,`应该`在枚举值没有被显式指明时被使用。API`必须`在文档中指明应该如何处理默认值`0`
118+
每个枚举必须从`0`开始定义,它应该在枚举值没有被显式指明时使用。API必须在文档中指明应该如何处理默认值`0`
109119

110-
如果有通用的默认行为,枚举值`0``需要`被使用,同时API文档也应该说明预期的行为。
120+
如果有通用的默认行为,枚举值`0`就应该被使用,同时API文档也应该说明预期的行为。
111121

112-
如果没有通用的默认行为,枚举值`0``应该`被命名为 `ENUM_TYPE_UNSPECIFIED`并且要和`INVALID_ARGUMENT`错误一起使用。
122+
如果没有通用的默认行为,枚举值`0`应该被命名为 `ENUM_TYPE_UNSPECIFIED`并且要和`INVALID_ARGUMENT`错误一起使用。
113123

114124
```
115125
enum Isolation {
@@ -125,12 +135,12 @@ enum Isolation {
125135
Isolation level = 1;
126136
```
127137

128-
`0`值可以使用一个惯用名称来命名。例如,在没有错误码的情况下,可以使用惯用名称`google.rpc.Code.OK`,在这种情况下,`OK`语义上等同于枚举类型上下文的`UNSPECIFIED`
138+
`0`值可以使用一个惯用名称来命名。例如,在没有错误码的情况下,可以使用惯用名称`google.rpc.Code.OK`,在这种情况下,`OK`语义上等价于枚举类型上下文的`UNSPECIFIED`
129139

130-
若存在本质上合理和安全的默认值,这个值可以被用来作为`0`值。例如,在资源视图枚举中`BASIC`就是这种情况的`0`值。
140+
若存在本质上合理和安全的默认值,这个值就可以被用来作为`0`值。例如,在资源视图枚举中`BASIC`就是`0`值。
131141

132142
## 语法句法
133-
在API设计中,有时候需要为某些特定的数据格式定义简单的语法,例如可接受的文本输入。为了在不同 API 中提供一致的开发体验和减少学习曲线,API 设计者`必须`使用扩展的Backus-Naur 表格(EBNF)变种句法来定义这些语法
143+
在API设计中,有时候需要为某些特定的数据格式定义简单的语法,例如可接受的文本输入。为了在跨APIs中提供一致的开发体验和减少学习曲线,API设计者们必须使用如下Extended Backus-Naur Form (EBNF)句法变种来定义这些语法
134144

135145
```
136146
Production = name "=" [ Expression ] ";" ;
@@ -147,10 +157,10 @@ Repetition = "{" Expression "}" ;
147157
## 整数类型
148158
在API设计中,不应该使用像`uint32``fixed32`这种无符号整型,这是因为一些重要的编程语言和系统(例如Java, JavaScript和OpenAPI)不能很好地支持它们,并且他们更容易导致溢出错误。另一个问题是,不同API很可能对同一个资源使用不同的数据类型(有符号和无符号整型)。
149159

150-
在大小和时间这种负数没有意义的类型中`可以`使用且仅可以使用`-1`来表示特定的意义,例如文件结尾(EOF)、无穷的时间、无资源限额或未知的年龄。当在这种情况下使用负数时,`必须`在文档中明确说明其意义以防止混淆。如果语义不明显,API生产者也应该在文档中记录隐式默认值`0`表示的行为。
160+
在大小和时间这种负数没有意义的类型中`可以`使用且仅可以使用`-1`来表示特定的意义,例如文件结尾(EOF)、无穷的时间、无资源限额或未知的年龄。当在这种情况下使用负数时,必须在文档中明确说明其意义以防止混淆。如果不够显而易见,API生产者也应该在文档中记录隐式默认值`0`表示的行为。
151161

152162
## 部分响应
153-
客户端有时只需要响应信息中的特定子集。一些API平台提供了对部分响应的原生支持。Google API平台通过响应字段掩码来为其提供支持。对于任一REST API 调用,有一个隐式的系统query参数 `$fields`,它是 `google.protobuf.FieldMask`的JSON表示。在返回给客户端之前,响应消息会被`$fields`字段过滤。此行为是在API平台上的所有API方法上执行的
163+
客户端有时只需要响应信息中的特定子集。一些API平台提供了对部分响应的原生支持。Google API平台通过响应字段掩码来为其提供支持。对于任一REST API 调用,有一个隐式的系统query参数 `$fields`,它是 `google.protobuf.FieldMask`的JSON表示。在返回给客户端之前,响应消息会被`$fields`字段过滤。这个逻辑在API平台的所有API方法上都会被处理
154164

155165
```
156166
GET https://library.googleapis.com/v1/shelves?$fields=name
@@ -161,10 +171,10 @@ GET https://library.googleapis.com/v1/shelves?$fields=name
161171

162172
参数满足以下条件:
163173

164-
* `应该`是枚举类型
165-
* `必须`命名为`view`
174+
* *应该*是枚举类型
175+
* *必须*命名为`view`
166176

167-
枚举中的每个值定义了资源的哪部分(字段)在响应中会被返回。文档中`应该`明确指定每个`view`值会返回什么。
177+
枚举中的每个值定义了资源的哪部分(字段)在响应中会被返回。文档中应该明确指定每个`view`值会返回什么。
168178

169179
```
170180
package google.example.library.v1;
@@ -191,7 +201,7 @@ enum BookView {
191201
message ListBooksRequest {
192202
string name = 1;
193203
194-
// 指定返回图书资源的哪些部分
204+
// 指定图书资源的哪些部分应该被返回
195205
BookView view = 2;
196206
}
197207
```
@@ -204,7 +214,7 @@ GET https://library.googleapis.com/v1/shelves/shelf1/books?view=BASIC
204214
可以在[标准方法](Standard_Methods.md)一章中查看更多关于定义方法、请求和响应的内容。
205215

206216
## ETags
207-
ETag是一个不透明的标识符,允许客户端进行条件性请求。为了支持 ETag,API`应该`在资源定义中包含一个字符串字段`etag`它的语义`必须`与ETag的常用用法相匹配。通常,`etag`包含由服务器计算出的资源指纹。更多详细信息,请参阅 [维基百科](https://en.wikipedia.org/wiki/HTTP_ETag)[RFC 7232](https://tools.ietf.org/html/rfc7232#section-2.3)
217+
ETag是一个不透明的标识符,允许客户端进行条件性请求。为了支持 ETag,API应该在资源定义中包含一个字符串字段`etag`它的语义必须与ETag的常用用法相匹配。通常,`etag`包含由服务器计算出的资源指纹。更多详细信息,请参阅 [维基百科](https://en.wikipedia.org/wiki/HTTP_ETag)[RFC 7232](https://tools.ietf.org/html/rfc7232#section-2.3)
208218

209219
ETags可以是强验证的或弱验证的,其中弱验证的ETag以`W/`为前缀。在这种情况下,强验证意味着具有相同ETag的两个资源具有每字节都相同的内容和相同的额外字段(例如,Content-Type),同时也意味着强验证的ETag允许对稍后组装的部分响应进行缓存。
210220

@@ -219,7 +229,7 @@ ETags可以是强验证的或弱验证的,其中弱验证的ETag以`W/`为前
219229
W/"1a2b3c4d5ef"
220230
```
221231

222-
值得注意的是引号也是ETag值的一部分,而且为了遵循 [RFC 7232](https://tools.ietf.org/html/rfc7232#section-2.3) 它们一定要被表示出来。这就意味着ETags的JSON表示一定要对引号进行转义。例如ETags在JSON资源的表示是:
232+
值得注意的是引号也是ETag值的一部分,而且为了遵循[RFC 7232](https://tools.ietf.org/html/rfc7232#section-2.3)它们一定要被表示出来。这就意味着ETags的JSON表示一定要对引号进行转义。例如ETags在JSON资源的表示是:
223233

224234
```
225235
// 强验证
@@ -230,16 +240,16 @@ W/"1a2b3c4d5ef"
230240

231241
ETags中允许的字母:
232242

233-
* 可打印的ASCII码
234-
* RFC 2732中指定的非ASCII码,不过他们对开发者来说不是很友好
235-
* 没有空格
236-
* 除了上面的双引号,不能有别的双引号
237-
* RFC 7232中推荐不能使用反斜线以防止和转移符混淆
243+
* 可打印的ASCII码
244+
* RFC 2732中指定的非ASCII码,不过他们对开发者来说不是很友好
245+
* 没有空格
246+
* 除了上面的双引号,不能有别的双引号
247+
* RFC 7232中推荐不能使用反斜线以防止和转移符混淆
238248

239249
## 输出字段
240-
API可能希望将由客户端提供的输入字段和只由服务端在特定资源上返回的输出字段进行区分。对于仅输出的字段,`应该`记录该字段的属性
250+
API可能希望将由客户端提供的输入字段和只由服务端在特定资源上返回的输出字段进行区分。对于仅输出的字段,应该记录该字段的属性
241251

242-
请注意,如果客户端在请求中设置了仅输出(output only)字段,或者客户端使用仅输出字段指定了一个`google.protobuf.FieldMask`则服务器`必须`接受该请求而不能报错。这意味着服务器`必须`忽略仅输出字段的存在及其任何指示。这个建议的原因是因为客户端通常会将服务器返回的资源重用为另一个请求的输入,例如一个获取到的`Book`将在`UPDATE`方法中被再次使用。如果要验证仅输出字段,客户端需要做清除输出字段的额外工作。
252+
请注意,如果客户端在请求中设置了仅输出(output only)字段,或者客户端对仅输出字段指定了一个`google.protobuf.FieldMask`则服务器必须接受该请求而不能报错。这意味着服务器必须忽略仅输出字段的存在及其任何指示。这个建议的原因是因为客户端通常会将服务器返回的资源重用为另一个请求的输入,例如一个获取到的`Book`将在`UPDATE`方法中被再次使用。如果要验证仅输出字段,客户端需要做清除输出字段的额外工作。
243253

244254
```
245255
message Book {
@@ -250,9 +260,9 @@ message Book {
250260
```
251261

252262
## 单例资源
253-
单例资源使用在父节点只有一个资源实例的情况下(如果没有父资源,则其存在整个API中)
263+
单例资源被使用于整个父级资源范围内(如果没有父级资源,则是整个API范围内)只有一个资源实例的情况下
254264

255-
标准`Create``Delete`方法不能被用于单例资源上;单例会在其父资源创建或删除的时候被隐式创建或删除(或者在其没有父资源的前提下,会隐式存在)。资源`必须`可以通过标准的`Get``Update`方法被访问到。
265+
标准`Create``Delete`方法不能被用于单例资源上;单例会在其父资源创建或删除的时候被隐式创建或删除(或者在其没有父资源的前提下,会隐式存在)。资源必须可以通过标准的`Get``Update`方法被访问到。
256266

257267
例如,`User`资源的API应该对外暴露一个作用于单个用户`Setting`API:
258268

@@ -288,7 +298,7 @@ message UpdateSettingsRequest {
288298
}
289299
```
290300

291-
## 流媒体半关闭
292-
对于任何双向API或客户端流式APIs,服务器都`应该`依赖由RPC系统提供的客户端启动的半关闭来完成客户端流。 没有必要定义一个明确的完成消息。
301+
## 流的半关闭
302+
对于任何双向API或客户端流式APIs,服务器都应该依赖由RPC系统提供的客户端启动的半关闭来完成客户端流。没有必要定义一个明确的完成消息。
293303

294304
客户需要在半关闭之前发送的任何信息都必须定义为请求消息的一部分。

0 commit comments

Comments
 (0)