Google Drive API Changes:list にはまった話

謎の事業本部のサーバサイドエンジニアの@mashiikeです。
これは2020年カヤックアドベントカレンダーの1日目の記事です。今年もよろしくお願いします。

英語苦手な自分は、英語のドキュメントをGoogle翻訳様にお願いして翻訳してもらい斜め読みしています。
ところが、そんなことをしていると思わぬところにハマることもあります。
今回は ドキュメントを注意深く読みましょう という話をします。

背景

とある社内案件で、大量のGoogle Spreadsheetの変更監視・状態同期をするアプリケーションを作成してました。

初期の頃は、Spreadsheetの件数も少なく Files:get のAPIを使用することで状態の確認が可能でした。
しかし、Spreadsheetの件数が多くなってくると、Usage limitに到達するようになり『どうしたものかなー』と言う状態になり、 Changes:list を使用したいという状況になってまいりました。

謎のremoved事件

Changes:listのAPIを使用するためには、まずはじめに Changes:getStartPageToken というAPIより最初にStartPageTokenというものを取得します。
そして、Changes:listはそのStartPageTokenは取得した時点からのGoogle Driveに起きた変更を一覧して返します。

Go言語のコードで試しに取得したトークンを指定して、その挙動を確認してみます。

package main

import (
    "context"
    "encoding/json"
    "log"

    "google.golang.org/api/drive/v3"
)

func main() {
    ctx := context.Background()
    srv, err := drive.NewService(ctx)
    if err != nil {
        log.Fatal(err)
    }
    token := "10000" //実際には別途取得したトークンを指定
    changes, err := srv.Changes.List(token).Do()
    if err != nil {
        log.Fatal(err)
    }
    bs, _ := json.MarshalIndent(changes, "", "  ")
    log.Println(string(bs))
}

StartPageTokenを適当に取得したあとに、コードを編集してTokenを置き換えて go run main.go として実行! (実際には GOOGLE_APPLICATION_CREDENTIALS を指定するなどの作業もあります)

すると以下のような結果が帰って来ます。(※もちろんこれはダミーの結果に差し替えてあります。)

$ go run main.go
2020/11/30 18:30:00 {
  "changes": [
    {
      "changeType": "file",
      "fileId": "1234567890abcdefghijk1234567890abcdefghijk00",
      "kind": "drive#change",
      "removed": true,
      "time": "2020-11-30T17:55:30.126Z",
      "type": "file"
    },
    {
      "changeType": "file",
      "file": {
        "id": "abcxyzOPQRS123_ABCXYZopqrs456-568KEKEKEKEKEK",
        "kind": "drive#file",
        "mimeType": "application/vnd.google-apps.spreadsheet",
        "name": "すごい秘密のスプレットシート!"
      },
      "fileId": "abcxyzOPQRS123_ABCXYZopqrs456-568KEKEKEKEKEK",
      "kind": "drive#change",
      "time": "2020-11-30T18:00:30.126Z",
      "type": "file"
    }
  ],
  "kind": "drive#changeList",
  "newStartPageToken": "10003"
}

この結果をみて、removed とされていている変更があります。

https://developers.google.com/drive/api/v3/reference/changes#resource

こちらのAPIドキュメントによれば、以下のように書いてあります。

  • Property name: removed
  • Value: boolean
  • Description: Whether the file or shared drive has been removed from this list of changes, for example by deletion or loss of access.

なるほど。
..., for example by deletion or loss of access. ということですので、
ファイルが削除された、もしくはアクセス権が消えたようです。
しかし、関係者の方々に聞いてみるとファイルを削除したわけではないようです。
このプログラムに渡しているサービスアカウントのアクセス権も消えていないようです。
これが謎のremoved事件です。

真相

Go言語のコードを少し修正してみますと、真相が判明しました。

  • before:
changes, err := srv.Changes.List(token).Do()
  • after:
changes, err := srv.Changes.List(token).
        IncludeItemsFromAllDrives(true).
        SupportsAllDrives(true).
        Do()

このように変更してみると、以下のように結果が変わりました。

$ go run main.go
2020/11/30 19:00:00 {
  "changes": [
    {
      "changeType": "file",
      "file": {
        "id": "1234567890abcdefghijk1234567890abcdefghijk00",
        "kind": "drive#file",
        "mimeType": "application/vnd.google-apps.spreadsheet",
        "name": "共有ドライブに入ったみんなの秘密スプレットシート!"
      },
      "fileId": "1234567890abcdefghijk1234567890abcdefghijk00",
      "kind": "drive#change",
      "time": "2020-11-30T17:55:30.126Z",
      "type": "file"
    },
    {
      "changeType": "file",
      "file": {
        "id": "abcxyzOPQRS123_ABCXYZopqrs456-568KEKEKEKEKEK",
        "kind": "drive#file",
        "mimeType": "application/vnd.google-apps.spreadsheet",
        "name": "すごい秘密のスプレットシート!"
      },
      "fileId": "abcxyzOPQRS123_ABCXYZopqrs456-568KEKEKEKEKEK",
      "kind": "drive#change",
      "time": "2020-11-30T18:00:30.126Z",
      "type": "file"
    }
  ],
  "kind": "drive#changeList",
  "newStartPageToken": "10003"
}

removedが消えました!
(ダミーのIDですが)FileId:1234567890abcdefghijk1234567890abcdefghijk00は実は共有ドライブのファイルだったのでした。
でもなぜ? もう一回ドキュメントを注意深く読んでみます。 Whether the file or shared drive has been removed from this list of changes, ...

ん?、読んでそのままの意味で捉えましょう。変更のリストから削除されたらremovedtrue になるようです。 そして、コードで変更したのは、共有ドライブをサポートするかどうかに関わるパラメータです。
どうやら、supportsAllDrives と includeItemsFromAllDrives が false だった最初の場合、変更として知りたいものの対象外なので レスポンスのリストから削除 されていたようです。 なるほど!!!!!

おわりに

というわけで、今回は ドキュメントを注意深く読みましょう という事例な話をしました。
エンジニアには英語を読む力も大事ですねということを思いました。
ドキュメントの斜め読みも良くないですねということも思いました。

きっと明日は誰かがもっといい話をしてくれることでしょう。