リモート データへのアクセス
Note
この電子ブックは 2017 年の春に発行され、それ以来更新されていません。 貴重なままの本に多くがありますが、資料の一部は時代遅れです。
現代の多くの Web ベース ソリューションでは、Web サーバーによりホストされる Web サービスを使用することで、リモート クライアント アプリケーションの機能を提供します。 Web サービスにより公開される操作によって Web API が構成されています。
クライアント アプリでは、API によって公開されるデータや操作がどのように実装されるかがわかっていなくても、Web API を利用できる必要があります。 そのためには、API が共通基準を順守し、クライアント アプリおよび Web サービスが、使用するデータ形式や、クライアント アプリと Web サービスの間で交換されるデータの構造に同意できるようにする必要があります。
Representational State Transfer の概要
Representational State Transfer (REST) は、ハイパーメディアに基づき分散システムを構築するためのアーキテクチャ スタイルです。 REST モデルの主な利点は、オープン スタンダードに基づいており、それにアクセスするモデルやクライアント アプリの実装を、特定の実装に結びつけないという点です。 そのため、MICROSOFT ASP.NET Core MVC を使用して REST Web サービスを実装でき、クライアント アプリは、HTTP 要求を生成して HTTP 応答を解析できる任意の言語とツールセットを使用して開発できます。
REST モデルでは、リソースと呼ばれる、ネットワーク経由のオブジェクトおよびサービスを表すために、ナビゲーション スキームを使用します。 通常、REST を実装するシステムでは、これらのリソースにアクセスするための要求を送信するのに HTTP プロトコルを使用します。 そのようなシステムでは、クライアント アプリにより、リソースを識別する URI、およびそのリソースで実行する操作を示す HTTP メソッド (GET、POST、PUT、DELETE など) の形式で要求が送信されます。 HTTP 要求の本文には、操作を実行するのに必要なデータが含まれています。
注意
REST ではステートレス要求モデルを定義します。 したがって、HTTP 要求は独立している必要があり、任意の順序で発生する可能性があります。
REST 要求からの応答では標準 HTTP 状態コードが使用されます。 たとえば、有効なデータを返す要求には HTTP 応答コード 200 (OK) を含める必要があり、指定したリソースの確認および削除に失敗した要求は HTTP 状態コード 404 (Not Found) を含む応答を返す必要があります。
RESTful Web API では一連の接続されたリソースを公開し、アプリでこれらのリソースを操作してその間を簡単に移動できるようにする主要な操作が提供されます。 このため、一般的な RESTful Web API を構成する URI は、公開するデータ指向であり、このデータを操作するために HTTP によって提供される機能を使用します。
HTTP 要求でクライアント アプリによって含められるデータや、Web サーバーからの対応する応答メッセージは、さまざまな形式 (メディアの種類ともいう) で表示できます。 クライアント アプリは、メッセージの本文でデータを返す要求を送信するときに、要求のヘッダーで処理できるメディアの種類を Accept
指定できます。 Web サーバーがこのメディアの種類をサポートしている場合は、メッセージの本文内のデータの形式を Content-Type
指定するヘッダーを含む応答で応答できます。 応答メッセージを解析し、メッセージ本文内の結果を適切に解釈することはクライアント アプリの役割です。
REST の詳細については、「 API の設計と API の 実装」を参照してください。
RESTful API の使用
eShopOnContainers モバイル アプリは Model-View-ViewModel (MVVM) パターンを使用し、パターンのモデル要素はアプリで使用されるドメイン エンティティを表します。 eShopOnContainers 参照アプリケーションのコントローラーおよびリポジトリ クラスでは、これらのモデル オブジェクトの多くを受け入れて返します。 そのため、モバイル アプリとコンテナー化されたマイクロサービスの間で渡されるすべてのデータを保持するデータ転送オブジェクト (DTO) として使用されます。 DTO を使用して Web サービスとの間でデータを送受信する主な利点は、1 回のリモート呼び出しでより多くのデータを送信することで、アプリで行う必要があるリモート呼び出しの数を減らせることです。
Web 要求を作成する
eShopOnContainers モバイル アプリでは、 クラスを HttpClient
使用して HTTP 経由で要求を行い、JSON がメディアの種類として使用されます。 このクラスでは、HTTP 要求を非同期に送信し、URI で識別されたリソースから HTTP 応答を受信するための機能が提供されます。 クラスは HttpResponseMessage
、HTTP 要求が行われた後に REST API から受信した HTTP 応答メッセージを表します。 これには、状態コード、ヘッダー、本文など、応答に関する情報が含まれます。 クラスは HttpContent
、 や などの Content-Type
Content-Encoding
HTTP 本文とコンテンツ ヘッダーを表します。 コンテンツは、データの形式に応じて、ReadAsStringAsync
や ReadAsByteArrayAsync
などの ReadAs
メソッドのいずれかを使用して読み取ることができます。
GET 要求の作成
CatalogService
クラスは、カタログ マイクロサービスからのデータ取得プロセスを管理するために使用されます。 クラスの RegisterDependencies
ViewModelLocator
メソッドでは、 CatalogService
クラスは Autofac 依存関係挿入コンテナーを使用して型に ICatalogService
対する型マッピングとして登録されます。 次に、 クラスのインスタンスが CatalogViewModel
作成されると、そのコンストラクターは型を ICatalogService
受け入れます。この型は Autofac によって解決され、 クラスのインスタンスが返されます CatalogService
。 依存関係の挿入の詳細については、「 依存関係の挿入の概要」を参照してください。
図 10-1 は、 によって表示されるカタログ マイクロサービスからカタログ データを読み取るクラスの相互作用を CatalogView
示しています。
図 10-1: カタログ マイクロサービスからのデータの取得
に CatalogView
移動すると、 クラスの OnInitialize
CatalogViewModel
メソッドが呼び出されます。 このメソッドでは、次のコード例に示すように、カタログ マイクロサービスからカタログ データを取得します。
public override async Task InitializeAsync(object navigationData)
{
...
Products = await _productsService.GetCatalogAsync();
...
}
このメソッドは、 GetCatalogAsync
Autofac によって に挿入されたインスタンスの CatalogService
メソッドを CatalogViewModel
呼び出します。 次のコード例は、GetCatalogAsync
メソッドを示しています。
public async Task<ObservableCollection<CatalogItem>> GetCatalogAsync()
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.CatalogEndpoint);
builder.Path = "api/v1/catalog/items";
string uri = builder.ToString();
CatalogRoot catalog = await _requestProvider.GetAsync<CatalogRoot>(uri);
...
return catalog?.Data.ToObservableCollection();
}
このメソッドでは、要求が送信されるリソースを識別する URI を構築し、RequestProvider
クラスを使用してリソースに対して GET HTTP メソッドを呼び出してから、CatalogViewModel
に結果を返します。 RequestProvider
クラスには、リソースを識別する URI の形式で要求を送信する機能、そのリソースに対して実行する操作を示す HTTP メソッド、および操作を実行するために必要なデータを含む本文が含まれています。 クラスを にCatalogService class
挿入するRequestProvider
方法の詳細については、「依存関係の挿入の概要」を参照してください。
次のコード例は、RequestProvider
クラスの GetAsync
メソッドを示しています。
public async Task<TResult> GetAsync<TResult>(string uri, string token = "")
{
HttpClient httpClient = CreateHttpClient(token);
HttpResponseMessage response = await httpClient.GetAsync(uri);
await HandleResponse(response);
string serialized = await response.Content.ReadAsStringAsync();
TResult result = await Task.Run(() =>
JsonConvert.DeserializeObject<TResult>(serialized, _serializerSettings));
return result;
}
このメソッドでは、適切なヘッダーが設定された HttpClient
クラスのインスタンスを返す CreateHttpClient
メソッドを呼び出します。 その後、URI によって識別されるリソースに非同期 GET 要求を送信し、応答がインスタンスに HttpResponseMessage
格納されます。 その後、HandleResponse
メソッドが呼び出されます。応答に成功 HTTP 状態コードが含まれていない場合は例外がスローされます。 次に、応答が文字列として読み取られ、JSON から CatalogRoot
オブジェクトに変換され、CatalogService
に返されます。
CreateHttpClient
メソッドを次のコード例に示します。
private HttpClient CreateHttpClient(string token = "")
{
var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Accept.Add(
new MediaTypeWithQualityHeaderValue("application/json"));
if (!string.IsNullOrEmpty(token))
{
httpClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", token);
}
return httpClient;
}
このメソッドは、 クラスの新しいインスタンスをHttpClient
作成し、インスタンスによってHttpClient
行われた要求のヘッダーを にapplication/json
設定Accept
します。これは、応答の内容が JSON を使用して書式設定されることを想定していることを示します。 その後、アクセス トークンが CreateHttpClient
メソッドの引数として渡された場合、HttpClient
インスタンスによって行われた要求の Authorization
ヘッダーに追加され、文字列 Bearer
が付加されます。 承認の詳細については、「承認」を参照してください。
クラスの メソッドが GetAsync
をRequestProvider
呼び出HttpClient.GetAsync
Items
すと、Catalog.API プロジェクトの CatalogController
クラス内の メソッドが呼び出されます。これは、次のコード例に示されています。
[HttpGet]
[Route("[action]")]
public async Task<IActionResult> Items(
[FromQuery]int pageSize = 10, [FromQuery]int pageIndex = 0)
{
var totalItems = await _catalogContext.CatalogItems
.LongCountAsync();
var itemsOnPage = await _catalogContext.CatalogItems
.OrderBy(c=>c.Name)
.Skip(pageSize * pageIndex)
.Take(pageSize)
.ToListAsync();
itemsOnPage = ComposePicUri(itemsOnPage);
var model = new PaginatedItemsViewModel<CatalogItem>(
pageIndex, pageSize, totalItems, itemsOnPage);
return Ok(model);
}
このメソッドは、EntityFramework を使用して SQL データベースからカタログ データを取得し、成功した HTTP 状態コードと JSON 形式 CatalogItem
のインスタンスのコレクションを含む応答メッセージとして返します。
POST 要求の作成
BasketService
クラスは、バスケット マイクロサービスでデータの取得と更新のプロセスを管理するために使用されます。 クラスの RegisterDependencies
ViewModelLocator
メソッドでは、 BasketService
クラスは Autofac 依存関係挿入コンテナーを使用して型に IBasketService
対する型マッピングとして登録されます。 次に、 クラスのインスタンスが BasketViewModel
作成されると、そのコンストラクターは型を IBasketService
受け入れます。この型は Autofac によって解決され、 クラスのインスタンスが返されます BasketService
。 依存関係の挿入の詳細については、「 依存関係の挿入の概要」を参照してください。
図 10-2 は、 によって BasketView
表示されるバスケット データをバスケット マイクロサービスに送信するクラスの相互作用を示しています。
図 10-2: バスケット マイクロサービスへのデータの送信
アイテムが買い物かごに追加されると、BasketViewModel
クラスの ReCalculateTotalAsync
メソッドが呼び出されます。 このメソッドでは、次のコード例に示すように、バスケット内のアイテムの合計値を更新し、バスケット データをバスケット マイクロサービスに送信します。
private async Task ReCalculateTotalAsync()
{
...
await _basketService.UpdateBasketAsync(new CustomerBasket
{
BuyerId = userInfo.UserId,
Items = BasketItems.ToList()
}, authToken);
}
このメソッドは、 UpdateBasketAsync
Autofac によって に挿入されたインスタンスの BasketService
メソッドを BasketViewModel
呼び出します。 次のメソッドは UpdateBasketAsync
メソッドを示しています。
public async Task<CustomerBasket> UpdateBasketAsync(CustomerBasket customerBasket, string token)
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint);
string uri = builder.ToString();
var result = await _requestProvider.PostAsync(uri, customerBasket, token);
return result;
}
このメソッドでは、要求が送信されるリソースを識別する URI をビルドし、RequestProvider
クラスを使用してリソースに対して POST HTTP メソッドを呼び出してから、BasketViewModel
に結果を返します。 認証プロセス中に IdentityServer から取得されたアクセス トークンは、バスケット マイクロサービスへの要求を承認するために必要であることに注意してください。 承認の詳細については、「承認」を参照してください。
次のコード例は、RequestProvider
クラスの PostAsync
メソッドの 1 つを示しています。
public async Task<TResult> PostAsync<TResult>(
string uri, TResult data, string token = "", string header = "")
{
HttpClient httpClient = CreateHttpClient(token);
...
var content = new StringContent(JsonConvert.SerializeObject(data));
content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
HttpResponseMessage response = await httpClient.PostAsync(uri, content);
await HandleResponse(response);
string serialized = await response.Content.ReadAsStringAsync();
TResult result = await Task.Run(() =>
JsonConvert.DeserializeObject<TResult>(serialized, _serializerSettings));
return result;
}
このメソッドでは、適切なヘッダーが設定された HttpClient
クラスのインスタンスを返す CreateHttpClient
メソッドを呼び出します。 その後、URI によって識別されたリソースに非同期 POST 要求を送信し、シリアル化されたバスケット データは JSON 形式で送信され、応答は HttpResponseMessage
インスタンスに格納されます。 その後、HandleResponse
メソッドが呼び出されます。応答に成功 HTTP 状態コードが含まれていない場合は例外がスローされます。 次に、応答が文字列として読み取られ、JSON から オブジェクトに CustomerBasket
変換され、 に BasketService
返されます。 メソッドの詳細については、「GET 要求のCreateHttpClient
作成」を参照してください。
クラスの メソッドが PostAsync
をRequestProvider
呼び出HttpClient.PostAsync
Post
すと、Basket.API プロジェクトの BasketController
クラス内のメソッドが呼び出されます。これは、次のコード例に示されています。
[HttpPost]
public async Task<IActionResult> Post([FromBody]CustomerBasket value)
{
var basket = await _repository.UpdateBasketAsync(value);
return Ok(basket);
}
このメソッドでは、RedisBasketRepository
クラスのインスタンスを使用して、バスケット データを Redis Cache に保持し、成功 HTTP 状態コードと、JSON 形式の CustomerBasket
インスタンスを含む応答メッセージとして返します。
DELETE 要求の作成
図 10-3 は、 のバスケット マイクロサービスからバスケット データを削除するクラスの相互作用を CheckoutView
示しています。
図 10-3: バスケット マイクロサービスからのデータの削除
チェックアウト プロセスが呼び出されると、CheckoutViewModel
クラスの CheckoutAsync
メソッドが呼び出されます。 このメソッドは、次のコード例に示すように、買い物かごをクリアする前に新しい注文を作成します。
private async Task CheckoutAsync()
{
...
await _basketService.ClearBasketAsync(_shippingAddress.Id.ToString(), authToken);
...
}
このメソッドは、 ClearBasketAsync
Autofac によって に挿入されたインスタンスの BasketService
メソッドを CheckoutViewModel
呼び出します。 次のメソッドは ClearBasketAsync
メソッドを示しています。
public async Task ClearBasketAsync(string guidUser, string token)
{
UriBuilder builder = new UriBuilder(GlobalSetting.Instance.BasketEndpoint);
builder.Path = guidUser;
string uri = builder.ToString();
await _requestProvider.DeleteAsync(uri, token);
}
このメソッドは、要求の送信先のリソースを識別する URI をビルドし、 クラスを RequestProvider
使用してリソースに対して DELETE HTTP メソッドを呼び出します。 認証プロセス中に IdentityServer から取得されたアクセス トークンは、バスケット マイクロサービスへの要求を承認するために必要であることに注意してください。 承認の詳細については、「承認」を参照してください。
次のコード例は、RequestProvider
クラスの DeleteAsync
メソッドを示しています。
public async Task DeleteAsync(string uri, string token = "")
{
HttpClient httpClient = CreateHttpClient(token);
await httpClient.DeleteAsync(uri);
}
このメソッドでは、適切なヘッダーが設定された HttpClient
クラスのインスタンスを返す CreateHttpClient
メソッドを呼び出します。 次に、URI によって識別されるリソースに非同期 DELETE 要求を送信します。 メソッドの詳細については、「GET 要求のCreateHttpClient
作成」を参照してください。
クラスの メソッドが DeleteAsync
をRequestProvider
呼び出HttpClient.DeleteAsync
Delete
すと、Basket.API プロジェクトの BasketController
クラス内のメソッドが呼び出されます。これは、次のコード例に示されています。
[HttpDelete("{id}")]
public void Delete(string id)
{
_repository.DeleteBasketAsync(id);
}
このメソッドでは RedisBasketRepository
クラスのインスタンスを使用して、Redis Cache からバスケット データを削除します。
キャッシュされたデータ
頻繁にアクセスされるデータを、アプリの近くにある高速ストレージにキャッシュすることで、アプリのパフォーマンスを向上させることができます。 高速ストレージが元のソースよりもアプリの近くに配置されている場合、キャッシュを使用すると、データを取得するときの応答時間が大幅に向上する可能性があります。
キャッシュの最も一般的な形式は、読み取りスルー キャッシュです。ここで、アプリではキャッシュを参照してデータを取得します。 データがキャッシュにない場合は、データ ストアから取得され、キャッシュに追加されます。 アプリでは、キャッシュアサイド パターンを使用して読み取りスルー キャッシュを実装できます。 このパターンでは、アイテムが現在キャッシュ内にあるかどうかを判断します。 アイテムがキャッシュ内にない場合は、データ ストアから読み取られ、キャッシュに追加されます。 詳細については、「 Cache-Aside パターン」を参照してください。
ヒント
頻繁に読み取られ、変更頻度の低いデータをキャッシュします。 このデータは、アプリによって初めて取得されるときに、必要に応じてキャッシュに追加できます。 これは、アプリでデータ ストアからデータを取り込む必要があるのは 1 回のみで、以降のアクセスにはキャッシュを利用できることを意味します。
eShopOnContainers 参照アプリケーションなどの分散アプリケーションでは、次のキャッシュのいずれかまたは両方を提供する必要があります。
- 共有キャッシュ。複数のプロセスまたはコンピューターからアクセスできます。
- プライベート キャッシュ。データは、アプリを実行しているデバイス上でローカルに保持されます。
eShopOnContainers モバイル アプリはプライベート キャッシュを使用します。このキャッシュでは、アプリのインスタンスを実行しているデバイス上でデータがローカルに保持されます。 eShopOnContainers 参照アプリケーションで使用されるキャッシュについては、「.NET マイクロサービス: コンテナー化された .NET アプリケーションのアーキテクチャ」を参照してください。
ヒント
キャッシュは、いつ消えるかわからない一時的なデータ ストアと考えてください。 データが元のデータ ストアとキャッシュに確実に保持されるようにしてください。 そうすれば、キャッシュが使用できなくなった場合に、データが失われる可能性は最小限に抑えられます。
データの有効期限の管理
キャッシュされたデータが常に元のデータと一致することを期待するのは現実的ではありません。 元のデータ ストア内のデータは、キャッシュされた後に変更され、キャッシュされたデータが古くなる可能性があります。 したがって、アプリでは、キャッシュのデータをできるだけ最新の状態に維持できるようにするための戦略を実装する必要がありますが、キャッシュのデータが古くなったときに発生する状況を検出して対処することもできます。 ほとんどのキャッシュ メカニズムでは、データの有効期限が切れるようキャッシュを構成できるため、データが古くなっている可能性がある期間を短縮できます。
ヒント
キャッシュの構成時に既定の有効期限を設定します。 多くのキャッシュで有効期限が実装され、データが無効になり、指定された期間にアクセスされていない場合はキャッシュから削除されます。 しかし、有効期限を選択するときは注意が必要です。 短すぎると、データの有効期限が早くなりすぎて、キャッシュの利点が減ります。 長すぎると、データが古くなるリスクがあります。 そのため、有効期限は、データを使用するアプリのアクセス パターンと一致する必要があります。
キャッシュされたデータは、有効期限が切れたときにキャッシュから削除する必要があり、アプリで元のデータ ストアからデータを取得してキャッシュに戻す必要があります。
また、データを長期間保持することが許可されている場合は、キャッシュがいっぱいになる可能性もあります。 そのため、キャッシュに新しいアイテムを追加する要求は、''削除'' と呼ばれるプロセスで一部のアイテムを削除するために必要な場合があります。 通常、キャッシュ サービスでは最も長く使われていないデータを削除します。 ただし、最近使用した削除ポリシーや先入れ先出しポリシーなど、他にも削除ポリシーがあります。詳細については、「 キャッシュ ガイダンス」を参照してください。
イメージのキャッシュ
eShopOnContainers モバイル アプリは、キャッシュのメリットを得られるリモート製品イメージを使用します。 これらのイメージは、コントロールとCachedImage
、FFImageLoading ライブラリによって提供されるコントロールによって表示されますImage
。
コントロールは Xamarin.FormsImage
、ダウンロードしたイメージのキャッシュをサポートします。 キャッシュは既定で有効になっており、イメージは 24 時間ローカルに格納されます。 また、 プロパティを使用して有効期限を CacheValidity
構成することもできます。 詳細については、「 ダウンロードしたイメージ キャッシュ」を参照してください。
FFImageLoading の CachedImage
コントロールはコントロールの代わり Xamarin.FormsImage
であり、補助機能を有効にする追加のプロパティを提供します。 この機能の中でも、コントロールは構成可能なキャッシュを提供し、エラーをサポートし、画像プレースホルダーを読み込みます。 次のコード例は、eShopOnContainers モバイル アプリが で コントロールをCachedImage
使用する方法を示しています。これは、 の CatalogView
コントロールによってListView
使用されるデータ テンプレートProductTemplate
です。
<ffimageloading:CachedImage
Grid.Row="0"
Source="{Binding PictureUri}"
Aspect="AspectFill">
<ffimageloading:CachedImage.LoadingPlaceholder>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS, Android" Value="default_campaign" />
<On Platform="UWP" Value="Assets/default_campaign.png" />
</OnPlatform>
</ffimageloading:CachedImage.LoadingPlaceholder>
<ffimageloading:CachedImage.ErrorPlaceholder>
<OnPlatform x:TypeArguments="ImageSource">
<On Platform="iOS, Android" Value="noimage" />
<On Platform="UWP" Value="Assets/noimage.png" />
</OnPlatform>
</ffimageloading:CachedImage.ErrorPlaceholder>
</ffimageloading:CachedImage>
コントロールは CachedImage
、 LoadingPlaceholder
プロパティと ErrorPlaceholder
プロパティをプラットフォーム固有のイメージに設定します。 プロパティは LoadingPlaceholder
、 プロパティで Source
指定されたイメージの取得中に表示されるイメージを指定し ErrorPlaceholder
、 プロパティで指定されたイメージを取得しようとしたときにエラーが発生した場合に表示されるイメージを Source
指定します。
名前が示すように、コントロールは、 プロパティの CachedImage
値で指定された時間、デバイス上のリモート イメージを CacheDuration
キャッシュします。 このプロパティ値が明示的に設定されていない場合、既定値の 30 日が適用されます。
回復性の向上
リモート サービスおよびリソースと通信するすべてのアプリは、一時的な障害に特別な注意を払う必要があります。 一時的な障害には、サービスへのネットワーク接続の瞬間的な喪失、サービスの一時的な使用不可、サービスがビジー状態のときに発生するタイムアウトなどがあります。 多くの場合、これらの障害は自己修正され、しばらくしてから操作を繰り返せば、成功する可能性があります。
予測しうるあらゆる状況下で十分にテストされていても、一時的な障害がアプリの感性品質に大きく影響する場合があります。 リモート サービスと通信するアプリが確実に動作するように、次のすべてを実行できる必要があります。
- 障害が発生したときにその障害を検出し、障害が一時的なものである可能性があるかどうかを判断する。
- 一時的な障害である可能性があると判断した場合、操作を再試行し、操作の再試行回数を把握する。
- 再試行回数、試行間隔、試行失敗後の措置を指定する適切な再試行戦略を使用する。
この一時的な障害の処理は、再試行パターンを実装するコード内のリモート サービスへのアクセスのすべての試行をラップすることによって実現できます。
再試行パターン
アプリでリモート サービスに要求を送信しようとしたときに障害が検出された場合は、次のいずれかの方法で障害を処理できます。
- 操作を再試行する。 アプリで失敗した要求を直ちに再試行できます。
- しばらくしてから操作を再試行する。 アプリは、要求を再試行する前に適切な時間、待機する必要があります。
- 操作を取り消す。 アプリケーションでは操作を取り消し、例外を報告する必要があります。
再試行戦略は、アプリのビジネス要件と一致するように調整する必要があります。 たとえば、再試行する操作の再試行回数と再試行間隔を最適化することが重要です。 操作がユーザー操作の一部である場合は、再試行間隔を短くし、ユーザーに応答を待たせないように再試行の回数を少なく抑える必要があります。 操作が実行時間の長いワークフローの一部であり、ワークフローの取り消しや再起動にコストがかかる場合や時間がかかる場合は、試行の間隔を長めにし、再試行回数を増やすのが妥当です。
注意
最短の待ち時間で何回も再試行を行う積極的な再試行戦略は、フル稼働しているかその状態に近いリモート サービスを低下させる可能性があります。 さらに、このような再試行戦略は、失敗した操作を継続的に再試行しようとした場合、アプリの応答性にも影響を与える可能性があります。
何回再試行しても要求が失敗する場合は、アプリで同じリソースに要求がそれ以上送信されないようにし、障害を報告することをお勧めします。 その後、設定された期間が経過すると、アプリではリソースに対して 1 つまたは複数の要求を行って、それらが成功したかどうかを確認できます。 詳細については、「 サーキット ブレーカー パターン」を参照してください。
ヒント
再試行が際限なく繰り返される設計は確実に避けてください。 再試行回数を制限するか、サーキット ブレーカー パターンを実装してサービスが回復できるようにします。
eShopOnContainers モバイル アプリでは、RESTful Web 要求を行うときの再試行パターンは現在実装されていません。 ただし、CachedImage
FFImageLoading ライブラリによって提供されるコントロールは、イメージの読み込みを再試行することによって一時的な障害処理をサポートします。 イメージの読み込みに失敗した場合は、さらに試行されます。 試行回数は プロパティで指定され、 プロパティで RetryCount
指定された遅延後に再試行が RetryDelay
行われます。 これらのプロパティ値が明示的に設定されていない場合は、既定値 (プロパティの場合は 3 RetryCount
、プロパティの場合は 250 ミリ秒) が RetryDelay
適用されます。 コントロールの詳細については、「イメージのCachedImage
キャッシュ」を参照してください。
eShopOnContainers 参照アプリケーションでは再試行パターンを実装します。 再試行パターン HttpClient
と クラスを組み合わせる方法の説明など、詳細については、「 .NET マイクロサービス: コンテナー化された .NET アプリケーションのアーキテクチャ」を参照してください。
再試行パターンの詳細については、「 再試行パターン」 を参照してください。
遮断器のパターン
場合によっては、修正に時間がかかる予想されるイベントが原因で障害が発生することがあります。 これらの障害は、接続の部分的な喪失からサービスの完全な障害までの範囲で発生する可能性があります。 このような状況では、成功する可能性が低い操作をアプリで再試行するのは無意味です。そうではなく、操作が失敗したことを受け入れ、それに応じてこの障害を処理する必要があります。
サーキット ブレーカー パターンを使用すると、失敗する可能性がある操作をアプリで繰り返し試行することを防ぐことができ、同時にアプリで障害が解決されたかどうかを検出することもできます。
注意
サーキット ブレーカー パターンの目的は、再試行パターンとは異なります。 再試行パターンでは、アプリで成功を見込んで操作を再試行することができます。 サーキット ブレーカー パターンにより、失敗する可能性がある操作がアプリで実行されなくなります。
サーキット ブレーカーは、失敗する可能性のある操作のプロキシとして機能します。 プロキシでは、最近発生した障害の数を監視し、この情報を使用して、操作を続行できるか、すぐに例外を返すかを判断します。
eShopOnContainers モバイル アプリでは、現在サーキット ブレーカー パターンは実装されていません。 しかし、eShopOnContainers ではそうします。 詳細については、「NET マイクロサービス: コンテナー化された .NET アプリケーションのアーキテクチャ」を参照してください。
ヒント
再試行およびサーキット ブレーカー パターンを組み合わせます。 アプリでは、再試行パターンを使用してサーキット ブレーカーを介して操作を呼び出すことによって、再試行およびサーキット ブレーカー パターンを組み合わせることができます。 ただし、再試行ロジックは、サーキット ブレーカーによって返されるすべての例外から大きな影響を受け、エラーが一時的なものではないことが示されると、再試行回数を破棄します。
サーキット ブレーカー パターンの詳細については、「サーキット ブレーカー パターン」を参照してください。
まとめ
現代の多くの Web ベース ソリューションでは、Web サーバーによりホストされる Web サービスを使用することで、リモート クライアント アプリケーションの機能を提供します。 Web サービスによって公開される操作では Web API が構成され、クライアント アプリでは、API によって公開されるデータや操作がどのように実装されるかがわかっていなくても、Web API を利用できる必要があります。
頻繁にアクセスされるデータを、アプリの近くにある高速ストレージにキャッシュすることで、アプリのパフォーマンスを向上させることができます。 アプリでは、キャッシュアサイド パターンを使用して読み取りスルー キャッシュを実装できます。 このパターンでは、アイテムが現在キャッシュ内にあるかどうかを判断します。 アイテムがキャッシュ内にない場合は、データ ストアから読み取られ、キャッシュに追加されます。
Web API と通信する場合、アプリは一時的な障害に特別な注意を払う必要があります。 一時的な障害には、サービスへのネットワーク接続の瞬間的な喪失、サービスの一時的な使用不可、サービスがビジー状態のときに発生するタイムアウトなどがあります。 多くの場合、これらの障害は自己修正され、しばらくしてから操作が繰り返されれば、成功する可能性があります。 したがって、アプリでは、一時的な障害処理メカニズムを実装するコード内の Web API へのアクセスのすべての試行をラップする必要があります。