射影と抜粋
Spring Data REST は、エクスポートするドメインモデルのデフォルトビューを表示します。ただし、さまざまな理由で、そのモデルのビューを変更する必要がある場合があります。このセクションでは、リソースの単純化されたビューと縮小されたビューを提供するために、射影と抜粋を定義する方法について説明します。
射影
次のドメインモデルを検討してください。
@Entity
public class Person {
@Id @GeneratedValue
private Long id;
private String firstName, lastName;
@OneToOne
private Address address;
…
}
前の例の Person
オブジェクトには、いくつかの属性があります。
id
が主キーです。firstName
とlastName
はデータ属性です。address
は、別のドメインオブジェクトへのリンクです。
ここで、次のように、対応するリポジトリを作成するとします。
interface PersonRepository extends CrudRepository<Person, Long> {}
デフォルトでは、Spring Data REST は、すべての属性を含むこのドメインオブジェクトをエクスポートします。firstName
と lastName
は、そのままのプレーンデータオブジェクトとしてエクスポートされます。address
属性には 2 つのオプションがあります。1 つのオプションは、次のように Address
オブジェクトのリポジトリも定義することです。
interface AddressRepository extends CrudRepository<Address, Long> {}
この状況では、Person
リソースは、address
属性を対応する Address
リソースへの URI としてレンダリングします。システムで "Frodo" を検索すると、次のような HAL ドキュメントが表示されることが予想されます。
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
},
"address" : {
"href" : "http://localhost:8080/persons/1/address"
}
}
}
別のメソッドがあります。Address
ドメインオブジェクトに独自のリポジトリ定義がない場合、次の例に示すように、Spring Data REST には Person
リソース内のデータフィールドが含まれます。
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"address" : {
"street": "Bag End",
"state": "The Shire",
"country": "Middle Earth"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
}
}
}
しかし、address
の詳細をまったく知りたくない場合はどうすればよいでしょうか ? ここでも、デフォルトで Spring Data REST はそのすべての属性をエクスポートします ( id
を除く)。1 つ以上の射影を定義することで、REST サービスのコンシューマーに代替案を提供できます。次の例は、住所を含まない射影を示しています。
@Projection(name = "noAddresses", types = { Person.class }) (1)
interface NoAddresses { (2)
String getFirstName(); (3)
String getLastName(); (4)
}
1 | @Projection アノテーションは、これを射影としてフラグ付けします。name 属性は、射影の名前を提供します。これについては、後ほど詳しく説明します。types 属性は、Person オブジェクトにのみ適用されるようにこの射影を対象としています。 |
2 | これは Java インターフェースであり、宣言型になっています。 |
3 | firstName をエクスポートします。 |
4 | lastName をエクスポートします。 |
NoAddresses
射影には、firstName
および lastName
の getter しかありません。つまり、アドレス情報を提供しません。Address
リソース用の個別のリポジトリがあると仮定すると、次の例に示すように、Spring Data REST からのデフォルトのビューは前の表現とは少し異なります。
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1{?projection}", (1)
"templated" : true (2)
},
"address" : {
"href" : "http://localhost:8080/persons/1/address"
}
}
}
1 | このリソースには、{?projection} という新しいオプションがあります。 |
2 | self URI は URI テンプレートです。 |
リソースへの射影を表示するには、localhost:8080/persons/1?projection=noAddresses
を検索します。
projection クエリパラメーターに指定された値は、@Projection(name = "noAddress") で指定された値と同じです。これは、射影のインターフェースの名前とは何の関係もありません。 |
複数の射影を持つことができます。
サンプルプロジェクトを確認するには、射影を参照してください。試してみることをお勧めします。 |
Spring Data REST は、次のように射影定義を見つけます。
エンティティ定義(またはそのサブパッケージの 1 つ)と同じパッケージにある
@Projection
インターフェースがすべて登録されます。RepositoryRestConfiguration.getProjectionConfiguration().addProjection(…)
を使用して、手動で射影を登録できます。
いずれの場合も、射影インターフェースには @Projection
アノテーションが必要です。
既存の射影を見つける
Spring Data REST は、マイクロメタデータ形式であるアプリケーションレベルのプロファイルセマンティクス (ALPS) ドキュメントを公開します。ALPS メタデータを表示するには、ルートリソースによって公開されている profile
リンクをたどります。Person
リソース ( /alps/persons
) の ALPS ドキュメントに移動すると、Person
リソースに関する多くの詳細を見つけることができます。射影は、GET
REST 遷移に関する詳細とともに、次の例のようなブロックにリストされています。
{ …
"id" : "get-person", (1)
"name" : "person",
"type" : "SAFE",
"rt" : "#person-representation",
"descriptors" : [ {
"name" : "projection", (2)
"doc" : {
"value" : "The projection that shall be applied when rendering the response. Acceptable values available in nested descriptors.",
"format" : "TEXT"
},
"type" : "SEMANTIC",
"descriptors" : [ {
"name" : "noAddresses", (3)
"type" : "SEMANTIC",
"descriptors" : [ {
"name" : "firstName", (4)
"type" : "SEMANTIC"
}, {
"name" : "lastName", (4)
"type" : "SEMANTIC"
} ]
} ]
} ]
},
…
1 | ALPS ドキュメントのこのパートには、GET および Person リソースの詳細が示されています。 |
2 | このパートには、projection オプションが含まれています。 |
3 | このパートには、noAddresses 射影が含まれています。 |
4 | この射影によって提供される実際の属性には、firstName と lastName が含まれます。 |
次の場合、射影定義が取得され、クライアントが利用できるようになります。
|
隠しデータを取り込む
このセクションではこれまで、射影を使用してユーザーに表示される情報を減らすメソッドについて説明してきました。射影は、通常は見えないデータをもたらすこともあります。例: Spring Data REST は、@JsonIgnore
アノテーションでマークアップされたフィールドまたは getter を無視します。次のドメインオブジェクトを検討してください。
@Entity
public class User {
@Id @GeneratedValue
private Long id;
private String name;
@JsonIgnore private String password; (1)
private String[] roles;
…
1 | Jackson の @JsonIgnore は、password フィールドが JSON に直列化されるのを防ぐために使用されます。 |
前の例の User
クラスは、ユーザー情報の格納や Spring Security との統合に使用できます。UserRepository
を作成する場合、通常、password
フィールドはエクスポートされているはずですが、これは適切ではありません。前の例では、password
フィールドに Jackson の @JsonIgnore
を適用することにより、これが発生しないようにします。
@JsonIgnore がフィールドの対応する getter 関数上にある場合、Jackson はフィールドを JSON に直列化しません。 |
ただし、射影により、このフィールドに引き続き対応できるようになります。次の射影を作成することができます。
@Projection(name = "passwords", types = { User.class })
interface PasswordProjection {
String getPassword();
}
このような射影が作成されて使用されると、User.password
に配置された @JsonIgnore
ディレクティブが回避されます。
この例は少し不自然に見えるかもしれませんが、より豊富なドメインモデルと多くの射影を使用すると、そのような詳細が誤って漏洩する可能性があります。Spring Data REST はそのようなデータの機密性を識別できないため、そのような状況を回避するのはあなた次第です。 |
射影は仮想データを生成することもできます。次のエンティティ定義があるとします。
@Entity
public class Person {
...
private String firstName;
private String lastName;
...
}
次のように、前の例の 2 つのデータフィールドを組み合わせた射影を作成できます。
@Projection(name = "virtual", types = { Person.class })
public interface VirtualProjection {
@Value("#{target.firstName} #{target.lastName}") (1)
String getFullName();
}
1 | Spring の @Value アノテーションを使用すると、ターゲットオブジェクトを取得し、その firstName 属性と lastName 属性をつなぎ合わせて読み取り専用の fullName をレンダリングする SpEL 式をプラグインできます。 |
抜粋
抜粋は、リソースコレクションに自動的に適用される射影です。例: PersonRepository
は次のように変更できます。
@RepositoryRestResource(excerptProjection = NoAddresses.class)
interface PersonRepository extends CrudRepository<Person, Long> {}
前の例では、Person
リソースをコレクションまたは関連リソースに埋め込むときに、NoAddresses
射影を使用するよう Spring Data REST に指示しています。
抜粋の射影は、単一のリソースに自動的に適用されません。それらは意図的に適用する必要があります。抜粋射影は、コレクションデータのデフォルトのプレビューを提供することを目的としていますが、個々のリソースをフェッチする場合は提供しません。このテーマに関する議論については、Spring Data REST アイテムリソースに抜粋射影が自動的に適用されないのはなぜですか ? (英語) を参照してください。 |
次のセクションに示すように、デフォルトのレンダリングを変更することに加えて、抜粋には追加のレンダリングオプションがあります。
一般的にアクセスされるデータの抜粋
REST サービスの一般的な状況は、ドメインオブジェクトを構成するときに発生します。例: Person
は 1 つのテーブルに保存され、関連する Address
は別のテーブルに保存されます。デフォルトでは、Spring Data REST は、クライアントがナビゲートする必要がある URI として人の address
を提供します。ただし、コンシューマーがこの余分なデータを常にフェッチすることが一般的である場合、抜粋射影はこの余分なデータをインラインで配置し、余分な GET
を節約できます。これを行うには、次のように別の抜粋射影を定義できます。
@Projection(name = "inlineAddress", types = { Person.class }) (1)
interface InlineAddress {
String getFirstName();
String getLastName();
Address getAddress(); (2)
}
1 | この射影は inlineAddress と名付けられました。 |
2 | この射影は getAddress を追加し、Address フィールドを返します。射影内で使用すると、情報がインラインで含まれます。 |
次のように、PersonRepository
定義にプラグインできます。
@RepositoryRestResource(excerptProjection = InlineAddress.class)
interface PersonRepository extends CrudRepository<Person, Long> {}
これを行うと、HAL ドキュメントが次のように表示されます。
{
"firstName" : "Frodo",
"lastName" : "Baggins",
"address" : { (1)
"street": "Bag End",
"state": "The Shire",
"country": "Middle Earth"
},
"_links" : {
"self" : {
"href" : "http://localhost:8080/persons/1"
},
"address" : { (2)
"href" : "http://localhost:8080/persons/1/address"
}
}
}
1 | address データはインラインで直接含まれているため、ナビゲートして取得する必要はありません。 |
2 | Address リソースへのリンクは引き続き提供されているため、独自のリソースに移動することもできます。 |
前の例は、この章で前述した例を組み合わせたものであることに注意してください。最終的な例への進行をたどるために、読み返してみることをお勧めします。
リポジトリ用に @RepositoryRestResource(excerptProjection=…) を構成すると、デフォルトの動作が変更されます。すでにリリースを行っている場合、これにより、サービスのコンシューマーに重大な変更が生じる可能性があります。 |