다음을 통해 공유


원격 데이터에 액세스

이 콘텐츠는 ‘.NET MAUI를 사용하는 엔터프라이즈 애플리케이션 패턴’ eBook에서 발췌한 것으로, .NET Docs에서 제공되거나 오프라인으로 읽을 수 있는 다운로드 가능한 무료 PDF로 제공됩니다.

‘.NET MAUI를 사용하는 엔터프라이즈 애플리케이션 패턴’ 전자책 표지 썸네일

많은 최신 웹 기반 솔루션은 웹 서버에서 호스트되는 웹 서비스를 사용하여 원격 클라이언트 애플리케이션의 기능을 제공합니다. 웹 API는 웹 서비스에 표시되는 작업으로 구성됩니다.

클라이언트 앱은 API가 노출하는 데이터 또는 작업이 구현되는 방식을 알지 못하는 상황에서 웹 API를 활용할 수 있어야 합니다. 즉, API는 클라이언트 앱과 웹 서비스가 사용할 데이터 형식 및 클라이언트 앱과 웹 서비스 간에 교환되는 데이터의 구조를 일치시킬 수 있는 공통 표준을 적용해야 합니다.

Representational State Transfer 소개

REST(Representational State Transfer)는 하이퍼미디어 기반 분산 시스템을 구축하기 위한 아키텍처 스타일입니다. REST 모델의 주요 이점은 개방형 표준을 기반으로 하고 있어 해당 모델의 구현 또는 그 모델에 액세스하는 클라이언트 앱의 구현이 어떠한 특정 구현에도 바인딩되지 않는다는 것입니다. 따라서 Microsoft ASP.NET Core를 사용하여 REST 웹 서비스를 구현할 수 있으며, 클라이언트 앱은 HTTP 요청을 생성하고 HTTP 응답을 구문 분석할 수 있는 언어 및 도구 집합을 사용하여 개발할 수 있습니다.

REST 모델은 탐색 체계를 사용하여 네트워크를 통해 개체 및 서비스(리소스라고 함)를 나타냅니다. 일반적으로 REST를 구현하는 시스템은 HTTP 프로토콜을 사용하여 이러한 리소스에 액세스하기 위한 요청을 전송합니다. 이러한 시스템에서 클라이언트 앱은 하나의 리소스를 식별하는 URI와 해당 리소스에서 수행할 작업을 나타내는 HTTP 메서드(예: GET, POST, PUT 또는 DELETE)의 형식으로 요청을 제출합니다. HTTP 요청의 본문은 작업을 수행하는 데 필요한 모든 데이터를 포함하고 있습니다.

참고

REST는 상태 비저장 요청 모델을 정의합니다. 따라서 HTTP 요청은 독립적이어야 하며 어떤 순서로든 발생할 수 있습니다.

REST 요청에 대한 응답은 표준 HTTP 상태 코드를 사용합니다. 예를 들어 유효한 데이터를 반환하는 요청은 HTTP 응답 코드 200(OK)을 포함해야 하는 반면, 지정된 리소스를 찾거나 삭제하는 데 실패한 요청은 HTTP 상태 코드 404(Not Found)가 포함된 응답을 반환해야 합니다.

RESTful 웹 API는 일련의 연결된 리소스를 표시하며 앱이 해당 리소스를 조작하고 리소스 사이를 쉽게 탐색할 수 있도록 하는 코어 작업을 제공합니다. 따라서 일반적인 RESTful 웹 API를 구성하는 URI는 그것이 표시하는 데이터를 지향하며 HTTP가 이 데이터에 대해 작업하기 위해 제공하는 기능을 사용해야 합니다.

HTTP 요청에서 클라이언트 앱이 포함시킨 데이터 및 웹 서버에서 발신되는 해당 응답 메시지를 미디어 형식이라고 하는 다양한 형식으로 제공할 수 있습니다. 클라이언트 앱은 메시지 본문에 데이터를 반환하는 요청을 보낼 때 처리할 수 있는 미디어 형식을 요청의 Accept 헤더에 지정할 수 있습니다. 웹 서버는 이 미디어 형식을 지원하는 경우 메시지 본문의 데이터 형식을 지정하는 Content-Type 헤더를 포함하고 있는 응답으로 회신할 수 있습니다. 응답 메시지를 분석하고 메시지 본문의 결과를 적절히 해석하는 것은 클라이언트 앱에서 해야 합니다.

REST에 관한 자세한 내용은 Microsoft Docs API 디자인API 구현을 참조하세요.

RESTful API 사용

eShop 다중 플랫폼 앱은 MVVM(Model-View-ViewModel) 패턴을 사용하고 패턴의 모델 요소는 앱에서 사용되는 도메인 엔터티를 나타냅니다. eShop 참조 애플리케이션의 컨트롤러 및 리포지토리 클래스는 이러한 많은 모델 개체를 수락하고 반환합니다. 따라서 이러한 클래스는 앱과 컨테이너화된 마이크로 서비스 간에 전달되는 모든 데이터를 포함하는 DTO(데이터 전송 개체)로 사용됩니다. DTO를 사용하여 웹 서비스에 데이터를 전달하고 웹 서비스에서 데이터를 받을 때의 주요 이점은 단일 원격 호출에서 더 많은 데이터를 전송하면 앱에서 수행해야 하는 원격 호출 수를 줄일 수 있다는 것입니다.

웹 요청 수행

eShop 다중 플랫폼 앱은 HttpClient 클래스를 사용하여 HTTP를 통해 요청을 수행하고 JSON은 미디어 유형으로 사용됩니다. 이 클래스는 HTTP 요청을 비동기적으로 보내고 URI 식별 리소스에서 HTTP 응답을 수신하는 기능을 제공합니다. HttpResponseMessage 클래스는 HTTP 요청이 이루어진 후 REST API에서 받은 HTTP 응답 메시지를 나타냅니다. 상태 코드, 헤더 및 모든 본문을 포함 하 여 응답에 대 한 정보를 포함 합니다. HttpContent 클래스는 콘텐츠 형식 및 콘텐츠 인코딩과 같은 HTTP 본문 및 콘텐츠 헤더를 나타냅니다. 콘텐츠는 데이터 형식에 따라 ReadAsStringAsyncReadAsByteArrayAsync와 같은 ReadAs 메서드를 사용하여 읽을 수 있습니다.

GET 요청 만들기

CatalogService 클래스는 카탈로그 마이크로 서비스에서 데이터 검색 프로세스를 관리하는 데 사용됩니다. MauiProgram 클래스의 RegisterViewModels 메서드에서 CatalogService 클래스는 종속성 주입 컨테이너를 사용하여 ICatalogService 형식에 대한 형식 매핑으로 등록됩니다. 그런 다음, CatalogViewModel 클래스의 인스턴스가 생성되면 그 생성자는 종속성 주입 컨테이너가 확인하는 ICatalogService type를 수락하고 CatalogService 클래스의 인스턴스를 반환합니다. 종속성 주입에 관한 자세한 내용은 종속성 주입을 참조하세요.

아래 이미지는 CatalogView에서 표시하기 위해 카탈로그 마이크로 서비스에서 카탈로그 데이터를 읽는 클래스의 상호 작용을 보여줍니다.

카탈로그 마이크로 서비스에서 데이터 검색.

CatalogView를 탐색할 때 CatalogViewModel 클래스의 OnInitialize 메서드가 호출됩니다. 이 메서드는 다음 코드 예제에 설명된 대로 카탈로그 마이크로 서비스에서 카탈로그 데이터를 검색합니다.

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

이 메서드는 종속성 주입 컨테이너에 의해 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;          
} 

이 메서드는 요청이 전송될 리소스를 식별하는 URI를 빌드하고 RequestProvider 클래스를 사용하여 리소스에 대한 GET HTTP 메서드를 호출한 후 그 결과를 CatalogViewModel로 반환합니다. RequestProvider 클래스에는 리소스를 식별하는 URI 형식으로 요청을 제출하는 기능, 해당 리소스에서 수행할 작업을 나타내는 HTTP 메서드, 작업을 수행하는 데 필요한 데이터가 포함된 본문이 각각 포함됩니다. RequestProvider 클래스를 CatalogService 클래스에 삽입하는 방법에 관한 자세한 내용은 종속성 주입을 참조하세요.

다음 코드 예제에서는 RequestProvider 클래스의 GetAsync 메서드를 보여줍니다.

public async Task<TResult> GetAsync<TResult>(string uri, string token = "")
{
    HttpClient httpClient = GetOrCreateHttpClient(token);
    HttpResponseMessage response = await httpClient.GetAsync(uri);

    await HandleResponse(response);
    TResult result = await response.Content.ReadFromJsonAsync<TResult>();

    return result;
}

이 메서드는 GetOrCreateHttpClient 메서드를 호출하여 적절한 헤더가 설정된 HttpClient 클래스의 인스턴스를 반환합니다. 그런 다음, 응답이 HttpResponseMessage 인스턴스에 저장되면서 URI로 식별된 리소스에 비동기 GET 요청을 제출합니다. 그런 다음, HandleResponse 메서드가 호출되며 응답에 성공 HTTP 상태 코드가 포함되지 않은 경우 예외가 발생합니다. 그런 다음, 응답은 문자열로 읽혀지고 JSON에서 CatalogRoot 개체로 변환되며 CatalogService로 반환됩니다.

GetOrCreateHttpClient 메서드는 다음 코드 예제에 나와 있습니다.

private readonly Lazy<HttpClient> _httpClient =
    new Lazy<HttpClient>(
        () =>
        {
            var httpClient = new HttpClient();
            httpClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
            return httpClient;
        },
        LazyThreadSafetyMode.ExecutionAndPublication);

private HttpClient GetOrCreateHttpClient(string token = "")
    {
        var httpClient = _httpClient.Value;

        if (!string.IsNullOrEmpty(token))
        {
            httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
        }
        else
        {
            httpClient.DefaultRequestHeaders.Authorization = null;
        }

        return httpClient;
    }

이 메서드는 새 인스턴스를 만들어 사용하거나 HttpClient 클래스의 캐시된 인스턴스를 검색하고 HttpClient 인스턴스에서 수행한 요청의 Accept 헤더를 application/json으로 설정합니다. 이는 JSON을 사용하여 모든 응답의 콘텐츠에 형식이 지정될 것으로 예상한다는 것을 나타냅니다. 그런 다음, 액세스 토큰이 GetOrCreateHttpClient 메서드에 인수로 전달된 경우 HttpClient 인스턴스에서 수행한 요청의 Authorization 헤더에 문자열 Bearer의 접두사로 추가됩니다. 권한 부여에 관한 자세한 내용은 권한 부여를 참조하세요.

애플리케이션 성능을 높이려면 HttpClient의 인스턴스를 캐시하고 다시 사용하는 것을 적극 권장합니다. 각 작업에 대해 새 HttpClient를 만들면 소켓 소모 문제가 발생할 수 있습니다. 자세한 내용은 Microsoft 개발자 센터의 HttpClient 인스턴스 만들기를 참조하세요.

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 클래스는 basket 마이크로 서비스를 사용하여 데이터 검색 및 업데이트 프로세스를 관리하는 데 사용됩니다. MauiProgram 클래스의 RegisterAppServices 메서드에서 BasketService 클래스는 종속성 주입 컨테이너를 사용하여 IBasketService 형식에 대한 형식 매핑으로 등록됩니다. 그런 다음, BasketViewModel 클래스의 인스턴스가 생성되면 그 생성자는 종속성 주입 컨테이너가 확인하는 IBasketService 형식을 수락하고 BasketService 클래스의 인스턴스를 반환합니다. 종속성 주입에 관한 자세한 내용은 종속성 주입을 참조하세요.

아래 이미지는 BasketView가 표시하는 basket 데이터를 basket 마이크로 서비스로 보내는 클래스의 상호 작용을 보여줍니다.

basket 마이크로 서비스로 데이터 보내기.

하나의 항목이 장바구니에 추가되면 BasketViewModel 클래스의 ReCalculateTotalAsync 메서드가 호출됩니다. 이 메서드는 장바구니에 있는 항목의 총계를 업데이트하고 다음 코드 예제에 설명된 대로 basket 데이터를 basket 마이크로 서비스로 보냅니다.

private async Task ReCalculateTotalAsync()
{
    // Omitted for brevity...

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

이 메서드는 종속성 주입 컨테이너에 의해 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에서 가져온 액세스 토큰은 basket 마이크로 서비스에 대한 요청에 권한을 부여해야 합니다. 권한 부여에 관한 자세한 내용은 권한 부여를 참조하세요.

다음 코드 예제에서는 RequestProvider 클래스의 PostAsync 메서드 중 하나를 보여 줍니다.

public async Task<TResult> PostAsync<TResult>(
    string uri, TResult data, string token = "", string header = "")
{
    HttpClient httpClient = GetOrCreateHttpClient(token);

    var content = new StringContent(JsonSerializer.Serialize(data));
    content.Headers.ContentType = new MediaTypeHeaderValue("application/json");
    HttpResponseMessage response = await httpClient.PostAsync(uri, content);

    await HandleResponse(response);
    TResult result = await response.Content.ReadFromJsonAsync<TResult>();
    
    return result;
}

이 메서드는 GetOrCreateHttpClient 메서드를 호출하여 적절한 헤더가 설정된 HttpClient 클래스의 인스턴스를 반환합니다. 그런 다음, 직렬화된 basket 데이터가 JSON 형식으로 전송되고 응답이 HttpResponseMessage 인스턴스에 저장되는 상태에서 URI로 식별된 리소스에 비동기 POST 요청을 제출합니다. 그런 다음, HandleResponse 메서드가 호출되며 응답에 성공 HTTP 상태 코드가 포함되지 않은 경우 예외가 발생합니다. 그런 다음, 응답이 문자열로 읽혀지고 JSON에서 개체로 CustomerBasket 변환되며 BasketService로 반환됩니다. GetOrCreateHttpClient 메서드에 관한 자세한 내용은 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 클래스의 인스턴스를 사용하여 basket 데이터를 Redis 캐시에 유지하며, 이 데이터를 성공 HTTP 상태 코드 및 JSON 형식 CustomerBasket 인스턴스를 포함하는 응답 메시지로 반환합니다.

DELETE 요청 만들기

아래 이미지는 CheckoutView에 대한 basket 마이크로 서비스에서 basket 데이터를 삭제하는 클래스의 상호 작용을 보여줍니다.

basket 마이크로 서비스에서 데이터 삭제.

체크 아웃 프로세스가 호출되면 CheckoutViewModel 클래스의 CheckoutAsync 메서드가 호출됩니다. 이 메서드는 다음 코드 예제와 같이 장바구니를 지우기 전에 새 주문을 만듭니다.

private async Task CheckoutAsync()
{
    // Omitted for brevity...

    await _basketService.ClearBasketAsync(
        _shippingAddress.Id.ToString(), authToken);
}

이 메서드는 종속성 주입 컨테이너에 의해 CheckoutViewModel에 삽입된 BasketService 인스턴스의 ClearBasketAsync 메서드를 호출합니다. 다음 메서드에서는 ClearBasketAsync 메서드를 보여줍니다.

public async Task ClearBasketAsync(string guidUser, string token)
{
    UriBuilder builder = new(GlobalSetting.Instance.BasketEndpoint);
    builder.Path = guidUser;
    string uri = builder.ToString();
    await _requestProvider.DeleteAsync(uri, token);
}

이 메서드는 요청이 전송될 리소스를 식별하는 URI를 빌드하고 RequestProvider 클래스를 사용하여 리소스에서 DELETE HTTP 메서드를 호출합니다. 인증 프로세스 중에 IdentityServer에서 가져온 액세스 토큰은 basket 마이크로 서비스에 대한 요청에 권한을 부여해야 합니다. 권한 부여에 관한 자세한 내용은 권한 부여를 참조하세요.

다음 코드 예제에서는 RequestProvider 클래스의 DeleteAsync 메서드를 보여줍니다.

public async Task DeleteAsync(string uri, string token = "")
{
    HttpClient httpClient = GetOrCreateHttpClient(token);
    await httpClient.DeleteAsync(uri);
}

이 메서드는 GetOrCreateHttpClient 메서드를 호출하여 적절한 헤더가 설정된 HttpClient 클래스의 인스턴스를 반환합니다. 그런 다음, URI로 식별된 리소스에 비동기 DELETE 요청을 제출합니다. GetOrCreateHttpClient 메서드에 관한 자세한 내용은 GET 요청 만들기를 참조하세요.

RequestProvider 클래스의 DeleteAsync 메서드가 HttpClient.DeleteAsync를 호출하면 Basket.API 프로젝트의 BasketController 클래스에 있는 Delete 메서드가 호출됩니다. 이 메서드를 보여주는 코드 예제는 다음과 같습니다.

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

이 메서드는 RedisBasketRepository 클래스의 인스턴스를 사용하여 Redis 캐시에서 장바구니 데이터를 삭제합니다.

데이터 캐싱

자주 액세스하는 데이터를 앱 가까이에 있는 빠른 스토리지에 캐싱하여 앱의 성능을 향상시킬 수 있습니다. 빠른 스토리지가 원래 원본보다 앱에 더 가까이 있는 경우 캐싱은 데이터를 검색할 때 응답 시간을 크게 향상시킬 수 있습니다.

캐싱의 가장 일반적인 형태는 앱이 캐시를 참조하여 데이터를 검색하는 read-through 캐싱입니다. 캐시에 데이터가 없으면 데이터 저장소에서 데이터가 검색된 후 캐시에 추가됩니다. 앱은 캐시 배제 패턴을 사용하여 read-through 캐싱을 구현할 수 있습니다. 이 패턴은 해당 항목이 현재 캐시에 있는지 여부를 결정합니다. 캐시에 해당 항목이 없으면 데이터 저장소의 데이터를 읽은 후 캐시에 추가합니다. 자세한 내용은 Microsoft Docs의 Cache-Aside(캐시 배제) 패턴을 참조하세요.

자주 읽으면서도 자주 변경되지 않는 데이터를 캐시합니다.

이 데이터는 앱에서 맨 처음 검색되었을 때 필요에 따라 캐시에 추가할 수 있습니다. 즉, 앱이 데이터 저장소에서 한 번만 데이터를 가져와야 하며 이후 액세스는 캐시를 사용하여 충족할 수 있습니다.

eShop 참조 애플리케이션과 같은 분산 애플리케이션은 다음 캐시 중 하나 또는 둘 다를 제공해야 합니다.

  • 여러 프로세스 또는 컴퓨터에서 액세스할 수 있는 공유 캐시입니다.
  • 앱을 실행하는 디바이스에서 데이터가 로컬에 보관되는 프라이빗 캐시입니다.

eShop 다중 플랫폼 앱은 프라이빗 캐시를 사용합니다. 여기서 데이터는 앱의 인스턴스를 실행하는 디바이스에서 로컬로 유지됩니다.

캐시는 언제든지 사라질 수 있는 임시 데이터 저장소로 간주하세요.

데이터가 원래 데이터 저장소와 캐시에서 유지 관리되는지 확인합니다. 캐시를 사용할 수 없게 되면 데이터 손실 가능성이 최소화됩니다.

데이터 만료 관리

캐시된 데이터가 항상 원래 데이터와 일치할 것으로 예상하는 것은 비현실적입니다. 원래 데이터 저장소의 데이터는 캐시된 이후 변경될 수 있으며 이는 캐시된 데이터의 생산성을 떨어뜨리게 됩니다. 따라서 앱은 캐시에 보관된 데이터를 최신 상태로 유지할 수 있도록 지원하는 전략을 구현하는 동시에, 캐시에 보관된 오래된 데이터로 인해 발생하는 상황을 감지하고 처리할 수 있어야 합니다. 대부분의 캐싱 메커니즘을 사용하면 데이터를 만료하도록 캐시를 구성할 수 있으며, 데이터가 만료될 수 있는 기간을 단축할 수 있습니다.

캐시를 구성할 때 기본 만료 시간을 설정합니다.

상당수의 캐시는 규정된 기간 동안 액세스하지 않은 경우 데이터를 무효화하고 캐시에서 삭제하는 만료를 구현합니다. 그러나 만료 기간을 선택할 때는 주의해야 합니다. 기간이 너무 짧으면 데이터가 너무 빨리 만료되며 캐싱의 이점이 줄어듭니다. 기간이 너무 길어지면 데이터 위험이 부실해질 수 있습니다. 따라서 만료 시간은 데이터를 사용하는 앱에 대한 액세스 패턴과 일치해야 합니다.

캐시된 데이터가 만료되면 캐시에서 데이터를 제거해야 하며 앱은 원래 데이터 저장소에서 데이터를 검색하여 캐시에 다시 배치해야 합니다.

데이터를 너무 오랜 기간동안 유지하도록 허용하면 캐시를 채우는 것도 가능합니다. 따라서 제거로 알려진 프로세스에서 일부 항목을 제거하려면 캐시에 새 항목을 추가하라는 요청이 필요할 수 있습니다. 캐싱 서비스는 대체로 가장 최근에 사용한 기준으로 데이터를 제거합니다. 그러나 가장 최근에 사용한 정책 및 선입선출(FIFO)을 포함한 그 밖의 제거 정책이 있습니다. 자세한 내용은 Microsoft Docs에 관한 캐싱 참고 자료를 참조하세요.

이미지 캐싱

eShop 다중 플랫폼 앱은 캐시되는 이점을 활용하는 원격 제품 이미지를 사용합니다. 이러한 이미지는 이미지 컨트롤에 의해 표시됩니다. .NET MAUI 이미지 컨트롤은 기본적으로 캐싱을 사용하도록 설정된 다운로드한 이미지의 캐싱을 지원하며 24시간 동안 이미지를 로컬에 저장합니다. 또한 CacheValidity 속성을 사용하여 만료 시간을 구성할 수 있습니다. 자세한 내용은 Microsoft 개발자 센터에서 다운로드한 이미지 캐싱을 참조하세요.

복원력 향상

원격 서비스 및 리소스와 통신하는 모든 앱은 일시적인 오류에 민감해야 합니다. 일시적인 오류로는 서비스의 순간적인 네트워크 연결 끊김, 서비스의 일시적인 사용 중단, 서비스가 사용 중일 때 발생하는 시간 제한 등이 있습니다. 이러한 오류는 종종 자동으로 수정되며 적절한 지연 후에 작업이 반복되면 성공할 가능성이 높습니다.

일시적인 오류는 예측 가능한 모든 환경에서 완벽하게 테스트된 경우에도 앱의 인지된 품질에 큰 영향을 미칠 수 있습니다. 원격 서비스와 통신하는 앱이 안정적으로 작동하려면 다음 사항들을 모두 수행할 수 있어야 합니다.

  • 오류가 발생할 때 오류를 감지하고 오류가 일시적일 수 있는지 확인합니다.
  • 오류가 일시적인 것으로 확인되면 작업을 재시도하고 해당 작업의 재시도 횟수를 추적합니다.
  • 재시도 횟수, 각 시도 간 지연 및 시도 실패 후 수행할 작업을 규정하는 적절한 재시도 전략을 활용합니다.

이 일시적인 오류 처리는 재시도 패턴을 구현하는 코드에서 원격 서비스에 액세스하려는 모든 시도를 래핑하여 수행할 수 있습니다.

다시 시도 패턴

앱이 원격 서비스에 요청을 보내려 할 때 오류를 감지할 경우 다음과 같은 방법으로 오류를 처리할 수 있습니다.

  • 작업을 다시 시도합니다. 앱에서 실패한 요청을 즉시 다시 시도할 수 있습니다.
  • 지연 후 작업을 다시 시도합니다. 앱은 요청을 다시 시도하기 전에 적절한 시간 동안 대기해야 합니다.
  • 작업을 취소합니다. 애플리케이션은 작업을 취소하고 예외를 보고해야 합니다.

재시도 전략은 앱의 비즈니스 요구 사항에 맞게 조정해야 합니다. 예를 들어, 시도 중인 작업에 맞게 재시도 횟수 및 재시도 간격을 최적화하는 것이 중요합니다. 작업이 사용자 상호 작용의 일부에 속하는 경우, 재시도 간격은 짧아야 하며 사용자가 응답을 기다리지 않도록 몇 번의 재시도만 해야 합니다. 작업이 장기 실행 워크플로의 일부에 속하며 워크플로를 취소하거나 다시 시작하는 데 비용이 많이 들거나 시간이 많이 소요되는 경우, 시도 간 대기 시간을 더 길게 유지하고 재시도 횟수를 늘리는 것이 적절합니다.

참고

시도간 지연 시간을 최소화한 적극적인 재시도 전략과 많은 재시도 횟수는 수용작업량에 근접하거나 수용작업량으로 실행 중인 원격 서비스의 질을 저하시킬 수 있습니다. 또한 계속해서 실패한 작업을 수행하려는 경우, 이러한 재시도 전략은 앱의 응답성에 영향을 미칠 수도 있습니다.

수많은 재시도 후에도 요청이 계속 실패하는 경우, 앱에서 후속 요청이 동일한 리소스로 가지 않도록 하고 이러한 실패를 보고하는 것이 좋습니다. 그런 다음, 설정된 기간이 지나면 앱이 리소스에 대해 하나 이상의 요청을 수행하여 요청이 성공했는지 확인할 수 있습니다. 자세한 내용은 회로 차단기 패턴을 참조하세요.

무한 재시도 메커니즘을 구현하지 않습니다. 그 대신 지수 백오프를 선호합니다.

한정된 수의 재시도를 적용하거나 회로 차단기 패턴을 구현하여 서비스에서 복구할 수 있도록 합니다.

eShop 참조 애플리케이션은 재시도 패턴을 구현합니다.

재시도 패턴에 관한 자세한 내용은 Microsoft Docs 재시도 패턴을 참조하세요.

회로 차단기 패턴

경우에 따라서는 수정하는 데 시간이 더 오래 걸리는 예상 이벤트로 인해 오류가 발생할 수 있습니다. 이러한 오류는 부분적 연결 손실에서부터 전체 서비스 오류에 이르기까지 다양하게 나타날 수 있습니다. 이러한 상황에서 성공할 가능성이 없는 작업을 앱이 다시 시도하는 것은 무의미하며, 그 대신 작업이 실패했음을 받아들이고 그에 따라 이 오류를 처리해야 합니다.

회로 차단기 패턴은 오류가 해결되었는지 여부를 앱이 감지하는 동시에 실패할 가능성이 있는 작업을 앱에서 반복적으로 실행하는 것을 방지할 수 있습니다.

참고

회로 차단기 패턴의 목적은 재시도 패턴과는 다릅니다. 재시도 패턴을 사용하면 앱에서 작업이 성공한다는 기대로 작업을 다시 시도할 수 있습니다. 회로 차단기 패턴은 실패할 가능성이 있는 작업을 앱이 수행하지 않도록 합니다.

회로 차단기는 실패할 수 있는 작업에서 프록시 역할을 합니다. 프록시는 발생한 최근 오류 수를 모니터링하거나, 이 정보를 사용하여 작업을 진행하도록 허용할지를 결정하거나 예외를 즉시 반환해야 합니다.

eShop 다중 플랫폼 앱은 현재 회로 차단기 패턴을 구현하지 않습니다. 그러나 eShop은 구현합니다.

재시도 및 회로 차단기 패턴을 결합합니다.

앱은 재시도 패턴을 사용하여 회로 차단기를 통해 작업을 호출하여 재시도 및 회로 차단기 패턴을 결합할 수 있습니다. 그러나 재시도 논리는 회로 차단기에서 반환되는 모든 예외에 민감하며, 회로 차단기에서 오류가 일시적임을 나타내는 경우 재시도를 중단합니다.

회로 차단기 패턴에 관한 자세한 내용은 Microsoft Docs 회로 차단기 패턴을 참조하세요.

요약

많은 최신 웹 기반 솔루션은 웹 서버에서 호스트되는 웹 서비스를 사용하여 원격 클라이언트 애플리케이션의 기능을 제공합니다. 웹 서비스가 노출하는 작업은 웹 API를 구성하며, 클라이언트 앱은 API가 노출하는 데이터 또는 작업이 구현되는 방식을 모르는 상태에서 웹 API를 활용할 수 있어야 합니다.

자주 액세스하는 데이터를 앱 가까이에 있는 빠른 스토리지에 캐싱하여 앱의 성능을 향상시킬 수 있습니다. 앱은 캐시 배제 패턴을 사용하여 read-through 캐싱을 구현할 수 있습니다. 이 패턴은 해당 항목이 현재 캐시에 있는지 여부를 결정합니다. 캐시에 해당 항목이 없으면 데이터 저장소의 데이터를 읽은 후 캐시에 추가합니다.

웹 API와 통신할 때 앱은 일시적인 오류에 민감해야 합니다. 일시적인 오류로는 서비스의 순간적인 네트워크 연결 끊김, 서비스의 일시적인 사용 중단, 서비스가 사용 중일 때 발생하는 시간 제한 등이 있습니다. 이러한 오류는 종종 자동으로 수정되며 적절한 지연 후에 작업이 반복되면 성공할 가능성이 높습니다. 따라서 앱은 일시적인 오류 처리 메커니즘을 구현하는 코드에서 웹 API에 액세스하려는 모든 시도를 래핑해야 합니다.