Asset Publisher

null 【後編】開発者の視点から見たLiferay 7.2の検索機能

技術者向け7.2検索

【後編】開発者の視点から見たLiferay 7.2の検索機能

Liferay 7.2で強化された検索機能について、後編となる今回は、具体的な実装方法と、拡張ポイントに関して見ていきましょう。

はじめに:テクニカル記事ごとに必要な知識レベルについて


本記事は以下の知識レベルの方たち、ユースケースが対象です。


■ 本記事対象の知識レベルとユースケース

  • 中級レベル
  • 一般的なLiferay DXP開発に必須

前回の前編では検索機能の概略と、実際のユースケースが公式ドキュメントのどこを見れば参考になるかの解説でした。今回は実際の実装方法について詳しく見て行ってみましょう。

インデックス作成と検索のカスタマイズ

次に、各拡張ポイントについて説明し、カスタムサービスを作成した場合の利用方法を説明します

インデックス作成と検索のカスタマイズ


※本記事はLiferay Community Blogに投稿されている”Understanding Search from a Developers Perspective”を翻訳したものです。配信元または著者の許可を得て配信しています。
※オリジナルの英記事著者:David H Nebinger サウスカロライナ州のサマーヴィルに住むLiferay, Inc.のSoftware Architect/リードコンサルタント。Liferay Communityサイトにて、多くの技術情報を投稿するなど精力的に活動しています。

各拡張ポイントについて説明し、カスタムサービスを作成した場合の利用方法を説明しますす。

 

モデルエンティティフィールドをインデックスに提供する


Liferayドキュメントはこちら

検索に含まれる、または検索から除外されるフィールドを制御できることは知っていますが、ここでは理論にまずは注目してみましょう。

カスタムのService Builderエンティティ(または実際に検索するエンティティ)がある場合、エンティティのドキュメントにフィールドを提供する必要があります。

ModelDocumentContributorは、インデックスを作成する検索ドキュメントのフィールドに、エンティティの列をマップするクラスです。

エンティティの列は手動でインデックスにマッピングする必要があります。インデックス付けが必要なエンティティには全て、このクラスの実装が必須です。

エンティティのドキュメントにフィールドを追加するときは、次のことに注意してください。

  • エンティティのすべてのフィールドをドキュメントとしてフィールドとして持つ必要はありません。キーワード検索で一致するものだけが必要です。ドキュメントには常に主キー値が含まれているため、エンティティが検索結果にひっかかった(ヒットした)場合、いつでも対応するエンティティを取得できます。
  • 代表的なフィールド名(Asset関連、Group IDなど)には、com.liferay.portal.kernel.search.Fieldクラスの定数値を使用してみてください。エンティティに名前がある場合は、Field.NAMEを使用します。エンティティに説明がある場合は、Field.DESCRIPTIONを使用します。定数を使用すると、Liferayが検索クエリに含める方法をすでに知っているドキュメント内のフィールドが再利用されるため、カスタマイズの手間が軽減されます。
  • そうでないものに定数値を使用しないでください。たとえば、エンティティに化学名の配列がある場合、それらを連結してField.CAPTIONタイプとして保存しないでください。これらは単にキャプションではないためです。独自のフィールド名を考え出しても構いません。
  • addText()とaddKeyword()を理解しましょう。これらのメソッドは両方ともオーバーロードされており、多くの異なるタイプのテキストまたはキーワードを追加できますが、検索インデックスはそれぞれ異なる方法で処理します。
  • addDate()、addNumber()、addGeolocation()など、さまざまなデータ型用のその他の多数の追加メソッドがあります。すべてのデータを文字列に強制しないでください。検索結果が意味をなさなくなる可能性があるためです。たとえば、「19」を検索して、19xx年と2019年のすべてのレコードを検索結果として期待するようなケースはほとんど無いかと思います。
  • フィールドは、エンティティデータの正確なコピーである必要はありません。多くの場合、エンティティ値ではなく、正規化されたデータを表す方が優れています。たとえば、エンティティにclientIdがあり、別のクライアントレコードを指す場合があります。インデックスの場合、Fieldが実際のクライアント名を利用した方が、clientIdだけを利用するより、良い検索結果が得られるはずです。
  • エンティティの一部ではないデータのフィールドを作成できるため、実際は5つのフィールドしか持たないエンティティでも、要件に合わせてより詳細な検索ができるように、20個のフィールドを持つドキュメントで表すことも可能です。
  • フィルタリングおよび/またはソートのサポートを追加する場合、フィルタリングまたはソートするフィールドをドキュメントのフィールドとして追加する必要があります。たとえば、検索にはデータベースの追加検索からではなく、インデックスからのヒットのみが含まれるため、エンティティのフィールドで並べ替えることはできません。
  • ローカライズ可能なテキストを処理する場合、すべての言語フィールドにインデックスしてください。 Liferayのドキュメントは、特別に細工されたフィールド名を使用して、ローカライズされたフィールドを追加する方法を示しています。特定のロケールのフィールドに対してのみ検索が実行されるので、該当の言語で正しい結果が返され、別の言語における、偶然の語彙の一致で生じる可能性がある、誤検知を除外できます。
  • 後でModelSummaryContributor実装で使用されるすべてのフィールドを必ず含めてください。サマリーを作成する場合、エンティティを直接取得してサマリーに含める追加情報を取得する必要はありません。

テキストとキーワード

フィールドをテキストとして追加するか、キーワードとして追加するか迷うこともあるかと思います。大きな違いは、全文が含まれるかどうか、またはキーワードのみが含まれるかどうかです。

テキストとして保存する場合、「The quick brown fox jumps over the lazy dog」などのフレーズがそのままフィールドに保存されます。これは、「quick brown fox」や「lazy dog」などのフレーズを検索する場合に便利です。全文はそのままなので、一致するフレーズを含むレコードのみがヒットになります。

キーワードを保存すると、一般的な単語と重複する単語がテキストから削除されます。

一般的な単語、またはインデックス作成ではストップワードと呼ばれますが、言語では一般的ですが、インデックスの観点からは価値がありません。これには、「the、 this、a、that、those、he、him、her」などの単語が含まれます。上記のフレーズから、ストップワードを削除すると、「quick brown fox jumps over lazy dog」というインデックスが作成されます。

さらに重複した単語も削除されますが、出現回数は残ります。たとえば、このブログでは、これまでにFieldという単語を少なくとも50回使用したことがあるはずです。キーワードストレージでは、フィールドが含まれ、カウントには50が含まれますが、テキスト内のすべての形式の文構造または配置は失われます。

もちろん、キーワードベースのフィールドの実際のストレージは、SolrでもElasticsearchでも、検索アプライアンス次第です。ここで説明されている方法とは異なる方法で処理する場合もありますが、コーディングの観点からは、これがどのように行われるかを想像する方が簡単です。

キーワードとして保存すると、ストレージサイズを削減でき、キーワードマッチングには適していますが、フレーズはこの保存方法では保持されないため、「キーレスエントリ」などのフレーズ検索には役立ちません。

出現回数は保持されるため、「キーレス」を検索すると、単語を使用したが散発的に他のドキュメントと比べて、出現回数が多いヒットをより高くランク付けできます。キーワードの保存は、キーワード検索の生のテキストの保存よりも高速になる傾向があります。

テキストを2つのフィールドとして保存することもできます。1つはテキストを使用し、もう1つはキーワードを使用します。前者はフレーズ検索を優先し、他のキーワード検索を優先します。

再インデックス付けとバッチインデックス付けの動作を構成する


公式ドキュメントはこちら

管理者がコントロールパネルの[インデックスの再作成]オプションを使用すると、バッチのインデックス再作成プロセスが開始されます。特定のエンティティについて、これは通常、テーブル内の各レコードが取得され、登録されたModelDocumentContributorを使用してドキュメントが読み込まれ、ドキュメントがインデックスサービス(通常はElasticsearch)に送信されて格納されることを意味します。

このプロセスをカスタマイズする必要がある場面は何か、と思うかもしれません。ほとんどの場合、すべてのレコードにインデックスを付ける必要がありますが、インデックスから自動的に除外されるレコードがあることも一般的です。

たとえば、JournalArticlesは明らかにインデックス付けされますが、APPROVEDまたはIN_TRASHのワークフローステータスにある記事のみがインデックス化されます。 PENDING、DENIEDなどはインデックス作成から除外されます。独自の観点によっては、IN_TRASHエンティティのインデックス付けは意味がないと思われる場合があるため、それらを除外することもできます。一部のユーザーについては、インデックスにPENDINGを含めると、検索結果に保留中の記事が表示され、承認された後の一般的な検索でのランク付け方法を確認できます。

要件によって、どのドキュメントをインデックスに登録し、どのドキュメントを除外するかをみなさんが管理することになるでしょう。

その場合はModelDocumentContributorの中で記事のインデックス作成を除外するのではなく、ModelIndexerWriterContributorを利用してレコードを最初の処理から除外します。

これは、ネットワークとデータベースの帯域幅(インデックスを作成しないレコードを取得しないこと)と処理時間(インデックスを作成する必要のないレコードのドキュメントを作成しようとする無駄な時間)を節約する、効率的な方法です。

インデックスを作成するすべてのエンティティには、このクラスのインスタンスが必要です。

少なくとも、Liferayドキュメントのほとんどのコードはそのまま使用できます。唯一の変更点は、customize()メソッドに対するものです。customize()メソッドの最小限の実装は次のようになります。


@Override
public void customize(
  BatchIndexingActionable batchIndexingActionable,
  ModelIndexerWriterDocumentHelper modelIndexerWriterDocumentHelper) {

  batchIndexingActionable.setPerformActionMethod(
    (FooEntry fooEntry) -> {
      Document document =
        modelIndexerWriterDocumentHelper.getDocument(fooEntry);

        batchIndexingActionable.addDocuments(document);
  });
}

このバージョンでは、テーブルのレコードはフィルタリングされず、すべての行のインデックスが再作成されます。

エンティティ/行をインデックスから除外する方法を学ぶために、Liferayのドキュメントには、customize()メソッドの記述方法を示すサンプルコードが記載されていますが、実装方法の決定に役立つ、追加の情報を以下に示します。

  • BatchIndexingActionableは、DynamicQueryのラッパーです。 DynamicQueryでできることはすべて、BatchIndexingActionableインスタンスに追加できます。
  • インデックスしたくないレコードを除外するのが目的です。ワークフローステータスまたは独自のステータスコードによってレコードが除外される場合もあります。システムから実際にはレコードを削除せず、除外してそれらの検索ヒットを防ぐことができます。
  • サンプルコードのbatchIndexingActionable.setPerformActionMethod()のコンテンツは、独自のエンティティクラスを変更するのに99%の時間を費やす部分です。
  • getIndexerWriterMode()メソッドは、通常IndexerWriterMode.UPDATEを返します。他のオプションは、削除する必要があるレコードを「クリーンアップ」するために使用されます。

モデルエンティティの用語をクエリに追加する


公式ドキュメントはこちら

これは、ModelDocumentContributorクラスの兄弟クラスです。このクラスでは、ドキュメントインスタンスで定義したフィールドを、検索コンテキストクエリヘルパーに追加して、フィールドでのキーワード検索を容易にします。

すべてのエンティティがKeywordQueryContributorの実装を必要とするわけではありません。エンティティのModelDocumentContributorによって追加された検索クエリに、フィールドを追加する必要がある場合にのみ利用します。

つまり、すでに検索が開始されている時に、キーワード検索のためにフィールドを追加する必要があるような場合です。ModelDocumentContributorで行ったすべてのフィールドを追加する必要はありませんが、キーワード検索に含めるフィールドの場合は、ここに追加してください。

Liferayのサンプルコードでは、FooEntryModelDocumentContributorが2つの日付フィールド、単純なテキストサブタイトルフィールド、およびコンテンツとタイトルのローカライズされたフィールドを追加しました。

対応するFooEntryKeywordQueryContributorでは、サブタイトル、タイトル、およびコンテンツフィールドのみがクエリに追加されました。 2つの日付フィールドは、実際にはキーワード検索の対象ではないためではありません。

同様に、独自のModelDocumentContributorに追加する他のフィールドがあり、KeywordQueryContributorに含める場合と、含めない場合があります。含めるものは検索されますが、除外するものは検索されないことに注意してください。

事前フィルタリング


公式ドキュメントはこちら

事前フィルタリングは、返された検索結果からヒットを除外する方法です。このようなことをする必要はないかもしれませんが、オプションはあります。

保留中のJournalArticlesがインデックスに登録され、コンテンツ承認者が検索結果に保留中の記事を表示することが提案された、前述の例のような状況では、保留中の記事を他のユーザーの閲覧は防ぎたい、という要件も同時にあるかと思います。

そういった場合、カスタムのModelPreFilterContributor実装により、ロール固有のフィルターを追加して、PENDING記事を通常のユーザーから除外し、コンテンツ承認者にのみ含めることができます。

すべてのエンティティがModelPreFilterContributorの実装を必要とするわけではありませんが、検索結果エンティティの一部のインスタンスを含めるべきでない場合にのみ、この方法が利用できます。

結果サマリーの作成


公式ドキュメントはこちら

すべてのヒット(検索結果)は、Liferayの検索結果ポートレットに表示されます。 ModelSummaryContributorを使用して、検索結果に表示されるサマリーコンテンツを制御できます。

Liferayサンプルは、一致したエンティティの概要を設定することを示しています。コンテンツフィールドとタイトルフィールドの両方がローカライズされていることに注意してください。提供される実装は、ローカライズされたフィールドに使用されるフィールドの命名を公開しますが、最終的にはローカライズされたタイトルとコンテンツがドキュメントから抽出され、Summaryクラスの作成に使用されます。

ローカライズされたフィールドを使用していない場合、概要の作成は提供されているサンプルよりも簡単になります。

Liferayの例では強調表示されていませんが、Summaryインスタンスを作成するときにDocument Fieldsを使用するようにしてください。サマリー用の情報を取得するのに、DBクエリを実行する必要がある場合、パフォーマンスが低下することになります。 DBクエリを回避するために、サマリーで必要または必要なすべての値をドキュメントのフィールドとして追加することをお勧めします。

Hit(検索結果)として返される可能性のあるすべてのエンティティは、ModelSummaryContributorクラスを実装する必要があります。

モデルエンティティの可視性の制御


公式ドキュメントはこちら

これは、めったに使用されない拡張ポイントになります。 1つのエンティティが関連アセットを持つことができる場合、ModelVisibilityContributorは、エンティティを関連アセットとして選択できるかどうかを決定します。

たとえば、Webコンテンツには、関連アセットとしてDLDocumentを含めることができます。 Webコンテンツを作成する際、関連アセットとして追加可能なドキュメントを検索できるようすることが可能です。逆に、ModelVisibilityContributorを使用して、エンティティを選択できないようにすることもできます。

ドキュメントのサンプルLiferay実装では、適切なワークフローステータスにないFooEntryインスタンスをマスクします。

すべてのエンティティがModelVisibilityControllerのインスタンスを必要とするわけではありません。別のアセットに関連付けられ、インスタンスが使用可能かどうかを制御したい場合のみ、これらのクラスのいずれかを実装します。

検索サービス登録


公式ドキュメントはこちら

カスタムインデックス/検索実装の最後の部分は、検索サービスレジストラです。

前述のすべてのクラスは、インデックス作成と検索をサポートするLiferayインターフェイスの実装で、すべてOSGiコンポーネントとして登録されていますが、Liferay検索レジストリには登録されません。

Liferayの全てのインデックス作成、または検索を処理は、検索レジストリを介して行われ、SearchRegistrarが全ての処理の最後を締めくくります。

Liferayの実装では、通常の@Componentと@Activateメソッドを使用して、検索登録プロセスをトリガーします。これは、@ Referencedサービスが接続されるとすぐに呼び出されます。

ドキュメントの一部として作成したすべてのインデックスクラスと検索クラスの@Reference依存関係を追加する必要があります。

サンプルコードでは、レジストレーションで以下のmodelSearchDefinitionを設定します。

  • デフォルトで選択されているフィールド名は、選択されているフィールドのデフォルトのリストです。表示されるリストはほとんどが標準ですが、MODIFIED_DATEが含まれています(FooEntityModelDocumentContributorはこのField値を設定します)。
  • デフォルトで選択されているローカライズされたフィールド: FooEntityはTITLEとCONTENTをローカライズしているため、デフォルトの選択フィールドとして任意で追加しています(全く設定しない、いずれかのみの設定も可能です)。
  • FooEntityのModelIndexWriteContributorを設定します。
  • FooEntityのModelSummaryContributorを設定します。
  • FooEntityのModelVisibilityContributorを設定します(全く設定しない、いずれかのみの設定も可能です)。

さらに、モジュールがアンロードされる際に、検索レジストリからすべてを登録解除する@Deactivateメソッドがあります。

インデックス作成/検索されているすべてのエンティティは、同じ方法でクラスを登録します。

各エンティティには独自のSearchRegistrar実装を実装することをお勧めしますが、必須ではありません。1つのSearchRegistrarですべてのエンティティクラスを登録することもできますが、単一の@Referencedコンポーネントだと、エンティティ検索クラスの登録がブロックされて遅くなる可能性が非常に高いです。そのため、依存関係を解決する際に必要なエンティティのみをブロックするよう、各エンティティで個別のレジストラを用意することをお勧めします。

結論


新しいブログプロジェクトの実装のために、上記の情報すべてが必要だったので、ここにその過程を公開することにしました。大きなプロジェクトで、検索の実装をする時がきたら、どういう判断のもとに実装したか、という過程がよく理解できるかと思います。

同時に、エンティティインデックス/検索の要件にとう取り組むか、というヒントになれば幸いです。


いかがでしたかでしょうか?
検索の紹介は以上以上です。記事に関するご意見、ご感想などございましたら、お気軽に[email protected]までご連絡ください。


■ Liferay Japan User Groupスラックを開設しました。製品に関する質問、設計に関する疑問などでもこちらにポストしてください!
https://liferay-community.slack.com/messages/C7P2GV5RA

■ これまで発信してきた日本語による技術コンテンツを、Qiitaでお読みいただけます。よろしければそちらの記事も合わせてご役立てください。
https://qiita.com/yasuflatland-lf

■ Twitterでも公式プレスリリースやなどの情報を発信しています。
https://twitter.com/LiferayJapan

Doorkeeperのライフレイコミュニティメンバー大歓迎!
コーポレートブログの技術コンテンツ更新情報など定期的におとどけしています。
https://liferay.doorkeeper.jp/

Upgrade - jp banner - gartner

業界最高の評価

Gartner Magic Quadrant Digital Experience Platforms 2019

Gartnerレポート:『リーダー』に位置づけ

ライフレイは9年連続で、Gartner社によるデジタルエクスペリエンスプラットフォーム(DXP )分野のマジック・クアドラントにおいて、リーダーに選出されました。

ガートナーは、ガートナー・リサーチの発行物に掲載された特定のベンダー、製品またはサービスを推奨するものではありません。また、最高のレーティング又はその他の評価を得たベンダーのみを選択するようにテクノロジーユーザーに助言するものではありません。ガートナー・リサーチの発行物は、ガートナー・リサーチの見解を表したものであり、事実を表現したものではありません。ガートナーは、明示または黙示を問わず、本リサーチの商品性や特定目的への適合性を含め、一切の責任を負うものではありません。

レポートの詳細

Upgrade - jp common footer contact or demo