Rest APIの設計-IDまたはリテラル文字列を操作しますか?


8

RESTful Webサービスを設計するとき、サーバー間でやり取りされる値の文字列のIDを機能するようにAPIを設計する必要がありますか?

次に例を示します。ステータスと性別の属性を持つ従業員リソースがあるとします。データベースのステータスと性別、および個別のテーブル、つまり個別のドメインオブジェクトで、それぞれが独自の識別子を持ちます。

クライアントのリクエスト/ employee / 1としましょう。サーバーが次のようなものを返す可能性があります...

ケース1:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "id": 1,
        "gender": "FEMALE"
    },
    "status": {
        "id": 3,
        "status": "FULL_TIME"
    }
}

ケース2:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "FEMALE",
    "status": "FULL_TIME"
}

ケース3:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "genderId": 1,
    "statusId": 3
}

ケース3は、クライアントが性別ID 1が何であるかわからないので、向きを変えてサーバーにもう一度呼び出してそのデータを取得しない限り、最も意味がありません。

ただし、クライアントが次の方法でユーザーを更新しているとします。

PUT /employee/1

リクエストのペイロードはIDまたは文字列を使用する必要がありますか?どちらの方法でも、バックエンドはそれらが有効であることを確認するためにそれらをルックアップする必要がありますが、文字列よりもIDを処理する方が適切です。

回答:


3

ステータスと性別の属性を持つ従業員リソースがあるとします。データベースのステータスと性別、および個別のテーブル、つまり個別のドメインオブジェクトで、それぞれが独自の識別子を持ちます。

APIの表現は、実装の詳細と密接に結びついてはなりません。私は、実装の詳細からAPI表現を導き出すことは、まったく逆であるとまで言っています。

ギャングオブフォーブックAdapter Patternから考える。Webのメッセージは、ドキュメントストアのメッセージです。APIを作成する際の目標は、消費者が必要とするドキュメントを作成すると同時に、それらのドキュメントを作成する際の重要な詳細から隔離することです。

これを行う動機は、いつでも実装の詳細をいつでも変更できることです。そのため、返される表現を変更しない限り、クライアントが壊れることはありません。

また、単一の論理リソースには多くの表現が含まれる場合があり、その一部のみが変更をサポートすることに注意してください。

クライアントがユーザーを更新しているとしましょう

消費者として、どの表現を使いたいですか?私の推測では、最も近いのは

{
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "FEMALE",
    "status": "FULL_TIME"
}

その表現を指定した場所に配置すると、残りの部分を正しく処理できるはずです。

マシンが使用する表現を作成している場合は、おそらくスペルの曖昧さを少なくしたいでしょう。

{
    "https://schema.org/givenName": "Jane",
    "https://schema.org/familyName": "Doe",
    "active": true,
    "https://schema.org/gender": "https://schema.org/Female",
    "https://schema.org/employmentType": "FULL_TIME"
}

同じ論理リソース、2つの異なる表現。コース用の馬。


1

ケース1とケース2はどちらも問題ありません。選択は、ドメインモデルの編成方法によって予測できます。

Employeeテーブル、Genderテーブル、Statusテーブルをドメインに反映しました(ORMを使用していると思います)。この特定のモデルのこれらの各クラスは、独自の識別子を持つエンティティです。REST APIを介してモデル全体をさらに公開すると、論理的に見え、ケース1に適合します。

また、あなたはに多くの注目を支払うDDDの原則に固執することの違いとのエンティティと値オブジェクト。この観点から見ると、Employeeはエンティティ(ID付き)であり、GenderとStatus (Employeeエンティティに埋め込まれ、識別子なしで)値オブジェクトになるのに適した候補です。これはケース2に適合します。

ケース3はノーゴーであることを完全に同意します。


1
やむを得ない理由がない限り、私はWebサービスAPIをデータベース設計に密結合することはしません。APIとデータベースには、さまざまなニーズを持つさまざまなクライアントがあります。
Eric Stein

完全に同意します。これは、筆者のAPIデザイン(GenderとStateがIDを公開する)についての私の観察です。DDDの原則に従って、ドメインモデルの値オブジェクトとして設計し、結果として、REST APIを介してそれらの識別子を公開しません(ケース2)。
Serhii Shushliapin 2017

1

ケース2が唯一の現実的な選択肢です。ケース3の問題についてはすでに指摘しました。ケース1は、APIのクライアントが気にしない情報(たとえば、ステータスの内部ID)を提供し、PUTリクエストを構築するためにそれらについて知る必要があります。 。はい、完全な文字列の代わりにIDを使用できる場合、PUTリクエストは少し簡潔になりますが、「FULL_TIME」または「PART_TIME」の指定はクライアントが知っていることであり、データベースに任意のIDが存在することではありません。

もちろん、APIドキュメントでIDを文書化することもできますが、文字列に許可されている有効な値を文書化するのは同じくらい簡単で、おそらくより明確です。


2
これは、性別やステータスの名前を変更できないことを意味することに注意してください。クライアントが名前でのみ作業している場合、名前は事実上一意の識別子です。クライアントがidを一意の識別子として使い始める必要がある場合、それは重大な変更です。
Eric Stein

0

ここにあるような列挙データは非常にキャッシュ可能です。オブジェクトの代わりにリンクを使用します。キャッシングヘッダーを使用して、クライアントが性別とステータスをローカルで、たとえば24時間キャッシュできるようにします。その後、その日の最初の呼び出しだけがクライアントマシンを離れます。おそらく、中間サーバーが情報を保持できるようにキャッシュを構成することもできます。これにより、一部のクライアント要求はサーバーに送信されません。

GET /employees/1
{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": "/genders/1",
    "status": "/statuses/3"
}

// clients populate their dropdown with
GET /genders
[
    {"id":1, "gender":"FEMALE"},
    {"id":2, "gender":"MALE"},
    ...
]

// clients look up an employee's gender with
GET /genders/1
{
    "id": 1,
    "gender": FEMALE
}

欠点の1つは、/genders/1人間が読めないことです。代わりにを使用でき/genders/femaleますが、クライアントを壊さずに性別の名前を変更することはできません。それが、合成キーと自然キーのトレードオフ、つまり柔軟性と人間の可読性です。

次のように、すべての値のリストを1つの共通のエンドポイントの下に置くことを検討することもできます。

/lists/genders/1
/lists/statuses/3

これにより、クライアントはすべて、基本的には異なるグループに属するキーと値のペアであることが明確になります。


0

Davidが言及した理由により、私は1と2の間の何かに行きます。

必要がない限り、IDを公開したくない。

ただし、ある時点でIDの公開が必要になる場合があります。その場合、下位互換性が問題になります。だから、私はこれを行います:

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "name": "FEMALE"
    },
    "status": {
        "name": "FULL_TIME"
    }
}

オプション2と同じプロパティがあります。ただし、後でIDを追加してもBCブレークが発生しないという利点があります。

{
    "id": 1,
    "firstName": "Jane",
    "lastName": "Doe",
    "active": true,
    "gender": {
        "id": 1,
        "name": "FEMALE"
    },
    "status": {
        "id": 3,
        "name": "FULL_TIME"
    }
}

エリックがコメントで指摘しているように、これはまだエンティティ名を一意の識別子として使用しています。IDが後で導入される場合でも、古いクライアントはそのIDに向けてコーディングしている可能性がある(またはそうする可能性がある)ため、名前は同じままである必要があります。


このアプローチは、新しいオプションを導入します。2つの異なるリソースがある:最初のクエリ用と2番目のforo作成または更新。コードが多すぎるように見えるかもしれませんが、メンテナンスが容易になります。
ライヴ2017

@Laiv:私はそれを提案しませんでした。ここではname、クエリと更新にを使用します。
marstato 2017

1
これは、性別やステータスの名前を変更できないことを意味することに注意してください。クライアントが名前でのみ作業している場合、名前は事実上一意の識別子です。クライアントがをid一意の識別子として使用し始める必要がある場合、それは重大な変更です。
Eric Stein
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.