Skip to main content

Google API Design Guide

· 16 min read

Google API Design Guide

面向资源设计

  • 设计流程
    • 确定 APi 提供的资源类型
    • 确定资源之间的关系
    • 决定资源的名字格式, 基于类型和关系
    • 决定资源的格式
    • 为资源添加最低限度的方法
  • 资源
    • 集合
    • 资源
  • 方法

资源名字

  • 基本概念
    • 资源是被命名的实体
    • 资源名即为其标识符
    • 每个资源必须有其唯一的资源名
    • 资源名由 资源 ID, 父资源 ID 和服务名
    • gRPC 资源名应该使用 scheme-less 的 URI
    • 集合为特殊的资源, 包含一列相同类型的子资源
    • 集合资源的 ID 为 集合 ID
  • 完整资源名
    • //library.googleapis.com/shelves/shelf1/books/book2
  • 相对资源名
    • 相对于服务
    • shelves/shelf1/books/book2
  • 资源 ID
    • 资源名中的资源 ID 可能会有超过一个的 URI segment
    • 服务应该使用 URL 友好的资源 ID
    • 必须要明确资源 ID 是由客户端指定,服务指定或都可以
      • 例如文件名通常由客户端指定
      • 有限 ID 通常由服务端指定
    • Collection ID -> files
    • Resource ID -> /source/py/parser.py
  • 集合 ID
    • 必须是有效的 C/C++ 标识符
    • 必须是负数的 lowerCamel 形式
      • 如果没有合适的负数形式, 那么应该使用单数形式, 例如 evidence,weather
    • 必须使用明确简洁的英语词汇
    • 通用的词汇应该避免使用或加限定符
      • values -> rowValues
      • 应该避免直接使用的词汇
        • elements
        • entries
        • instances
        • items
        • objects
        • resources
        • types
        • values
  • 资源名 vs URL
    • 在 REST API 使用时, 应该转换为相应的 URL 格式
    • 添加版本号和 Schema
    • 资源名 -> //calendar.googleapis.com/users/john smith/events/123
    • URL -> https://calendar.googleapis.com/v3/users/john%20smith/events/12
  • 资源名作为字符串
    • 资源名必须能被呈现为字符串
    • 资源名应该能像文件名一样被处理, 不支持百分号编码
    • 对于资源定义, 第一个字段应该为资源名, 应该为 name
    • 其他名字相关的字段应该添加限定符避免混淆, 例如 display_name, first_name
    • 资源名禁止包含前 /
  • 为什么不适用资源 ID 来标识一个资源
    • 例如为什么不使用诸如 (bucket, object)(user, album, photo) 的形式
    • 开发者必须要理解并且记住这样的匿名元组
    • 传递元组比字符串一般更加困难
    • 很多系统不能理解这样的形式
    • 特点的元组限制 API 设计的灵活性
  • 为什么使用 name 而不使用 id
    • 遵从资源名的概念
    • 通常 name 的定义会非常混淆, 保留 name 使得开发者选择一个更合适的名字, 例如 display_name, title
API Service NameCollection IDResource IDCollection IDResource ID
//storage.googleapis.com/buckets/bucket-id/objects/object-id
API Service NameCollection IDResource IDResource IDResource ID
//mail.googleapis.com/users/name@example.com/settings/customFrom

标准方法

Std.MethodHTTP MappingHTTP Request BodyHTTP Response Body
ListGET <collection URL>N/AResource* list
GetGET <resource URL>N/AResource*
CreatePOST <collection URL>ResourceResource*
UpdatePUT or PATCH <resource URL>ResourceResource*
DeleteDELETE <resource URL>N/Agoogle.protobuf.Empty**

自定义方法

  • HTTP 映射
    • https://service.name/v1/some/resource/name:customVerb
    • 应该使用 POST 方法
    • 如果是类似 List 的方法也许使用 GET
    • 不应该使用 PATCH, 也许会使用其他方法, 但应该遵从 HTTP 语义
    • 使用 GET 方法, 必须要求请求幂等, 不会有副作用
      • 例如自定义视图应该使用 GET
    • 请求消息中的资源名应该被映射到路径上
    • URL 路径必须以 :customVerb 结束
    • 如果允许请求体, 必须使用 body: "*", 应该将所有字段映射到请求消息上
    • 如果不接受请求体, 禁止使用 body 语句
    • 自定义的方法动词禁止重叠
Method NameCustom verbHTTP verb备注
Cancel:cancelPOST取消一个未完成的操作 (构建,计算 等)
BatchGet<plural noun>:batchGetGET批量获取多个资源
Move:movePOST将资源从父节点移动到另外一个父节点
Search:searchGET获取资源列表, 但不同于 List 语义
Undelete:undeletePOST恢复一个已删除的资源. 建议做 30 天的保留期

标准字段

NameTypeDescription
namestring包含相对资源名
parentstring对资源定义和 List/Create 请求, 该字段表示相对的父节点资源名
create_timeTimestamp创建时间
update_timeTimestamp最后一次更新时间, 在 create/patch/delete 时更新
delete_timeTimestamp删除时间, 如果支持资源保留
expire_timeTimestamp超时时间
start_timeTimestamp开始时间
end_timeTimestamp结束时间 (不管是否成功)
time_zonestring时区名. IANA TZ name, 例如 "America/Los_Angeles", 时区列表
region_codestring地区的 Unicode country/region code (CLDR), 例如 "US" and "419",unicode_region_subtag
language_codestringBCP-47 语言码, 例如 "en-US". Unicode locale identifier
mime_typestringIANA 发布的 MIME 类型. media-types
display_namestring实体的显示名字
titlestring实体的官方名字, 例如公司名. 作为正式的 display_name.
descriptionstring实体描述
filterstringList 方法的标准过滤参数
querystring等同于 List 方法的 filter, 不过用于 Search
page_tokenstringList 请求的分页符
page_sizeint32List 请求的分页大小
total_sizeint32总数
next_page_tokenstring下一页的分页符, 如果为空则表示没有更多结果
order_bystring指定 List 请求的排序
request_idstring用于检测重复请求的唯一标识符
resume_tokenstring用于恢复一个流请求的不透明标识符
labelsmap<string, string>资源标签
deletedbool如果资源允许 undelete 操作, 则应该有被删除标示
show_deletedbool如果资源允许 undelete 操作, 相应的 List 操作应该可以死指定显示已删除的资源
update_maskFieldMask用于 Update 对资源进行部分更新操作. 该属相对于资源而非请求消息
validate_onlybool如果为 true, 则应该只验证该请求, 而不执行

错误

  • 错误模型 google.rpc.Status
  • 错误码 google.rpc.Code
    • 单独的接口应该避免定义额外的错误码, 如果一个接口需要处理三四种错误码, 那么代码将大部分都是错误处理
  • 错误信息
    • 不要假设用户非常了解你的接口
    • 不要假设用户知道任何服务的实现相关, 或者了解错误的上下文信息
    • 如果可以, 错误信息应该能被一个技术人员理解并且修复错误
    • 保持错误信息简介, 如果可以, 可提供一个错误链接以供提问或反馈
  • google/rpc/error_details.proto
    • 定义了常用的错误详情
package google.rpc;

message Status {
// 简单的错误码, 实际的错误码由 `google.rpc.Code` 定义
int32 code = 1;

// 面向开发者的错误信息. 应该解释错误原因并提可操作的解决办法
string message = 2;

// 附加的错误信息, 例如重试延迟或帮助链接
repeated google.protobuf.Any details = 3;
}

名称转换

  • 遵循 简介, 直观, 一致 的原则
  • 产品名
    • 产品的营销名称
    • 应该与 接口, UI, 文档, TOS, 账单, 合同保持一致
    • 必须以 Google 开头, 除非由多个产品使用, 例如 Gmail, Youtube
    • 应该由产品或营销团队决定
  • 服务名
    • 应该是一个合法的 DNS 名字, 可以被解析为一个或多个网络地址
      • 谷歌接口的服务名均为 xxx.googleapis.com
      • 例如 calendar.googleapis.com
    • 如果 API 由多个服务组成, 名字应该帮助其发现相应的其他服务, 例如使用一个统一的前缀
      • 例如 build.googleapis.combuildresults.googleapis.com 同属于 Google Build API
  • 包名
    • .proto 中定义的包名, 应该尽量与产品和服务名保持你一直
    • 如果 API 有版本, 那么包必须以版本号作为结束
    • 不由具体服务使用的抽象 API, 应该使用与产品一致的包名
      • google.watcher.v1
    • Java 包必须与包名一直, 只在其前面加相应前缀 (com.net 等)
  • 集合标识符
    • 应该使用复数形式和驼峰命名
  • 接口名
    • .proto 中的 service, 避免与服务名冲突
    • 可认为服务名是一组 API 实现的合集, 而接口是 API 的抽象定义
    • 应该使用只管的名字, 例如 Calendar Blob
    • 不应该使用与已有的概念产生冲突, 例如 File
    • 在极端情况下, 避免与其他 API 冲突, 应该使用一个后缀(Api,Service)来避免歧义
  • 方法名
    • 应该使用 VerbNoun 动词名字 的格式, 其中的每次应该为资源类型
    • 动词应该使用祈使语气, 而不是疑问语气
      • 表达直接命令或请求
  • 消息名
    • 应该与方法名为前缀, 添加 RequestResponse 作为后缀, 除非
      • 消息为空, google.protobuf.Empty
      • 资源类型
      • 表示一个操作的资源
  • 枚举名
    • 类型名必须为 UpperCamelCase
    • 值名必须为 CAPITALIZED_NAMES_WITH_UNDERSCORES
    • 每个枚举值必须以 ; 结束
    • 第一个值应该为 ENUM_TYPE_UNSPECIFIED, 表示该值未被指定
  • 字段名
    • 字段名必须为 lower_case_underscore_separated_names
    • 应该避免使用介词
      • reason_for_error -> error_reason
      • cpu_usage_at_time_of_failure -> failure_time_cpu_usage
    • 应该避免使用后置形容词 Postpositive adjective
      • items_collected -> collected_items
      • objects_imported -> imported_objects
    • 重复字段应该使用复数形式
    • 时间点和持续时间
      • 使用 google.protobuf.Timestampgoogle.protobuf.Duration
      • _time_duration 作为后缀
      • 如果需要时间相关的后缀, 必须遵循 xxx_{time|duration|delay|latency}_{seconds|millis|micros|nanos} 格式
      • 如果不得不使用字符串格式, 那么应该使用 RFC 3339 格式, 例如 2014-07-30T10:43:17Z
    • 日期和一天中的时间
      • 应该使用 google.type.Dategoogle.type.TimeOfDay
      • 应该以 _date_time 作为后缀
      • 如果日期不得不使用字符串形式, 应该使用 ISO 8601 日期格式 YYYY-MM-DD 例如 2014-07-30
      • 如果时间不得不使用字符串形式, 应该使用 ISO 8601 24 小时的事件格式, HH:MM:SS[.FFF] 例如 14:55:01.672
    • 数量
      • 比包含测量单位
        • xxx_{bytes|width_pixels|meters}
      • 如果是项目的数量, 应该以 _count 作为后缀
    • List 过滤字段应该使用 filter
    • List 响应
      • 返回的资源字段必须是复数形式
  • 名字缩写
    • 在接口定义时应该使用常见的缩写, 在文档中应该使用标准格式
      • config (configuration)
      • id (identifier)
      • spec (specification)
      • stats (statistics)
API NameExample
产品名Google Calendar API
服务名calendar.googleapis.com
包名google.calendar.v3
接口名google.calendar.v3.CalendarService
源码目录//google/calendar/v3
API 名calendar

方法名

VerbNounMethod nameRequest messageResponse message
ListBookListBooksListBooksRequestListBooksResponse
GetBookGetBookGetBookRequestBook
CreateBookCreateBookCreateBookRequestBook
UpdateBookUpdateBookUpdateBookRequestBook
RenameBookRenameBookRenameBookRequestRenameBookResponse
DeleteBookDeleteBookDeleteBookRequestgoogle.protobuf.Empty

版本

  • MAJOR.MINOR.PATCH
    • MAJOR 接口不兼容
    • MINOR 添加新的功能
    • PATCH 问题修正
VersionProto PackageDescription
v1alphav1alpha1The v1 alpha release.
v1beta1v1beta1The v1 beta 1 release.
v1beta2v1beta2The second beta release of v1.
v1testv1testAn internal test release with dummy data.
v1v1The v1 major version, general availability.
v1.1beta1v1p1beta1The first beta release for minor changes to v1.
v1.1v1The minor update to v1.1 release.
v2beta1v2beta1The v2 beta 1 release.
v2v2The v2 major version, general availability.

Ubuntu 家用

· 8 min read
  • Ubuntu 桌面版
  • 安装至少需要 1024*768 的显示器

Ubuntu 新服务器配置

· 3 min read
# 修改主机名
hostnamectl set-hostname myHostName
# 如果想要直接使用主机名,还需要在 /etc/hosts 里添加相关记录
# nano /etc/hostname
# nano /etc/hosts

# 生成中文
locale-gen zh_CN.UTF-8
# 基本更新
apt-get update
apt-get upgrade

# 出现 The following packages have been kept back 可考虑 apt dist-upgrade 或 install
# 当 /etc/apt/sources.list* 有其他仓库时使用 dist-upgrade 相对没那么安全

# 修改密码
passwd


# 创建用于部署的用户
useradd deploy
mkdir /home/deploy
mkdir /home/deploy/.ssh
chmod 700 /home/deploy/.ssh
# 将需要使用该用户的公钥添加进去
vim /home/deploy/.ssh/authorized_keys

chmod 400 /home/deploy/.ssh/authorized_keys
chown deploy:deploy /home/deploy -R

# 编辑 sudoers
visudo
# 将需要 sudo 的用户添加进去
# deploy ALL=(ALL) ALL

# 修改 sshd 权限
vim /etc/ssh/sshd_config
# PermitRootLogin no # 不允许直接 root 登陆
# PasswordAuthentication no # 不允许密码登陆
# AllowUsers deploy@(your-ip) deploy@(another-ip-if-any) # 只允许指定地址的人登陆
# service ssh restart

# 防火墙设置
# DNS 53
# mosh allow 60000:61000/udp 或 allow mosh
ufw allow 22
ufw allow 80
ufw allow 443
ufw default deny
ufw enable

Tips

当有多台主机需要部署的时候,建议使用 sshrc, tmuxrc, 这样能快速的将所有的配置都带给服务器,能够快速方便的对多台进行安装部署.

mosh-dev

由于 mosh 部分鼠标相关的功能需要最新版,所以建议直接安装 dev 版本

apt-get install -y software-properties-common
add-apt-repository ppa:keithw/mosh-dev
apt-get update
apt install -y mosh

fail2ban

apt-get install fail2ban
cp /etc/fail2ban/jail.conf /etc/fail2ban/jail.local
# bantime = 3600 # ban 1 小时
# destemail = admin@admin # 发送警告的邮箱地址
# 将 ssh/ssh-ddos 等段落下的 enable 设置为 true 打开相应的 filter

env

apt-get install software-properties-common
# 添加 Oracle Java 仓库
add-apt-repository ppa:webupd8team/java#
apt-get update
# 安装 Oracle Java 8 JDK
apt-get install oracle-java8-installer
# 如果有多个 Java 环境可调整配置
update-alternatives --config java

ipv6

# https://jiandanxinli.github.io/2016-08-06.html

# /etc/sysctl.conf
# net.ipv6.conf.all.disable_ipv6=0
# net.ipv6.conf.default.disable_ipv6=0
# net.ipv6.conf.lo.disable_ipv6=0
sysctl -p

# https://www.tunnelbroker.net/
# 选择 linux route2, 然后执行脚本

# 检测 ipv6 是否可用
# http://ready.chair6.net/?url=wener.me

# 如果操作失败了则删除通道从来
ip tun del he-ipv6

分布式架构手记

· 4 min read

这几天尝试了很多的 *aaS, 期望能找到在当前架构下适合公司后续发展的一个系统架构, 然而一路下来发现并没那么简单.

Play Titans using shell

· One min read

Tap Titans on Play Store

Features

  • Auto upgrade
  • Auto spell skill
  • Auto start challenge
  • Auto close ad dialog
  • Verify fast tap (0.03s/tap)

NOTE

  • Only tested on Nexus 5, different devices may use different coordinates and event dev.
  • Used to plat tiantis long time ago, may not works in current version.Different version may use different color and coordinates

Requirement

  • ADB
  • Image Magic
  • Bash

Get started

chmod +x play.sh
# Connect through lan, so you don't have to use the usb
./play.sh use-tcp
# Push the pre-generated event file
adb push events /mnt/sdcard
# Start playing
./play.sh

How is works ?

Why fast ?

  • Because I don't use adb shell input tap, instead use a generated file that represent a tap event, then cat tap > /dev/input/event1.
  • Use generated shell run in android.

How to detect the screen event ?

Use screen capture, then use the image magic to check the color,kind of slowly, but works.

More

Check the source

Guava 简介

· 3 min read

Overviews - Five Ws

问题答案
是什么一套开源的 Java 公共组件
谁开发的主要由 Google 工程师开发维护
哪里用所有使用 Java 的地方
什么时候出现的2008 年九月 第一个 Google Collection 版本出现在 maven 仓库;
2010 年四月第一个 Guava 版本出现在maven 仓库
什么使用为什么不使用 ?

转战 Hugo, 博客迁移之路

· 2 min read

都快要把自己感动, 从最初(2011年)的 Wordpress, 到后来自己开发的 Tellets , 现在又到了 Hugo.

之所以抛弃 Wordpress 是因为它太笨重了,那些年是租的虚拟主机来挂博客,每年也还会有些投入,但是后来很少维护了,便不在想续费了.

后面想找一个轻便的能根据文件时生成的博客系统,所幸找到了 Droplet,但发现好多东西都不能满足要求,后来把 Droplet 完全重写成了 Tellts,自己添加的最喜欢的功能是直接配置 Github 的文章引用. Tellets 也是 PHP 的,而且必须要支持文件操作,但很多 PHP 应用服务器都不提供文件操作(例如: 新浪 SEA, 当初的京东云擎),后来主机停了便没有去管了. 再后来有点想用 Go 重写 Tellts, 但实在没时间,然后又过了很长一段时间.

在工作的情况下需要了解 Docker, 顺势也就利用 Docker 部署了 Hugo.