DominoでGraphQL

DominoサーバでGraphQLを作れたらいいな・・・と思ったので、超簡単なプロトタイプを作ってみました。

GraphQL、ご存じない方のために簡単に説明すると、「つながり」でできたデータ構造に問い合わせてデータを取得したり、更新したりすることができる「クエリ仕様」と言えばいいだろうか。

Facebook社が開発したんだけど、Facebook内のデータっていろんなところに「つながり」を持っていて、それを捌くのに開発したんじゃねっていう理解。なので、ツリー構造になっているNotesデータベース構造って、GraphQLでも扱いやすいんじゃないかと思ったのがきっかけ。

GraphQLはスキーマを決めて、スキーマに沿って問い合わせればいい、至極簡単。でもそれって使う側の言い分。実際問題、提供する側は結構面倒。JSONっぽいQueryは似て非なり。なので改めてQueryを字句解析しないといけない。C++での実装はそこが骨の折れるところ。でも優秀なライブラリを見つけたので、今回はこれのおかげ。

github.com

Microsoftとあるけど、LinuxMacでもできるらしい。

ちなみに、試しに作ったスキーマはこれ。

schema {
  query: Query
}

type Query {
  dbDirectory(rootPath: String!): [DbNode!]!
}

interface DbNode {
  path: String!
  isDirectory: Boolean!
}

type DbDirectory implements DbNode {
  path: String!
  isDirectory: Boolean!
  dbDirectory: [DbNode!]!
}

type Database implements DbNode {
  path: String!
  isDirectory: Boolean!
  replicaId: String!
}

最初の schema はGraphQLの3つの機能、問い合わせ(Query)、変更(Mutation)、サブスクリプション(Subscription)のどれを使うかってことらしい。サブスクは基本的にWebSocketなどがないと動かない。Domino HTTPのアドイン(DSAPI)ではおそらくなんともしがたいと思う。

今回はデータの取得だけなので、Queryのみを定義する。

続く type Query は、その問い合わせの仕様について。 dbDirectory というキーワードに rootPath でどのディレクトリ内にあるデータベースやサブディレクトリを取得するかを特定する引数を定義している。後ろの [DbNode!]! は、 DbNode のリストを意味する戻り値の仕様。

interface DbNode は先の DbNode の仕様に他ならないが、先頭が type ではなく interface なのがミソ。Javaなどのインターフェースと同義で、仕様のみで実装はない。Notes C APIではNotesデータベースとディレクトリって扱いが似ていて、ディレクトリもデータベースと同じくデータベースハンドル(DBHANDLE)で扱う。なので、両社に共通のパス名と、ディレクトリであるか否かを判定できるブール値を仕様にしている。

type DbDirectory ではその DbNode を実装してディレクトリを定義している。データベースとの違いは、さらに別のデータベースやディレクトリを配下に持てるので、再帰的に dbDirectory を持っている。最初の Query との違いは、すでにパスを保持している点にある。

type DatabaseDbNode を実装しつつ、ディレクトリにはないものを実装する。今回レプリカIDを取得できるようにしてみた。

例えば、これを使って、 mail ディレクトリ内のデータベースを取得しようと思ったら、次のようなGraphQLを書けばよい。

query {
    dbDirectory(rootPath: "mail") {
        path
        isDirectory
        ...on Database {
            replicaId
        }
    }
}

これでDomino GraphQLに問い合わせると、

{
    "data": {
        "dbDirectory": [
            {
                "path": "mail\\admin.nsf",
                "isDirectory": false,
                "replicaId": "49257D76xxxxxxxx"
            },
            {
                "path": "mail\\hkobayas.nsf",
                "isDirectory": false,
                "replicaId": "492581B9xxxxxxxx"
            },
            {
                "path": "mail\\kshiden.nsf",
                "isDirectory": false,
                "replicaId": "492581B9xxxxxxxx"
            },
            {
                "path": "mail\\rhosei.nsf",
                "isDirectory": false,
                "replicaId": "492581B9xxxxxxxx"
            }
        ]
    }
}

のようなJSONデータが返ってくる。なお、 replicaId を付与すると、一つ一つDBをオープンしてレプリカIDを取得するような実装方法を取ったため、取得するDBやディレクトリが多いと途端に遅くなるので、改善の余地がある。

GraphQLがRESTなどに比べてすごいところは、取りたいデータをコントロールするすべを仕様が定義しているところ。例えば、先ほどのクエリを以下のように変えてみる。

query {
    dbDirectory(rootPath: "mail") {
        path
    }
}

すると、返ってくるデータは次のようになる。

{
    "data": {
        "dbDirectory": [
            {
                "path": "mail\\admin.nsf",
            },
            {
                "path": "mail\\hkobayas.nsf",
            },
            {
                "path": "mail\\kshiden.nsf",
            },
            {
                "path": "mail\\rhosei.nsf",
            }
        ]
    }
}

求めたいフィールドを変更すると、戻り値のフィールドも変更される。GraphQLはそれを仕様として定義している。RESTでももちろんできるが、仕様ではないので、手法は実装者に委ねられている。

今回は本当に序の口。まだまだ紹介できるレベルには達していない。でもなんか元旦早々嬉しくなったので、早出ししてみた。