资源名称

在面向资源的APIs里,资源是命名实体,并且资源名称是他们的唯一标识。每一个资源必须有一个独一无二的资源名称。资源名称由资源本身的ID、它的各个父级的ID以及API服务名称组成。接下来我们会关注在资源ID和一个资源名称是如何被构建的。

gRPC APIs应当使用scheme-less URIs来描述资源名称。他们通常遵循REST URL的约定,并且表现的更像是一个网络上的文件路径。他们可以被轻易地映射到REST URLs:查阅标准方法获取更多细节。

集合是一种特殊的资源,它包含了一组相同类型的子资源。举个例子,一个文件夹是一组文件资源的集合。一个集合的资源ID被称为集合ID。

资源名称被有层级地按照集合IDs和资源IDs组织,通过斜杆分隔。如果一个资源包含一个子资源,那么子资源的名称将形如父级资源的名称尾随着一个子资源ID,他们同样是由斜杆来分隔。

例子1:一个存储服务有一组buckets的集合,而bucket又是一组objects的集合:

API Service Name

Collection ID

Resource ID

Collection ID

Resource ID

//storage.googleapis.com

/buckets

/bucket-id

/objects

/object-id

例子2:一个邮件服务有一组用户的集合,每一个用户有一个settings子资源,并且settings子资源又有复数的子资源,比如customFrom:

API Service Name

Collection ID

Resource ID

Resource ID

Resource ID

//mail.googleapis.com

/users

/name@example.com

/settings

/customFrom

一个API开发者可以为资源和集合IDs选择任意可接受的值,并且需要在资源层级内尽可能长来保证它们是唯一的。你可以在下文中找到更多关于如何选择合适的资源和集合IDs的指导方针。 通过分隔资源名称,比如name.split(“/“)[n],只要各个分段都不包含任何的正斜杆,就可以分别获得集合IDs和资源IDs。

完整的资源名称

一个scheme-less URI由一个兼容DNS的API服务名称和一个资源路径组成。资源路径也叫相对资源名称。举个例子:

//library.googleapis.com/shelves/shelf1/books/book2

API服务名称是为了让客户端可以定位API服务的终端,它可以是一个内部服务专用的伪DNS名。如果API服务的名称在正下文中是明确的,那么相对资源名称也经常被使用。

相对资源名称

指的是一个不以“/”开头的URI路径。它标识了一个API服务内的资源。举个例子:

"shelves/shelf1/books/book2"

资源ID

一个不为空的URI分段,它标识了一个被包含在它的父资源内的资源,可以参照之前的例子。 在资源名称中,资源ID可以是多个URI的分段。举个例子:

Collection ID

Resource ID

files

/source/py/parser.py

如果条件允许,API服务应该使用URL友好的资源IDs。资源IDs必须被清晰地标明它们是由客户端还是服务端来指定。举个例子,文件名通常是由客户端来指定的,而邮件的消息IDs则通常由服务端来指定。

集合ID

一个不为空的URI分段,它标识了被包含在它的父资源内集合资源,可以参照之前的例子。 由于集合IDs通常会出现在被生成的客户端库里,所以它们必须满足以下的需求:

  • 必须是合法的C/C++标识符

  • 必须是小写驼峰的复数形式。如果没有合适的复数形式,例如“evidence”或“weather”,那么应该使用单数形式。

  • 必须是清晰且简明的英文词汇

  • 过于概略的词汇应该避免使用,或者加以修饰。例如rowValues就比values更好。以下的词汇如果没有修饰应该被避免使用:

    • elements

    • entries

    • instances

    • items

    • objects

    • resources

    • types

    • values

资源名称 vs URL

虽然一个完整的资源名称看起来像是一个普通的URLs,但是它们其实并不相同。一个单独的资源可以被不同的方式暴露,例如不同的API版本,不同的API协议或者不同的网络终端。完整的资源名称并未指明这些信息,所以当它被使用时必须被映射到指定的API版本和API协议。

要通过REST API使用完整的资源名称,必须将其转换为REST URL,方法是在服务名称前添加HTTPS方案,在资源路径前添加API主版本,以及URL转义资源路径。例如:

// 这是一个日历事件的资源名称。
"//calendar.googleapis.com/users/john smith/events/123"

// 这是相应的HTTP URL。
"https://calendar.googleapis.com/v3/users/john%20smith/events/123"

资源名称即字符串

资源名称必须是字符串,除非有无法处理的向后兼容问题。资源名称应该像普通的文件路径那样被处理,并且不支持URL encoding。

对于资源的定义,第一个字段应该是一个字符串,表示资源名称,并且它应该被命名为name

提示:其他与name相关的字段应该加以修饰来消除歧义,例如display_name,first_name,last_name,full_name。

例如:

service LibraryService {
  rpc GetBook(GetBookRequest) returns (Book) {
    option (google.api.http) = {
      get: "/v1/{name=shelves/*/books/*}"
    };
  };
  rpc CreateBook(CreateBookRequest) returns (Book) {
    option (google.api.http) = {
      post: "/v1/{parent=shelves/*}/books"
      body: "book"
    };
  };
}

message Book {
  // Resource name of the book. It must have the format of "shelves/*/books/*".
  // For example: "shelves/shelf1/books/book2".
  string name = 1;

  // ... other properties
}

message GetBookRequest {
  // Resource name of a book. For example: "shelves/shelf1/books/book2".
  string name = 1;
}

message CreateBookRequest {
  // Resource name of the parent resource where to create the book.
  // For example: "shelves/shelf1".
  string parent = 1;
  // The Book resource to be created. Client must not set the `Book.name` field.
  Book book = 2;
}

提示:为了资源名称的一致性,词首的正斜杆一定不能被任何URL模版变量捕获。例如,URL模版必须使用/v1/{name=shelves/*/books/*}而不是"/v1{name=/shelves/*/books/*}"

问题

Q:为什么不使用资源IDs来标识一个资源?

A:在一个大型系统里有种类繁多的资源。使用资源IDs去标识一个资源,我们事实上是在使用一个指定资源的元组去标识一个资源,例如(bucket, object)或者(user, album, photo)。它会导致一些主要的麻烦:

  • 开发者必须去理解并且记住这些匿名元组

  • 传递元组获取通常比传递字符串更难

  • 中心化的基础设施,例如日志和访问权限控制系统,并不能理解指定的元组

  • 特定的元组限制了API设计的灵活性,例如提供可重用的API接口。

Q:为什么要把特殊字段命名为name而不是id?

A:特殊字段是以资源“名称”的概念命名的。通常,我们发现name这个词的含义经常困扰开发者。例如,文件名真的只是名字,还是一个完整路径呢?通过预定这个标准字段name,开发者被强制选择一个更恰当的词汇,例如display_name或者title或者full_name

Last updated