リモート データへのアクセス

Note

この電子ブックは 2017 年春に発行されたもので、その後は改訂されていません。 このブックの内容の多くは現時点でも有用ですが、一部の記載内容は古くなっています。

現代の多くの Web ベース ソリューションでは、Web サーバーによりホストされる Web サービスを使用することで、リモート クライアント アプリケーションの機能を提供します。 Web サービスにより公開される操作によって Web API が構成されています。

クライアント アプリでは、API によって公開されるデータや操作がどのように実装されるかがわかっていなくても、Web API を利用できる必要があります。 そのためには、API が共通基準を順守し、クライアント アプリおよび Web サービスが、使用するデータ形式や、クライアント アプリと Web サービスの間で交換されるデータの構造に同意できるようにする必要があります。

Representational State Transfer の概要

Representational State Transfer (REST) は、ハイパーメディアに基づき分散システムを構築するためのアーキテクチャ スタイルです。 REST モデルの主な利点は、オープン スタンダードに基づいており、それにアクセスするモデルやクライアント アプリの実装を、特定の実装に結びつけないという点です。 したがって、REST Web サービスは Microsoft ASP.NET Core MVC を使用して実装でき、クライアント アプリは任意の言語および 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 クラスは、HTTP 本文とコンテンツ ヘッダー (Content-TypeContent-Encoding など) を表します。 コンテンツは、データの形式に応じて、ReadAsStringAsyncReadAsByteArrayAsync などの ReadAs メソッドのいずれかを使用して読み取ることができます。

GET 要求を行う

CatalogService クラスは、カタログ マイクロサービスからのデータ取得プロセスを管理するために使用されます。 ViewModelLocator クラスの RegisterDependencies メソッドでは、CatalogService クラスは、Autofac 依存関係挿入コンテナーを使用して ICatalogService 型に対する型マッピングとして登録されます。 その後、CatalogViewModel クラスのインスタンスが作成されると、そのコンストラクターでは Autofac が解決する ICatalogService 型を受け入れ、CatalogService クラスのインスタンスを返します。 依存関係の挿入について詳しくは、「依存関係の挿入の概要」参照してください。

図 10-1 は、CatalogView で表示するためにカタログ マイクロサービスからカタログ データを読み取るクラスのやりとりを示しています。

カタログ マイクロサービスからのデータの取得

図 10-1: カタログ マイクロサービスからのデータの取得

CatalogView に移動すると、CatalogViewModel クラス内の OnInitialize メソッドが呼び出されます。 このメソッドでは、次のコード例に示すように、カタログ マイクロサービスからカタログ データを取得します。

public override async Task InitializeAsync(object navigationData)  
{  
    ...  
    Products = await _productsService.GetCatalogAsync();  
    ...  
}

このメソッドでは、Autofac によって CatalogViewModel に挿入された CatalogService インスタンスの GetCatalogAsync メソッドを呼び出します。 次のコード例は、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 メソッド、および操作を実行するために必要なデータを含む本文が含まれています。 RequestProvider クラスを CatalogService class に挿入する方法については、「依存関係の挿入の概要」参照してください。

次のコード例は、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 インスタンスによって行われた要求の Accept ヘッダーを application/json に設定します。これは、応答のコンテンツが JSON を使用して書式設定されることを想定していることを示します。 その後、アクセス トークンが CreateHttpClient メソッドの引数として渡された場合、HttpClient インスタンスによって行われた要求の Authorization ヘッダーに追加され、文字列 Bearer が付加されます。 承認の詳細については、「承認」を参照してください。

RequestProvider クラスの GetAsync メソッドで HttpClient.GetAsync が呼び出されると、Catalog.API プロジェクトの CatalogController クラスの Items メソッドが呼び出されます。これについては、次のコード例に示されています。

[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 クラスは、バスケット マイクロサービスでデータの取得と更新のプロセスを管理するために使用されます。 ViewModelLocator クラスの RegisterDependencies メソッドでは、BasketService クラスは、Autofac 依存関係挿入コンテナーを使用して IBasketService 型に対する型マッピングとして登録されます。 その後、BasketViewModel クラスのインスタンスが作成されると、そのコンストラクターでは Autofac が解決する IBasketService 型を受け入れ、BasketService クラスのインスタンスを返します。 依存関係の挿入について詳しくは、「依存関係の挿入の概要」参照してください。

図 10-2 は、BasketView によって表示されるバスケット データをバスケット マイクロサービスに送信するクラスのやりとりを示しています。

バスケット マイクロサービスへのデータの送信

図 10-2: バスケット マイクロサービスへのデータの送信

アイテムが買い物かごに追加されると、BasketViewModel クラスの ReCalculateTotalAsync メソッドが呼び出されます。 このメソッドでは、次のコード例に示すように、バスケット内のアイテムの合計値を更新し、バスケット データをバスケット マイクロサービスに送信します。

private async Task ReCalculateTotalAsync()  
{  
    ...  
    await _basketService.UpdateBasketAsync(new CustomerBasket  
    {  
        BuyerId = userInfo.UserId,   
        Items = BasketItems.ToList()  
    }, authToken);  
}

このメソッドでは、Autofac によって BasketViewModel に挿入された BasketService インスタンスの UpdateBasketAsync メソッドを呼び出します。 次のメソッドは 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 に返されます。 CreateHttpClient メソッドの詳細については、「GET 要求を行う」を参照してください。

RequestProvider クラスの PostAsync メソッドで HttpClient.PostAsync が呼び出されると、Basket.API プロジェクトの BasketController クラスの Post メソッドが呼び出されます。これについては、次のコード例に示されています。

[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);  
    ...  
}

このメソッドでは、Autofac によって CheckoutViewModel に挿入された BasketService インスタンスの ClearBasketAsync メソッドを呼び出します。 次のメソッドは 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 要求を送信します。 CreateHttpClient メソッドの詳細については、「GET 要求を行う」を参照してください。

RequestProvider クラスの DeleteAsync メソッドで HttpClient.DeleteAsync が呼び出されると、Basket.API プロジェクトの BasketController クラスの Delete メソッドが呼び出されます。これについては、次のコード例に示されています。

[HttpDelete("{id}")]  
public void Delete(string id)  
{  
    _repository.DeleteBasketAsync(id);  
}

このメソッドでは RedisBasketRepository クラスのインスタンスを使用して、Redis Cache からバスケット データを削除します。

キャッシュされたデータ

頻繁にアクセスされるデータを、アプリの近くにある高速ストレージにキャッシュすることで、アプリのパフォーマンスを向上させることができます。 高速ストレージが元のソースよりもアプリの近くに配置されている場合、キャッシュを使用すると、データを取得するときの応答時間が大幅に向上する可能性があります。

キャッシュの最も一般的な形式は、読み取りスルー キャッシュです。ここで、アプリではキャッシュを参照してデータを取得します。 データがキャッシュにない場合は、データ ストアから取得され、キャッシュに追加されます。 アプリでは、キャッシュアサイド パターンを使用して読み取りスルー キャッシュを実装できます。 このパターンでは、アイテムが現在キャッシュ内にあるかどうかを判断します。 アイテムがキャッシュ内にない場合は、データ ストアから読み取られ、キャッシュに追加されます。 詳細については、「キャッシュ アサイド パターン」を参照してください。

ヒント

頻繁に読み取られ、頻繁に変更されるデータをキャッシュします。 このデータは、アプリによって初めて取得されるときに、必要に応じてキャッシュに追加できます。 これは、アプリでデータ ストアからデータを取り込む必要があるのは 1 回のみで、以降のアクセスにはキャッシュを利用できることを意味します。

eShopOnContainers 参照アプリケーションなどの分散アプリケーションでは、次のキャッシュのいずれかまたは両方を提供する必要があります。

  • 共有キャッシュ。複数のプロセスまたはコンピューターからアクセスできます。
  • プライベート キャッシュ。データは、アプリを実行しているデバイス上でローカルに保持されます。

eShopOnContainers モバイル アプリではプライベート キャッシュが使用され、アプリのインスタンスを実行しているデバイス上でローカルにデータが保持されます。 eShopOnContainers 参照アプリケーションで使用されるキャッシュについては、「.NET マイクロサービス: コンテナー化された .NET アプリケーションのアーキテクチャ」を参照してください。

ヒント

キャッシュは、いつ消えるかわからない一時的なデータ ストアと考えてください。 データが元のデータ ストアとキャッシュに確実に保持されるようにしてください。 そうすれば、キャッシュが使用できなくなった場合に、データが失われる可能性は最小限に抑えられます。

データの有効期限の管理

キャッシュされたデータが常に元のデータと一致することを期待するのは現実的ではありません。 元のデータ ストア内のデータは、キャッシュされた後に変更され、キャッシュされたデータが古くなる可能性があります。 したがって、アプリでは、キャッシュのデータをできるだけ最新の状態に維持できるようにするための戦略を実装する必要がありますが、キャッシュのデータが古くなったときに発生する状況を検出して対処することもできます。 ほとんどのキャッシュ メカニズムでは、データの有効期限が切れるようキャッシュを構成できるため、データが古くなっている可能性がある期間を短縮できます。

ヒント

キャッシュの構成時に既定の有効期限を設定します。 多くのキャッシュで有効期限が実装され、データが無効になり、指定された期間にアクセスされていない場合はキャッシュから削除されます。 しかし、有効期限を選択するときは注意が必要です。 短すぎると、データの有効期限が早くなりすぎて、キャッシュの利点が減ります。 長すぎると、データが古くなるリスクがあります。 そのため、有効期限は、データを使用するアプリのアクセス パターンと一致する必要があります。

キャッシュされたデータは、有効期限が切れたときにキャッシュから削除する必要があり、アプリで元のデータ ストアからデータを取得してキャッシュに戻す必要があります。

また、データを長期間保持することが許可されている場合は、キャッシュがいっぱいになる可能性もあります。 そのため、キャッシュに新しいアイテムを追加する要求は、''削除'' と呼ばれるプロセスで一部のアイテムを削除するために必要な場合があります。 通常、キャッシュ サービスでは最も長く使われていないデータを削除します。 しかし、最近使用したポリシーや先入れ先出しポリシーなど、他にも削除ポリシーがあります。詳細については、「キャッシュに関するガイダンス」を参照してください。

イメージのキャッシュ

eShopOnContainers モバイル アプリでは、キャッシュすることで利点が得られるリモート製品イメージが使用されます。 これらのイメージは、Image コントロールと、FFImageLoading ライブラリによって提供される CachedImage コントロールによって表示されます。

Xamarin.FormsImage コントロールは、ダウンロードしたイメージのキャッシュをサポートします。 キャッシュは既定で有効になっており、画像は 24 時間ローカルに保存されます。 さらに、CacheValidity プロパティを使用して有効期限を構成できます。 詳細については、「ダウンロードしたイメージ のキャッシュ」を参照してください。

FFImageLoading の CachedImage コントロールは Xamarin.FormsImage コントロールの代わりであり、補助機能を有効にする追加のプロパティを提供します。 この機能の中でも、コントロールは構成可能なキャッシュを提供すると同時にし、エラーとイメージ プレースホルダーの読み込みをサポートします。 次のコード例は、eShopOnContainers モバイル アプリが ProductTemplateCachedImage コントロールを使用する方法を示しています。これは、CatalogView で ListView コントロールによって使用されるデータ テンプレートです。

<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 つまたは複数の要求を行って、それらが成功したかどうかを確認できます。 詳細については、「Circuit Breaker Pattern (Circuit Breaker パターン)」をご覧ください。

ヒント

再試行が際限なく繰り返される設計は確実に避けてください。 再試行回数を制限するか、サーキット ブレーカー パターンを実装してサービスが回復できるようにします。

eShopOnContainers モバイル アプリは現在、RESTful Web 要求を行う際の再試行パターンを実装していません。 ただし、FFImageLoading ライブラリによって提供される CachedImage コントロールは、イメージの読み込みを再試行することによって一時的な障害処理をサポートします。 イメージの読み込みに失敗した場合は、さらに試行されます。 試行回数は RetryCount プロパティによって指定され、再試行は RetryDelay プロパティで指定された遅延の後に行われます。 これらのプロパティ値が明示的に設定されていない場合は、既定値が適用されます。RetryCount プロパティは 3、RetryDelay プロパティは 250 ミリ秒 です。 CachedImage コントロールの詳細については、「イメージのキャッシュ」を参照してください。

eShopOnContainers 参照アプリケーションでは再試行パターンを実装します。 再試行パターンと HttpClient クラスを組み合わせる方法の説明など、詳細については、「NET マイクロサービス: コンテナー化された .NET アプリケーションのアーキテクチャ」を参照してください。

再試行パターンの詳細については、「再試行パターン」を参照してください。

Circuit Breaker パターン

場合によっては、修正に時間がかかる予想されるイベントが原因で障害が発生することがあります。 これらの障害は、接続の部分的な喪失からサービスの完全な障害までの範囲で発生する可能性があります。 このような状況では、成功する可能性が低い操作をアプリで再試行するのは無意味です。そうではなく、操作が失敗したことを受け入れ、それに応じてこの障害を処理する必要があります。

サーキット ブレーカー パターンを使用すると、失敗する可能性がある操作をアプリで繰り返し試行することを防ぐことができ、同時にアプリで障害が解決されたかどうかを検出することもできます。

注意

サーキット ブレーカー パターンの目的は、再試行パターンとは異なります。 再試行パターンでは、アプリで成功を見込んで操作を再試行することができます。 サーキット ブレーカー パターンにより、失敗する可能性がある操作がアプリで実行されなくなります。

サーキット ブレーカーは、失敗する可能性のある操作のプロキシとして機能します。 プロキシでは、最近発生した障害の数を監視し、この情報を使用して、操作を続行できるか、すぐに例外を返すかを判断します。

eShopOnContainers モバイル アプリでは、現在、サーキット ブレーカー パターンは実装されていません。 しかし、eShopOnContainers ではそうします。 詳細については、「NET マイクロサービス: コンテナー化された .NET アプリケーションのアーキテクチャ」を参照してください。

ヒント

再試行およびサーキット ブレーカー パターンを組み合わせます。 アプリでは、再試行パターンを使用してサーキット ブレーカーを介して操作を呼び出すことによって、再試行およびサーキット ブレーカー パターンを組み合わせることができます。 ただし、再試行ロジックは、サーキット ブレーカーによって返されるすべての例外から大きな影響を受け、エラーが一時的なものではないことが示されると、再試行回数を破棄します。

サーキット ブレーカー パターンの詳細については、「サーキット ブレーカー パターン」を参照してください。

まとめ

現代の多くの Web ベース ソリューションでは、Web サーバーによりホストされる Web サービスを使用することで、リモート クライアント アプリケーションの機能を提供します。 Web サービスによって公開される操作では Web API が構成され、クライアント アプリでは、API によって公開されるデータや操作がどのように実装されるかがわかっていなくても、Web API を利用できる必要があります。

頻繁にアクセスされるデータを、アプリの近くにある高速ストレージにキャッシュすることで、アプリのパフォーマンスを向上させることができます。 アプリでは、キャッシュアサイド パターンを使用して読み取りスルー キャッシュを実装できます。 このパターンでは、アイテムが現在キャッシュ内にあるかどうかを判断します。 アイテムがキャッシュ内にない場合は、データ ストアから読み取られ、キャッシュに追加されます。

Web API と通信する場合、アプリは一時的な障害に特別な注意を払う必要があります。 一時的な障害には、サービスへのネットワーク接続の瞬間的な喪失、サービスの一時的な使用不可、サービスがビジー状態のときに発生するタイムアウトなどがあります。 多くの場合、これらの障害は自己修正され、しばらくしてから操作が繰り返されれば、成功する可能性があります。 したがって、アプリでは、一時的な障害処理メカニズムを実装するコード内の Web API へのアクセスのすべての試行をラップする必要があります。