Pola desain umum

Respons Kosong

Metode Delete standar harus menampilkan google.protobuf.Empty, kecuali jika melakukan penghapusan "lunak", dalam hal ini metode harus menampilkan resource dengan statusnya diperbarui untuk menunjukkan bahwa penghapusan sedang berlangsung.

Untuk metode kustom, metode tersebut harus memiliki pesan XxxResponse sendiri meskipun kosong, karena kemungkinan besar fungsionalitasnya akan berkembang seiring waktu dan perlu menampilkan data tambahan.

Merepresentasikan Rentang

Kolom yang mewakili rentang harus menggunakan interval setengah terbuka dengan konvensi penamaan [start_xxx, end_xxx), seperti [start_key, end_key) atau [start_time, end_time). Semantik interval setengah terbuka biasanya digunakan oleh library STL C++ dan library standar Java. API harus menghindari penggunaan cara lain untuk merepresentasikan rentang, seperti (index, count), atau [first, last].

Label Resource

Dalam API berorientasi resource, skema resource ditetapkan oleh API. Agar klien dapat melampirkan sejumlah kecil metadata sederhana ke resource (misalnya, memberi tag pada resource virtual machine sebagai server database), API harus menambahkan kolom map<string, string> labels ke definisi resource:

message Book {
  string name = 1;
  map<string, string> labels = 2;
}

Operasi yang Berjalan Lama

Jika metode API biasanya membutuhkan waktu lama untuk diselesaikan, metode tersebut dapat dirancang untuk menampilkan resource Operasi yang Berjalan Lama ke klien, yang dapat digunakan klien untuk melacak progres dan menerima hasilnya. Operasi menentukan antarmuka standar untuk bekerja dengan operasi yang berjalan lama. Setiap API tidak boleh menentukan antarmukanya sendiri untuk operasi yang berjalan lama guna menghindari inkonsistensi.

Resource operasi harus ditampilkan langsung sebagai pesan respons dan setiap konsekuensi langsung dari operasi harus ditampilkan dalam API. Misalnya, saat membuat resource, resource tersebut harus muncul dalam metode LIST dan GET meskipun resource harus menunjukkan bahwa resource tersebut belum siap digunakan. Setelah operasi selesai, kolom Operation.response harus berisi pesan yang akan ditampilkan secara langsung, jika metode tidak berjalan lama.

Operasi dapat memberikan informasi tentang progresnya menggunakan kolom Operation.metadata. API harus menentukan pesan untuk metadata ini meskipun penerapan awal tidak mengisi kolom metadata.

Mencantumkan Penomoran Halaman

Koleksi yang dapat dicantumkan harus mendukung penomoran halaman, meskipun hasilnya biasanya kecil.

Rasionale: Jika API tidak mendukung penomoran halaman dari awal, mendukungnya nanti akan sulit karena menambahkan penomoran halaman akan merusak perilaku API. Klien yang tidak menyadari bahwa API sekarang menggunakan penomoran halaman dapat salah mengasumsikan bahwa mereka menerima hasil lengkap, padahal sebenarnya mereka hanya menerima halaman pertama.

Untuk mendukung penomoran halaman (menampilkan hasil daftar di halaman) dalam metode List, API harus:

  • tentukan kolom string page_token dalam pesan permintaan metode List. Klien menggunakan kolom ini untuk meminta halaman tertentu dari hasil daftar.
  • tentukan kolom int32 page_size dalam pesan permintaan metode List. Klien menggunakan kolom ini untuk menentukan jumlah hasil maksimum yang akan ditampilkan oleh server. Server dapat membatasi lebih lanjut jumlah maksimum hasil yang ditampilkan dalam satu halaman. Jika page_size adalah 0, server akan memutuskan jumlah hasil yang akan ditampilkan.
  • menentukan kolom string next_page_token dalam pesan respons metode List. Kolom ini menunjukkan token penomoran halaman untuk mengambil halaman hasil berikutnya. Jika nilainya "", artinya tidak ada hasil lebih lanjut untuk permintaan tersebut.

Untuk mengambil halaman hasil berikutnya, klien harus meneruskan nilai next_page_token respons dalam panggilan metode List berikutnya (di kolom page_token pesan permintaan):

rpc ListBooks(ListBooksRequest) returns (ListBooksResponse);

message ListBooksRequest {
  string parent = 1;
  int32 page_size = 2;
  string page_token = 3;
}

message ListBooksResponse {
  repeated Book books = 1;
  string next_page_token = 2;
}

Saat klien meneruskan parameter kueri selain token halaman, layanan harus menggagalkan permintaan jika parameter kueri tidak konsisten dengan token halaman.

Konten token halaman harus berupa buffering protokol berenkode base64 yang aman untuk URL. Dengan begitu, konten dapat berkembang tanpa masalah kompatibilitas. Jika token halaman berisi informasi yang berpotensi sensitif, informasi tersebut harus dienkripsi. Layanan harus mencegah modifikasi token halaman agar tidak mengekspos data yang tidak diinginkan melalui salah satu metode berikut:

  • memerlukan parameter kueri untuk ditentukan ulang pada permintaan tindak lanjut.
  • hanya mereferensikan status sesi sisi server di token halaman.
  • mengenkripsi dan menandatangani parameter kueri di token halaman, serta memvalidasi ulang dan memberikan otorisasi ulang parameter ini pada setiap panggilan.

Implementasi penomoran halaman dapat juga memberikan jumlah total item dalam kolom int32 bernama total_size.

Membuat Daftar Sub-Koleksi

Terkadang, API perlu mengizinkan List/Search klien di seluruh subkoleksi. Misalnya, Library API memiliki koleksi rak, dan setiap rak memiliki koleksi buku, dan klien ingin menelusuri buku di semua rak. Dalam kasus tersebut, sebaiknya gunakan List standar di subkoleksi dan tentukan ID koleksi karakter pengganti "-" untuk koleksi induk. Untuk contoh Library API, kita dapat menggunakan permintaan REST API berikut:

GET https://library.googleapis.com/v1/shelves/-/books?filter=xxx

Mendapatkan Referensi Unik dari Sub-Koleksi

Terkadang, resource dalam subkoleksi memiliki ID yang unik dalam koleksi induknya. Dalam hal ini, sebaiknya izinkan Get mengambil resource tersebut tanpa mengetahui koleksi induk mana yang berisi resource tersebut. Dalam kasus tersebut, sebaiknya gunakan Get standar pada resource dan tentukan ID koleksi karakter pengganti "-" untuk semua koleksi induk yang memiliki resource tersebut unik. Misalnya, di Library API, kita dapat menggunakan permintaan REST API berikut, jika buku tersebut unik di antara semua buku di semua rak:

GET https://library.googleapis.com/v1/shelves/-/books/{id}

Nama resource dalam respons terhadap panggilan ini harus menggunakan nama kanonis resource, dengan ID koleksi induk yang sebenarnya, bukan "-", untuk setiap koleksi induk. Misalnya, permintaan di atas harus menampilkan resource dengan nama seperti shelves/shelf713/books/book8141, bukan shelves/-/books/book8141.

Urutan Pengurutan

Jika metode API memungkinkan klien menentukan urutan pengurutan untuk hasil daftar, pesan permintaan seharusnya berisi kolom:

string order_by = ...;

Nilai string harus mengikuti sintaksis SQL: daftar kolom yang dipisahkan koma. Contoh: "foo,bar". Urutan penyortiran {i>default<i}-nya adalah menaik. Untuk menentukan urutan menurun untuk suatu kolom, akhiran " desc" harus ditambahkan ke nama kolom. Contoh: "foo desc,bar".

Karakter spasi yang redundan dalam sintaksis tidak signifikan. "foo,bar desc" dan "  foo ,  bar  desc  " setara.

Minta Validasi

Jika metode API memiliki efek samping dan perlu memvalidasi permintaan tanpa menyebabkan efek samping tersebut, pesan permintaan harus berisi kolom:

bool validate_only = ...;

Jika kolom ini disetel ke true, server tidak boleh menjalankan efek samping apa pun dan hanya melakukan validasi spesifik per penerapan secara konsisten dengan permintaan penuh.

Jika validasi berhasil, google.rpc.Code.OK harus ditampilkan dan permintaan lengkap apa pun yang menggunakan pesan permintaan yang sama tidak boleh menampilkan google.rpc.Code.INVALID_ARGUMENT. Perhatikan bahwa permintaan mungkin masih gagal karena error lain seperti google.rpc.Code.ALREADY_EXISTS atau karena kondisi race.

Minta Duplikasi

Untuk API jaringan, metode API idempoten sangat disarankan karena dapat dicoba lagi dengan aman setelah terjadi kegagalan jaringan. Namun, beberapa metode API tidak dapat dengan mudah bersifat idempoten, seperti membuat resource, dan terdapat kebutuhan untuk menghindari duplikasi yang tidak diperlukan. Untuk kasus penggunaan semacam itu, pesan permintaan harus berisi ID unik, seperti UUID, yang akan digunakan server untuk mendeteksi duplikasi dan memastikan permintaan hanya diproses satu kali.

// A unique request ID for server to detect duplicated requests.
// This field **should** be named as `request_id`.
string request_id = ...;

Jika permintaan duplikat terdeteksi, server harus menampilkan respons untuk permintaan yang sebelumnya berhasil, karena klien kemungkinan besar tidak menerima respons sebelumnya.

Nilai Default Enum

Setiap definisi enum harus dimulai dengan entri bernilai 0, yang akan digunakan saat nilai enum tidak ditentukan secara eksplisit. API harus mendokumentasikan cara penanganan nilai 0.

Nilai enum 0 harus diberi nama ENUM_TYPE_UNSPECIFIED. Jika ada perilaku default umum, perilaku tersebut akan digunakan saat nilai enum tidak ditentukan secara eksplisit. Jika tidak ada perilaku default umum, nilai 0 harus ditolak dengan error INVALID_ARGUMENT saat digunakan.

enum Isolation {
  // Not specified.
  ISOLATION_UNSPECIFIED = 0;
  // Reads from a snapshot. Collisions occur if all reads and writes cannot be
  // logically serialized with concurrent transactions.
  SERIALIZABLE = 1;
  // Reads from a snapshot. Collisions occur if concurrent transactions write
  // to the same rows.
  SNAPSHOT = 2;
  ...
}

// When unspecified, the server will use an isolation level of SNAPSHOT or
// better.
Isolation level = 1;

Nama idiomatis dapat digunakan untuk nilai 0. Misalnya, google.rpc.Code.OK adalah cara idiomatis untuk menentukan tidak adanya kode error. Dalam hal ini, OK secara semantik setara dengan UNSPECIFIED dalam konteks jenis enum.

Jika terdapat default yang masuk akal dan aman secara intrinsik, nilai tersebut dapat digunakan untuk nilai '0'. Misalnya, BASIC adalah nilai '0' dalam enum Resource View.

Sintaksis Tata Bahasa

Dalam desain API, sering kali perlu mendefinisikan tata bahasa sederhana untuk format data tertentu, seperti input teks yang dapat diterima. Untuk memberikan pengalaman developer yang konsisten di seluruh API dan mengurangi kurva pembelajaran, desainer API harus menggunakan varian sintaksis Extended Backus-Naur Form (EBNF) berikut untuk menentukan tata bahasa tersebut:

Production  = name "=" [ Expression ] ";" ;
Expression  = Alternative { "|" Alternative } ;
Alternative = Term { Term } ;
Term        = name | TOKEN | Group | Option | Repetition ;
Group       = "(" Expression ")" ;
Option      = "[" Expression "]" ;
Repetition  = "{" Expression "}" ;

Jenis Bilangan Bulat

Dalam desain API, jenis integer yang tidak ditandatangani seperti uint32 dan fixed32 tidak boleh digunakan karena beberapa bahasa dan sistem pemrograman penting tidak mendukungnya dengan baik, seperti Java, JavaScript, dan OpenAPI. Selain itu, jenis error ini cenderung menyebabkan error tambahan. Masalah lainnya adalah API yang berbeda sangat mungkin menggunakan jenis bertanda tangan dan tidak bertanda tangan yang tidak cocok untuk hal yang sama.

Jika jenis bilangan bulat bertanda digunakan untuk hal-hal yang nilai negatifnya tidak bermakna, seperti ukuran atau waktu tunggu, nilai -1 (dan hanya -1) dapat digunakan untuk menunjukkan makna khusus, seperti akhir file (EOF), waktu tunggu tak terbatas, batas kuota tidak terbatas, atau usia yang tidak diketahui. Penggunaan tersebut harus didokumentasikan dengan jelas untuk menghindari kebingungan. Produsen API juga harus mendokumentasikan perilaku nilai default implisit 0 jika tidak terlalu jelas.

Respons Sebagian

Terkadang klien API hanya memerlukan subset data tertentu dalam pesan respons. Untuk mendukung kasus penggunaan tersebut, beberapa platform API menyediakan dukungan native untuk respons parsial. Google API Platform mendukungnya melalui mask kolom respons.

Untuk setiap panggilan REST API, terdapat parameter kueri sistem implisit $fields, yang merupakan representasi JSON dari nilai google.protobuf.FieldMask. Pesan respons akan difilter oleh $fields sebelum dikirim kembali ke klien. Logika ini ditangani secara otomatis untuk semua metode API oleh Platform API.

GET https://library.googleapis.com/v1/shelves?$fields=shelves.name
GET https://library.googleapis.com/v1/shelves/123?$fields=name

Tampilan Referensi

Untuk mengurangi traffic jaringan, terkadang ada baiknya mengizinkan klien membatasi bagian resource mana yang harus ditampilkan oleh server dalam responsnya, yang menampilkan tampilan resource, bukan representasi resource lengkap. Dukungan tampilan resource di API diimplementasikan dengan menambahkan parameter ke permintaan metode yang memungkinkan klien menentukan tampilan resource yang ingin diterima dalam respons.

Parameter:

  • harus berjenis enum
  • harus diberi nama view

Setiap nilai enumerasi menentukan bagian resource (kolom mana) yang akan ditampilkan dalam respons server. Apa yang ditampilkan untuk setiap nilai view ditentukan oleh implementasi dan harus ditentukan dalam dokumentasi API.

package google.example.library.v1;

service Library {
  rpc ListBooks(ListBooksRequest) returns (ListBooksResponse) {
    option (google.api.http) = {
      get: "/v1/{name=shelves/*}/books"
    }
  };
}

enum BookView {
  // Not specified, equivalent to BASIC.
  BOOK_VIEW_UNSPECIFIED = 0;

  // Server responses only include author, title, ISBN and unique book ID.
  // The default value.
  BASIC = 1;

  // Full representation of the book is returned in server responses,
  // including contents of the book.
  FULL = 2;
}

message ListBooksRequest {
  string name = 1;

  // Specifies which parts of the book resource should be returned
  // in the response.
  BookView view = 2;
}

Konstruksi ini akan dipetakan ke URL seperti:

GET https://library.googleapis.com/v1/shelves/shelf1/books?view=BASIC

Anda dapat mengetahui lebih lanjut cara menentukan metode, permintaan, dan respons di bab Metode Standar dalam Panduan Desain ini.

ETags

ETag adalah pengenal buram yang memungkinkan klien membuat permintaan bersyarat. Untuk mendukung ETag, API harus menyertakan kolom string etag dalam definisi resource, dan semantiknya harus sesuai dengan penggunaan umum ETag. Biasanya, etag berisi sidik jari resource yang dihitung oleh server. Lihat Wikipedia dan RFC 7232 untuk detail selengkapnya.

ETag dapat divalidasi dengan kuat atau lemah. ETag yang divalidasi dengan lemah akan diawali dengan W/. Dalam konteks ini, validasi kuat berarti dua resource yang memiliki ETag yang sama memiliki konten identik byte-untuk-byte dan kolom tambahan yang identik (yaitu, Content-Type). Ini berarti ETag yang tervalidasi dengan baik mengizinkan penyimpanan respons parsial dalam cache untuk disusun nanti.

Sebaliknya, resource yang memuat nilai ETag yang tervalidasi dengan lemah berarti bahwa representasi tersebut setara secara semantik, tetapi tidak harus identik byte-untuk-byte, sehingga tidak cocok untuk caching respons permintaan rentang byte.

Contoh:

// This is a strong ETag, including the quotes.
"1a2f3e4d5b6c7c"
// This is a weak ETag, including the prefix and quotes.
W/"1a2b3c4d5ef"

Penting untuk dipahami bahwa tanda kutip benar-benar merupakan bagian dari nilai ETag dan harus ada agar sesuai dengan RFC 7232. Ini berarti representasi JSON dari ETag akan terhapus dari tanda petik. Misalnya, ETag akan ditampilkan dalam isi resource JSON sebagai:

// Strong
{ "etag": "\"1a2f3e4d5b6c7c\"", "name": "...", ... }
// Weak
{ "etag": "W/\"1a2b3c4d5ef\"", "name": "...", ... }

Ringkasan karakter yang diizinkan dalam ETag:

  • Khusus ASCII yang dapat dicetak
    • Karakter non-ASCII yang diizinkan oleh RFC 7232, tetapi kurang cocok untuk developer
  • Tanpa spasi
  • Tidak boleh ada tanda kutip ganda selain pada posisi yang ditunjukkan di atas
  • Hindari garis miring terbalik seperti yang direkomendasikan oleh RFC 7232 untuk mencegah kebingungan terkait escape

Kolom Output

API mungkin ingin membedakan antara kolom yang disediakan oleh klien sebagai input dan kolom yang hanya ditampilkan oleh server pada output pada resource tertentu. Untuk kolom yang hanya merupakan output, atribut kolom akan diberi anotasi.

Perlu diketahui bahwa jika kolom khusus output ditetapkan dalam permintaan atau disertakan dalam google.protobuf.FieldMask, server harus menerima permintaan tanpa error. Server harus mengabaikan keberadaan kolom hanya output dan indikasi apa pun. Alasan adanya rekomendasi ini karena klien sering menggunakan kembali resource yang ditampilkan server sebagai input permintaan lain, misalnya Book yang diambil akan digunakan kembali dalam metode UPDATE. Jika hanya kolom output yang divalidasi, tindakan ini akan menempatkan tugas tambahan pada klien untuk menghapus kolom khusus output.

import "google/api/field_behavior.proto";

message Book {
  string name = 1;
  Timestamp create_time = 2 [(google.api.field_behavior) = OUTPUT_ONLY];
}

Resource Singleton

Resource singleton dapat digunakan jika hanya ada satu instance resource dalam resource induknya (atau dalam API, jika tidak memiliki induk).

Metode Create dan Delete standar harus dihilangkan untuk resource singleton; singleton secara implisit dibuat atau dihapus saat induknya dibuat atau dihapus (dan secara implisit ada jika tidak memiliki induk). Resource tersebut harus diakses menggunakan metode Get dan Update standar, serta metode khusus apa pun yang sesuai untuk kasus penggunaan Anda.

Misalnya, API dengan resource User dapat mengekspos setelan per pengguna sebagai singleton Settings.

rpc GetSettings(GetSettingsRequest) returns (Settings) {
  option (google.api.http) = {
    get: "/v1/{name=users/*/settings}"
  };
}

rpc UpdateSettings(UpdateSettingsRequest) returns (Settings) {
  option (google.api.http) = {
    patch: "/v1/{settings.name=users/*/settings}"
    body: "settings"
  };
}

[...]

message Settings {
  string name = 1;
  // Settings fields omitted.
}

message GetSettingsRequest {
  string name = 1;
}

message UpdateSettingsRequest {
  Settings settings = 1;
  // Field mask to support partial updates.
  FieldMask update_mask = 2;
}

Streaming Setengah Sebentar

Untuk API dua arah atau streaming klien, server harus mengandalkan half-close yang dimulai oleh klien, seperti yang disediakan oleh sistem RPC, untuk menyelesaikan streaming sisi klien. Anda tidak perlu menentukan pesan penyelesaian eksplisit.

Informasi apa pun yang perlu dikirim klien sebelum penutup setengah harus ditetapkan sebagai bagian dari pesan permintaan.

Nama cakupan domain

Nama cakupan domain adalah nama entity yang diawali dengan nama domain DNS untuk mencegah konflik nama. Ini adalah pola desain yang berguna saat berbagai organisasi menetapkan nama entity mereka secara terdesentralisasi. Sintaksisnya menyerupai URI tanpa skema.

Nama cakupan domain banyak digunakan di antara Google API dan Kubernetes API, seperti:

  • Representasi jenis Any Protobuf: type.googleapis.com/google.protobuf.Any
  • Jenis metrik Stackdriver: compute.googleapis.com/instance/cpu/utilization
  • Kunci label: cloud.googleapis.com/location
  • Versi Kubernetes API: networking.k8s.io/v1
  • Kolom kind di ekstensi OpenAPI x-kubernetes-group-version-kind.

Bool vs. Enum vs. String

Saat mendesain metode API, sebaiknya Anda menyediakan serangkaian pilihan untuk fitur tertentu, seperti mengaktifkan pelacakan atau menonaktifkan penyimpanan cache. Cara umum untuk mencapainya adalah dengan memperkenalkan kolom permintaan jenis bool, enum, atau string. Tidak selalu terlihat jelas jenis apa yang tepat digunakan untuk kasus penggunaan tertentu. Pilihan yang direkomendasikan adalah sebagai berikut:

  • Menggunakan jenis bool jika kita ingin memiliki desain tetap dan sengaja tidak ingin memperluas fungsi. Misalnya, bool enable_tracing atau bool enable_pretty_print.

  • Menggunakan jenis enum jika kita ingin memiliki desain yang fleksibel, tetapi tidak berharap desainnya akan sering berubah. Prinsipnya adalah definisi enum hanya akan berubah setahun sekali atau lebih jarang. Misalnya, enum TlsVersion atau enum HttpVersion.

  • Menggunakan jenis string jika kita memiliki desain terbuka atau desain dapat sering diubah oleh standar eksternal. Nilai-nilai yang didukung harus didokumentasikan dengan jelas. Contoh:

Retensi Data

Saat mendesain layanan API, retensi data merupakan aspek penting dari keandalan layanan. Data pengguna umumnya dihapus secara keliru oleh bug software atau error manusia. Tanpa retensi data dan fungsi pembatalan penghapusan terkait, kesalahan sederhana dapat menyebabkan dampak bisnis yang fatal.

Secara umum, kami merekomendasikan kebijakan retensi data berikut untuk layanan API:

  • Untuk metadata pengguna, setelan pengguna, dan informasi penting lainnya, harus ada retensi data selama 30 hari. Misalnya, metrik pemantauan, metadata project, dan definisi layanan.

  • Untuk konten pengguna bervolume besar, harus ada retensi data selama 7 hari. Misalnya, blob biner dan tabel database.

  • Untuk status sementara atau penyimpanan yang mahal, harus ada retensi data 1 hari jika memungkinkan. Misalnya, instance memcache dan server Redis.

Selama jendela retensi data, data dapat dibatalkan penghapusannya tanpa kehilangan data. Jika menawarkan retensi data secara gratis itu mahal, layanan dapat menawarkan retensi data sebagai opsi berbayar.

Payload Besar

API jaringan sering kali bergantung pada beberapa lapisan jaringan untuk jalur datanya. Sebagian besar lapisan jaringan memiliki batas yang pasti untuk ukuran permintaan dan respons. 32 MB adalah batas yang umum digunakan di banyak sistem.

Ketika merancang metode API yang menangani payload yang lebih besar dari 10 MB, kita harus berhati-hati dalam memilih strategi yang tepat untuk kegunaan dan pertumbuhan di masa mendatang. Untuk Google API, sebaiknya gunakan streaming atau upload/download media untuk menangani payload yang besar. Dengan streaming, server secara bertahap menangani data besar secara sinkron, seperti Cloud Spanner API. Dengan media, data besar mengalir melalui sistem penyimpanan besar, seperti Google Cloud Storage, dan server dapat menangani data secara asinkron, seperti Google Drive API.

Kolom Primitif Opsional

Protocol Buffer v3 (proto3) mendukung kolom primitif optional, yang secara semantik setara dengan jenis nullable dalam banyak bahasa pemrograman. Parameter ini dapat digunakan untuk membedakan nilai kosong dari nilai yang tidak ditetapkan.

Dalam praktiknya, developer kesulitan menangani kolom opsional dengan benar. Sebagian besar library klien HTTP JSON, termasuk Library Klien Google API, tidak dapat membedakan proto3 int32, google.protobuf.Int32Value, dan optional int32. Jika desain alternatif sama jelasnya dan tidak memerlukan primitif opsional, pilih itu. Jika tidak menggunakan opsional akan menambah kerumitan atau ambiguitas, maka gunakan kolom primitif opsional. Jenis wrapper tidak boleh digunakan ke depannya. Secara umum, desainer API harus menggunakan jenis primitif biasa, seperti int32, agar lebih sederhana dan konsisten.