当サイトを最適な状態で閲覧していただくにはブラウザのJavaScriptを有効にしてご利用下さい。
JavaScriptを無効のままご覧いただいた場合には一部機能がご利用頂けない場合や正しい情報を取得できない場合がございます。
承知しました
本サイトではWebサイトのエクスペリエンスを向上させるために、Cookieを使用しています。Cookieはブラウザの設定から無効にできます。本サイトで使用するCookieについては、プライバシーポリシーをご確認ください。

Blog

ブログ

開発者向け

Webhookを使用した廃止されたキャッシュエントリのクリア

By Jan Lenoch  

Kentico Cloudコンテンツをアプリのキャッシュに保存していますか?最新のエントリだけで、キャッシュをすっきりさせたいと思ったことはありませんか?このハウツー記事では、KenticoCloudからのWebhook呼び出し時に廃止されたキャッシュエントリをクリアする方法を示します。

ステップバイステップの方法で、Kentico CloudでWebhookを構成し、Webhook呼び出しをリッスンしてキャッシュエントリを適切にクリアする単純なASP.NET CoreMVCアプリを作成します。

一般に、KenticoCloudを利用するアプリは多くのキャッシュを必要としません。 Kentico Cloud Delivery / Preview APIサービスは、ミリ秒単位で測定される応答待ち時間で、世界中で運用されているCDNネットワークを介してコンテンツを公開します。したがって、Webアプリは必ずしもコンテンツをキャッシュする必要はなく、KenticoCloudを直接参照できます。

ただし、キャッシュを実装する理由がある場合があります。KenticoCloudに対して行われたAPIリクエストを節約したい場合があります。無料プランには月額50,000のAPIリクエストが含まれ、有料プランにはさらに多くのプリペイドリクエストが含まれるため、コンテンツをキャッシュする必要はありませんが、キャッシュしたい場合は、次のように設定します。キャッシュを最新の状態に保つためのwebhook。

問題

つまり、KenticoCloudコンテンツアイテムを一定期間キャッシュするアプリがあります。しかし、キャッシュエントリの有効期限が切れる前に、コンテンツ寄稿者がKentico Cloudのコンテンツアイテムを変更するとどうなりますか?アプリは、コンテンツアイテムの廃止バージョンを提供します。これが起こりたくないことであると想定して、Webhook機能を設計しました。キャッシュを無効にするだけではありません。 webhookは、アプリがKenticoCloudで発生するイベントのシグナルを取得したい他のさまざまなケースに対応します。例については、KenticoテクニカルエバンジェリストのBryanSoltisによるWebhookを使用したAzureSearchインデックスの更新に関する記事を参照してください。

ソリューション

Webhookは、KenticoCloudがアプリの事前構成されたパブリックにルーティング可能なURLアドレスに送信するHTTPPOSTリクエストに他なりません。 Webhookリクエストのコンテンツ(つまりペイロード)には、何が起こったかに関する詳細情報が常に含まれています。このようにして、アプリは適切に反応できます。たとえば、廃止されたキャッシュアイテムをクリアします。

Kenticoクラウドをセットアップする

上で宣伝したように、私が最初にセットアップした場所はKenticoCloudでした。ここでは、メインメニューに移動して「Webhooks」を選択しました。このメニュー項目には、「開発者」および「プロジェクトマネージャー」の役割からアクセスできます。

その後、手順は非常に簡単でした。

[新しいWebhookを作成]をクリックすると、簡単なダイアログが表示されました。

Kentico Kontent


名前として「CacheWebhook」と入力し、将来のアプリのWebhookエンドポイントのパブリックURL(「http://example.com/webhook」)を入力しました。次に、生成されたシークレットを書き留めたら、アプリを作成する準備が整いました。

サンプルアプリを作成する

この部分が複雑になると予想した場合は、今すぐがっかりさせます。かなり簡単です。

以前と同様に、「DotnetNew」コマンドを使用してボイラープレートコードテンプレートをデプロイしました。

 dotnet new kentico-cloud-mvc --name 'WebhookCacheInvalidationMvc' --output 'WebhookCacheInvalidationMvc'


最高レベルの観点から、次の手順でロジックを実装しました。

  1. 私はと呼ばれるクラス作成のCacheManagerの状態を管理する責任あるMemoryCacheを
  2. 既存のCachedDeliveryClientクラスのコードを少し変更して、新しく作成されたキャッシュアイテムとその依存関係の識別子(コードネーム)を準備するだけにしました。次に、CacheManagerクラスを呼び出してキャッシュを操作します。
  3. 私はと呼ばれる新しいMVCコントローラ添加WebhookController上記ウェブフックエンドポイントを表すようにします。コントローラはCacheManagerを呼び出して、キャッシュエントリを無効にします。

CacheManagerクラス

MemoryCacheオブジェクトへの参照を保持する以外に、このシングルトンクラスには、GetOrCreateAsync とInvalidateEntryの2つの重要なメソッドがあります。

最初のメソッドは、CachedDeliveryClientのGetItem(s)Asyncメソッドによって呼び出されます。このメソッドは、「identifierTokens」、「valueFactory」、および「dependencyListFactory」を入力パラメーターとして受け入れます。

'valueFactory'デリゲートは、キャッシュエントリの有効期限が切れた場合に、(Delivery / Preview APIエンドポイントを呼び出すことによって)新しいキャッシュエントリを構築するために使用されます。 'dependencyListFactory'は、そのエントリを読み取り、現在のエントリが依存するエントリの識別子のコレクションを返す責任があります。 CachedDeliveryClientには、「valueFactory」パラメーターと「dependencyListFactory」パラメーターの両方に固有のメソッドがあります。

 public async Task GetOrCreateAsync(IEnumerable identifierTokens, Func> valueFactory, Func> dependencyListFactory){ // Check existence of the cache entry. if (!_memoryCache.TryGetValue(StringHelpers.Join(identifierTokens), out T entry)) { // If it doesn't exist, get it via valueFactory. T response = await valueFactory(); // Create it. (Could be off-loaded to a background thread.) CreateEntry(identifierTokens, response, dependencyListFactory); return response; } return entry;}


CreateEntry メソッドは、「dependencyListFactory」を呼び出し、識別子を使用して、すべての依存関係のダミーキャッシュアイテムを作成します。各ダミーエントリは、 キャンセルトークンをサブスクライブする他のエントリに対応するキャッシュエントリの無効化をアドバタイズするCancellationTokenSourceオブジェクトを保持します。これは、.NETCoreで依存関係が処理される方法です。

最後に、InvalidateEntryメソッドは、特定のキャッシュエントリと、それに依存するすべてのエントリを無効化(クリア)します。 InvalidateEntryメソッドは、WebhookControllerクラスによって呼び出されます。

 public void InvalidateEntry(IdentifierSet identifiers){ var typeIdentifiers = new List(); // Aggregate several types that appear in webhooks into one. if (identifiers.Type.Equals(CacheHelper.CONTENT_ITEM_TYPE_CODENAME, StringComparison.Ordinal) || identifiers.Type.Equals(CacheHelper.CONTENT_ITEM_VARIANT_TYPE_CODENAME, StringComparison.Ordinal)) { typeIdentifiers.AddRange(new[] { CacheHelper.CONTENT_ITEM_TYPE_CODENAME, string.Join(string.Empty, CacheHelper.CONTENT_ITEM_TYPE_CODENAME, '_variant'), string.Join(string.Empty, CacheHelper.CONTENT_ITEM_TYPE_CODENAME, '_typed'), string.Join(string.Empty, CacheHelper.CONTENT_ITEM_TYPE_CODENAME, '_runtime_typed') }); } else if (identifiers.Type.Equals(CacheHelper.CONTENT_ITEM_LISTING_IDENTIFIER, StringComparison.Ordinal)) { typeIdentifiers.AddRange(new[] { string.Join(string.Empty, CacheHelper.CONTENT_ITEM_LISTING_IDENTIFIER, '_typed'), string.Join(string.Empty, CacheHelper.CONTENT_ITEM_LISTING_IDENTIFIER, '_runtime_typed') }); } else { typeIdentifiers.Add(identifiers.Type); } foreach (var typeIdentifier in typeIdentifiers) { if (_memoryCache.TryGetValue(StringHelpers.Join('dummy', typeIdentifier, identifiers.Codename), out CancellationTokenSource dummyEntry)) { // Mark all subscribers to the CancellationTokenSource as invalid. dummyEntry.Cancel(); } }}

CachedDeliveryClientクラス

GetItem(s)Asyncメソッドは、CacheManagerを呼び出すだけです。たとえば、以下は、GetItemAsyncメソッドの強く型付けされたオーバーロードのコードです。

 public async Task> GetItemAsync(string codename, IEnumerable parameters){ var identifierTokens = new List { string.Join(string.Empty, CacheHelper.CONTENT_ITEM_TYPE_CODENAME, '_typed'), codename }; AddIdentifiersFromParameters(parameters, identifierTokens); return await _cacheManager.GetOrCreateAsync(identifierTokens, () => _deliveryClient.GetItemAsync(codename, parameters), GetDependencies);}


'dependencyListFactory'インスタンスは次のようになります(GetDependencies メソッド):

public static IEnumerable GetDependencies(T response){ var dependencies = new List(); // Both single-item and listing responses depend on their modular content items. Create dummy items for all modular content items. AddModularContentDependencies(response, dependencies); // Single-item responses if (response is DeliveryItemResponse || (response.GetType().IsConstructedGenericType && response.GetType().GetGenericTypeDefinition() == typeof(DeliveryItemResponse<>))) { // Create dummy item for the content item itself. var ownDependency = new IdentifierSet { Type = CacheHelper.CONTENT_ITEM_TYPE_CODENAME, Codename = GetContentItemCodenameFromResponse(response) }; if (!dependencies.Contains(ownDependency)) { dependencies.Add(ownDependency); } } // Listing responses else if (response is DeliveryItemListingResponse || (response.GetType().IsConstructedGenericType && response.GetType().GetGenericTypeDefinition() == typeof(DeliveryItemListingResponse<>))) { // Create dummy item for each content item in the listing. foreach (var codename in GetContentItemCodenamesFromListingResponse(response)) { var dependency = new IdentifierSet { Type = CacheHelper.CONTENT_ITEM_TYPE_CODENAME, Codename = codename }; if (!dependencies.Contains(dependency)) { dependencies.Add(dependency); } } } return dependencies;}


AddModularContentDependenciesメソッドおよびその他のバックエンドメソッドは、ModularContentプロパティからコードネームを抽出するために動的型に依存するだけです。

WebhookControllerクラス

コントローラの仕事は単純に次のとおりです。

•署名を確認します(KenticoCloudSignatureActionFilterを介して)
•KenticoCloudアーティファクトのタイプとそのコードネームを確認します
•必要に応じて、InvalidateEntryメソッドを呼び出します

[ServiceFilter(typeof(KenticoCloudSignatureActionFilter))]public IActionResult Index([FromBody] KenticoCloudWebhookModel model){ switch (model.Message.Type) { case CacheHelper.CONTENT_ITEM_TYPE_CODENAME: case CacheHelper.CONTENT_ITEM_VARIANT_TYPE_CODENAME: switch (model.Message.Operation) { case 'archive': case 'publish': case 'unpublish': case 'upsert': foreach (var item in model.Data.Items) { _cacheManager.InvalidateEntry(new IdentifierSet { Type = model.Message.Type, Codename = item.Codename }); } break; default: return Ok(); } return Ok(); default: return Ok(); }}

テスト時間

テストすることは基本的に2つありました。

  • キャッシュにすべての依存関係が設定されるかどうか
  • WebhookControllerの呼び出しがキャッシュエントリを無効にする場合

すべてのModularContentアイテムのダミーエントリを含め、キャッシュに正しく入力されました。リストの応答の場合、リスト内のすべてのコンテンツアイテムは、対応するダミーエントリを生成しました。したがって、モジュラーコンテンツまたはリストのいずれかのアイテムが廃止された場合、リスト全体が適切に無効化されます。

Kentico Kontent


WebhookControllerの呼び出し時に、キャッシュされたアイテムとリスト(つまり、キャッシュエントリ)は最初に無効とマークされ、次にパージされました。 MemoryCacheのGetメソッドは、古いキャッシュエントリではなく「null」を正しく返しました。その結果、アプリは新しいコンテンツを取得しました。依存関係の1つが無効になったときに、キャッシュエントリも削除されました。

Kentico Kontent


すべてが設定され、完了しました。

コードを取得する

もちろん、サンプルアプリのコードは、GitHubの一般的な記事のサンプルリポジトリから取得できます。また、機能をボイラープレートテンプレートにマージすることも計画しています。

前進する

改善できることがいくつかあります。たとえば、Kentico Cloudで基になるコンテンツタイプが変更されると、キャッシュ内のコンテンツアイテムが無効になる可能性があります。それを見たい場合は、以下のコメントまたはフォーラムでお知らせください。

Headless CMSの導入をお考えでしょうか?

クラウドとマルチデバイスに最適化されたKentico Kontentをお試しください