射影と抜粋

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 インターフェースであり、宣言型になっています。
3firstName をエクスポートします。
4lastName をエクスポートします。

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} という新しいオプションがあります。
2self 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"
      } ]
    } ]
  } ]
},
…
1ALPS ドキュメントのこのパートには、GET および Person リソースの詳細が示されています。
2 このパートには、projection オプションが含まれています。
3 このパートには、noAddresses 射影が含まれています。
4 この射影によって提供される実際の属性には、firstName と lastName が含まれます。

次の場合、射影定義が取得され、クライアントが利用できるようになります。

  • @Projection アノテーションでフラグが付けられ、ドメイン型の同じパッケージ(またはサブパッケージ)にある、または

  • RepositoryRestConfiguration.getProjectionConfiguration().addProjection(…) を使用して手動で登録します。

隠しデータを取り込む

このセクションではこれまで、射影を使用してユーザーに表示される情報を減らすメソッドについて説明してきました。射影は、通常は見えないデータをもたらすこともあります。例: 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;
  …
1Jackson の @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();

}
1Spring の @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"
    }
  }
}
1address データはインラインで直接含まれているため、ナビゲートして取得する必要はありません。
2Address リソースへのリンクは引き続き提供されているため、独自のリソースに移動することもできます。

前の例は、この章で前述した例を組み合わせたものであることに注意してください。最終的な例への進行をたどるために、読み返してみることをお勧めします。

リポジトリ用に @RepositoryRestResource(excerptProjection=…​) を構成すると、デフォルトの動作が変更されます。すでにリリースを行っている場合、これにより、サービスのコンシューマーに重大な変更が生じる可能性があります。