Lexicon の概要

Lexicon は、RPC メソッドとレコード型を定義するために使用されるスキーマ システムです。すべての Lexicon スキーマは、制約を定義するための JSON-Schema に似た形式で JSON で記述されます。

スキーマは、逆 DNS 形式である NSID を使用して識別されます。以下にメソッドの例を示します:

com.atproto.repo.getRecord
com.atproto.identity.resolveHandle
app.bsky.feed.getPostThread
app.bsky.notification.listNotifications

以下にレコード型の例を示します:

app.bsky.feed.post
app.bsky.feed.like
app.bsky.actor.profile
app.bsky.graph.follow

スキーマ型、定義言語、検証制約は Lexicon 仕様 で説明されており、JSON および CBOR での表現は データモデル仕様 で説明されています。

Lexicon が必要な理由

相互運用性。 atproto のようなオープンネットワークには、動作とセマンティクスについて合意する方法が必要です。 Lexicon は、開発者が比較的簡単に新しいスキーマを導入できるようにしながら、この問題を解決します。

Lexicon は RDF ではありません。 RDF はデータの記述には効果的ですが、スキーマの適用には向いていません。Lexicon は、RDF が提供するような一般性を必要としないため、より使いやすくなっています。実際、Lexicon のスキーマは、型と検証を使用したコード生成を可能にし、生活がより楽になります!

HTTP API メソッド

AT プロトコルの API システム XRPC は、基本的に HTTPS の薄いラッパーです。その目的は、Lexicon を HTTPS に適用することです。

たとえば、次の呼び出し

com.example.getProfile()

は、実際には次のような HTTP リクエストになります。

GET /xrpc/com.example.getProfile

スキーマは、有効なクエリー・パラメータ、リクエスト本文、およびレスポンス本文を確立します。

{
  "lexicon": 1,
  "id": "com.example.getProfile",
  "defs": {
    "main": {
      "type": "query",
      "parameters": {
        "type": "params",
        "required": ["user"],
        "properties": {
           "user": { "type": "string" }
        },
      },
      "output": {
        "encoding": "application/json",
        "schema": {
          "type": "object",
          "required": ["did", "name"],
          "properties": {
            "did": {"type": "string"},
            "name": {"type": "string"},
            "displayName": {"type": "string", "maxLength": 64},
            "description": {"type": "string", "maxLength": 256}
          }
        }
      }
    }
  }
}

コード生成により、これらのスキーマは非常に使いやすくなります:

await client.com.example.getProfile({user: 'bob.com'})
// => {name: 'bob.com', did: 'did:plc:1234', displayName: '...', ...}

レコード型

スキーマは、レコードが取りうる値を定義します。すべてのレコードには、スキーマにマップされ、レコードの URL を確立する "type" があります。

たとえば、この "follow" レコード:

{
  "$type": "com.example.follow",
  "subject": "at://did:plc:12345",
  "createdAt": "2022-10-09T17:51:55.043Z"
}

...URL は次のようになります:

at://bob.com/com.example.follow/12345

...スキーマは次のようになります:

{
  "lexicon": 1,
  "id": "com.example.follow",
  "defs": {
    "main": {
      "type": "record",
      "description": "A social follow",
      "record": {
        "type": "object",
        "required": ["subject", "createdAt"],
        "properties": {
          "subject": { "type": "string" },
          "createdAt": {"type": "string", "format": "datetime"}
        }
      }
    }
  }
}

トークン

トークンは、データで使用できるグローバル識別子を宣言します。

レコードスキーマで、信号機の 3 つの状態、「赤(red)」、「黄(yellow)」、「緑(green)」を指定するとします。

{
  "lexicon": 1,
  "id": "com.example.trafficLight",
  "defs": {
    "main": {
      "type": "record",
      "record": {
        "type": "object",
        "required": ["state"],
        "properties": {
          "state": { "type": "string", "enum": ["red", "yellow", "green"] },
        }
      }
    }
  }
}

これはまったく問題がありませんが、拡張性がありません。「点滅する黄色(flashing yellow)」や「紫色(purple)」などの新しい状態を追加することはできません(もしかすると、そんなことが起こるかもしれません)。

柔軟性を高めるには、列挙型の制約を削除し、可能な値のみを文書化します:

{
  "lexicon": 1,
  "id": "com.example.trafficLight",
  "defs": {
    "main": {
      "type": "record",
      "record": {
        "type": "object",
        "required": ["state"],
        "properties": {
          "state": {
            "type": "string",
            "description": "Suggested values: red, yellow, green"
          }
        }
      }
    }
  }
}

これは悪くありませんが、具体性に欠けています。状態の新しい値を考案しようとする人々は互いに衝突する可能性がありますし、各状態に関する明確な文書がありません。

代わりに、使用する値に対して Lexicon トークンを定義できます:

{
  "lexicon": 1,
  "id": "com.example.green",
  "defs": {
    "main": {
      "type": "token",
      "description": "Traffic light state representing 'Go!'.",
    }
  }
}
{
  "lexicon": 1,
  "id": "com.example.yellow",
  "defs": {
    "main": {
      "type": "token",
      "description": "Traffic light state representing 'Stop Soon!'.",
    }
  }
}
{
  "lexicon": 1,
  "id": "com.example.red",
  "defs": {
    "main": {
      "type": "token",
      "description": "Traffic light state representing 'Stop!'.",
    }
  }
}

これにより、trafficLight 状態で使用する明確な値が得られます。最終的なスキーマでは柔軟な検証が引き続き使用されますが、他のチームでは、値の取得元と独自の値を追加する方法がより明確になります。

{
  "lexicon": 1,
  "id": "com.example.trafficLight",
  "defs": {
    "main": {
      "type": "record",
      "record": {
        "type": "object",
        "required": ["state"],
        "properties": {
          "state": {
            "type": "string",
            "knownValues": [
              "com.example.green",
              "com.example.yellow",
              "com.example.red"
            ]
          }
        }
      }
    }
  }
}

バージョン管理

スキーマが公開されると、その制約を変更することはできません。制約を緩めると(可能な値を追加すると)、古いソフトウェアは新しいデータの検証に失敗し、制約を厳しくすると(可能な値を削除すると)、新しいソフトウェアは古いデータの検証に失敗します。結果として、スキーマは、以前に制約がなかったフィールドにオプションの制約のみを追加することしかできません。

スキーマが以前に公開された制約を変更する必要がある場合は、新しい NSID で新しいスキーマとして公開する必要があります。

スキーマの配布

スキーマは、機械可読でネットワークからアクセス可能になるように設計されています。現在、スキーマがネットワーク上で利用可能であることは必須ではありませんが、メソッドの利用者が単一の標準的で信頼できる表現を利用できるように、スキーマを公開することを強くお勧めします。