API 구현

이 문서는 Azure SDK 클라이언트 라이브러리를 구현하기 위한 가이드라인입니다. 이 가이드라인의 일부는 코드 생성 도구에 의해 자동으로 적용된 점을 유의하시기 바랍니다.

⛔️ DO NOT 구현 코드(즉, 공개 API의 일부를 구성하지 않은 코드)를 공개 API로 오인하지 않도록 하십시오. 구현 코드에는 두 가지 유효한 약정이 있는데, 우선 순위는 다음과 같습니다:

  1. 구현 클래스를 패키지-프라이빗으로 만들고, 이를 사용하는 클래스와 같은 패키지 내에 배치할 수 있습니다.
  2. 구현 클래스를 implementation으로 명명된 서브패키지 내에 배치할 수 있습니다.

CheckStyle 검사는 implementation 패키지 내의 클래스가 공개 API를 통해 노출되지 않도록 확인합니다. 하지만 우선 API를 공개 API로 구현하지 않는 것이 좋으므로, 가능하면 패키지-프라이빗을 적용하는 것이 더 낫습니다.

서비스 클라이언트

비동기 서비스 클라이언트

⛔️ DO NOT 비동기 클라이언트 라이브러리 내에 동기 함수 호출을 포함하지 마십시오. 비동기 API 안의 동기 함수 호출을 감지하기 위해 BlockHound를 사용하십시오.

주석

서비스 클라이언트 클래스에는 다음의 주석을 포함하십시오. 예를 들어, 이 코드에서는 두 주석을 사용하는 예시 클래스를 볼 수 있습니다:

@ServiceClient(builder = ConfigurationAsyncClientBuilder.class, isAsync = true, service = ConfigurationService.class)
public final class ConfigurationAsyncClient {

    @ServiceMethod(returns = ReturnType.COLLECTION)
    public Mono<Response<ConfigurationSetting>> addSetting(String key, String value) {
        ...
    }
}
주석 위치 설명
@ServiceClient 서비스 클라이언트 서비스 클라이언트를 인스턴스화하는 빌더, API가 비동기인지 여부, 서비스 인터페이스(@ServiceInterface 주석이 있는 인터페이스)에 대한 참조를 명시합니다.
@ServiceMethod 서비스 메서드 클라이언트 클래스인지 여부에 관계 없이, 네트워크 작업을 수행하는 모든 메서드에 명시합니다.

서비스 클라이언트 빌더

주석

@ServiceClientBuilder 주석은 서비스 클라이언트 인스턴스화를 담당하는 클래스에 반드시 명시되어야 합니다. (즉, @ServiceClient 주석이 적용된 클래스를 인스턴스화하는 클래스에 배치되어야 합니다.) 예시는 다음과 같습니다:

@ServiceClientBuilder(serviceClients = {ConfigurationClient.class, ConfigurationAsyncClient.class})
public final class ConfigurationClientBuilder { ... }

위의 빌더는 ConfigurationClientConfigurationAsyncClient의 인스턴스를 작성할 수 있다고 명시합니다.

지원 타입

모델 타입

주석

조건에 해당하는 경우, 모델 클래스에 적용해야 하는 두 가지 주석이 있습니다:

  • @Fluent 주석은 최종 사용자에게 Fluent API를 제공할 것으로 예상되는 모든 모델 클래스에 적용됩니다.
  • @Immutable 주석은 변경할 수 없는 모든 클래스에 적용됩니다.

SDK 기능 구현

로깅

클라이언트 라이브러리는 Azure Core의 강력한 로깅 메커니즘을 사용해야 합니다. 이는 소비자가 메서드 호출의 문제를 적절하게 진단하고, 그 문제가 개발자 코드, 클라이언트 라이브러리 코드, 서비스 중 어디에서 발생했는지 확인할 수 있도록 하기 위함입니다.

DO 모든 클라이언트 라이브러리에서는 Azure Core가 제공하는 ClientLogger API를 유일한 로깅 API로 사용하십시오. 내부적으로, ClientLoggerSLF4J를 감싸고 있으므로, SLF4J를 통해 제공되는 모든 외부 구성이 유효합니다. 최종 사용자에게 SLF4J 구성을 노출하기를 권장합니다. 자세한 내용은 SLF4J user manual을 참조하십시오.

DO 모든 관련 클래스에 ClientLogger 인스턴스를 생성하십시오. 단, 성능이 매우 중요하거나, 인스턴스의 수명이 짧아 고유한 로거를 사용하기에 비용이 과하거나, 클래스 인스턴스화가 허용되지 않는 static-only 클래스는 예외입니다. 이러한 예외의 경우, 공유 (또는 static) 로거 인스턴스가 허용됩니다. 예를 들어, 아래 코드는 ConfigurationAsyncClient에 대한 ClientLogger 인스턴스를 생성합니다:

public final class ConfigurationAsyncClient {
    private final ClientLogger logger = new ClientLogger(ConfigurationAsyncClient.class);

    // example async call to a service that uses the Project Reactor APIs to log request, success, and error
    // information out to the service logger instance
    public Mono<Response<ConfigurationSetting>> setSetting(ConfigurationSetting setting) {
        return service.setKey(serviceEndpoint, setting.key(), setting.label(), setting, getETagValue(setting.etag()), null)
            .doOnRequest(ignoredValue -> logger.info("Setting ConfigurationSetting - {}", setting))
            .doOnSuccess(response -> logger.info("Set ConfigurationSetting - {}", response.value()))
            .doOnError(error -> logger.warning("Failed to set ConfigurationSetting - {}", setting, error));
    }
}

Static 로거는 JVM 인스턴스에서 실행되는 모든 클라이언트 라이브러리 인스턴스 간에 공유된다는 점을 참고하십시오. Static 로거는 인스턴스 수명이 짧은 경우에만 신중히 사용해야 합니다.

DO 로그를 내보낼 때는 다음 로그 레벨 중 하나를 사용하십시오: Verbose(상세 정보), Informational(발생한 상황), Warning(문제일 수 있는 상황), Error.

DO Error 레벨은 응용 프로그램이 복구될 가능성이 거의 없는 오류(메모리 부족 등)에 사용하십시오.

DO Warning 레벨은 함수가 의도한 작업을 수행하지 못한 경우 사용하십시오. 이는 일반적으로 함수가 예외를 발생시킨다는 것을 의미합니다. 스스로 복구하는 이벤트 발생(예를 들어, 요청이 자동으로 재시도되는 경우)은 포함하지 마십시오.

✔️ YOU MAY 요청/응답 주기(응답 본문 시작까지)가 서비스 정의 임계계 값을 초과할 때, Warning 수준에서 요청 및 응답(아래 참조)을 기록할 수 있습니다. 임계 값은 거짓-긍정(false-positives)을 최소화하고, 서비스 문제를 식별하기 위해 선택되어야 합니다.

DO Informational 레벨은 함수가 정상적으로 작동할 때 사용하십시오.

DO Verbose 레벨은 자세한 문제 해결 시나리오를 위해 사용하십시오. 이는 주로 개발자 혹은 시스템 관리자가 특정 오류를 진단하기 위한 것입니다.

DO 승인된 헤더와 쿼리 파라미터의 서비스 제공 “허용 목록”에 있는 헤더 및 쿼리 파라미터만 로그로 기록하십시오. 다른 모든 헤더와 쿼리 파라미터는 해당 값을 수정해야 합니다.

DO Informational 메시지로 요청 행과 헤더를 로그에 남기십시오. 로그는 다음의 정보를 포함해야 합니다:

  • HTTP 메서드
  • URL
  • 쿼리 파라미터(허용 목록에 없는 경우 수정)
  • 요청 헤더(허용 목록에 없는 경우 수정)
  • SDK가 상관 광계 목적을 위해 제공하는 요청 ID
  • 요청이 시도된 횟수

이는 기본적으로 azure-core 내에서 수행하지만, 사용자가 httpLogOptions 구성 빌더를 통해 설정할 수 있습니다.

DO Informational 메시지로 응답 행과 헤더를 로그에 남기십시오. 로그 형식은 다음과 같아야 합니다:

  • SDK에서 제공한 요청 ID(위 참조)
  • 상태 코드
  • 상태 코드와 함께 제공된 메시지
  • 응답 헤더(허용 목록에 없는 경우 수정)
  • 요청의 첫 번째 시도와 본문의 첫 번째 바이트 사이의 기간

DO 서비스 요청이 취소된 경우 Informational 로그를 기록하십시오. 로그에는 다음이 포함되어야 합니다:

  • SDK에서 제공한 요청 ID(위 참조)
  • 취소 사유(가능한 경우)

DO 예외는 Warning 레벨 메시지로 기록하십시오. 로그 레벨이 Verbose로 설정된 경우, 스택 추적 정보를 메시지에 포함하십시오.

DO 클라이언트 라이브러리 코드 내에서 발생한 모든 예외는 다음 로거 API 중 하나를 통해 발생시킵시오 - ClientLogger.logThrowableAsError(), ClientLogger.logThrowableAsWarning(), ClientLogger.logExceptionAsError() 혹은 ClientLogger.logExceptionAsWarning().

아래 예시가 있습니다:

// NO!!!!
if (priority != null && priority < 0) {
    throw new IllegalArgumentException("'priority' cannot be a negative value. Please specify a zero or positive long value.");
}

// Good

// Log any Throwable as error and throw the exception
if (!file.exists()) {
    throw logger.logThrowableAsError(new IOException("File does not exist " + file.getName()));
}

// Log any Throwable as warning and throw the exception
if (!file.exists()) {
    throw logger.logThrowableAsWarning(new IOException("File does not exist " + file.getName()));
}

// Log a RuntimeException as error and throw the exception
if (priority != null && priority < 0) {
    throw logger.logExceptionAsError(new IllegalArgumentException("'priority' cannot be a negative value. Please specify a zero or positive long value."));
}

// Log a RuntimeException as warning and throw the exception
if (numberOfAttempts < retryPolicy.getMaxRetryCount()) {
    throw logger.logExceptionAsWarning(new RetryableException("A transient error occurred. Another attempt will be made after " + retryPolicy.getDelay()));
}

분산 추적

분산 추적 메커니즘을 통해 소비자는 그들의 코드를 프론트엔드부터 백엔드까지 추적할 수 있습니다. 분산 추적 라이브러리는 고유한 작업 단위인 범위를 생성합니다. 각각의 범위는 부모-자식 관계에 있습니다. 코드 계층 구조에 더 깊이 들어갈수록 더 많은 범위가 생성됩니다. 이러한 범위는 필요에 따라 적합한 수신자로 내보내질 수 있습니다. 범위를 추적하기 위해, 분산 추적 컨텍스트(이하 컨텍스트)는 각 연속 계층으로 전달됩니다. 자세한 정보는 OpenTelemetry의 추적 항목을 참조하십시오.

Azure Core 라이브러리는 런타임 시점에 파이프라인 정책을 추가하기 위한 Service Provider Interface (SPI)를 제공합니다. 파이프라인 정책은 소비자 배포에서 추적을 활성화하는 데 사용됩니다. 분산 추적을 사용하려면 모든 클라이언트 라이브러리에서 플러그형 파이프라인 정책을 지원해야 합니다. 추가 메타데이터는 서비스별 메서드에 따라 지정하여 소비자에게 더 풍부한 추적 경험을 제공할 수 있습니다.

DO HTTP 파이프라인 인스턴스화의 일부로써 플러그형 파이프라인 정책을 지원하십시오. 이를 통해 개발자는 추적 플러그인을 포함하고, 해당 파이프라인 정책을 사용 중인 모든 클라이언트 라이브러리에 자동으로 주입할 수 있습니다.

아래의 예시 코드를 검토해보십시오. 이 코드에서는 서비스 클라이언트 빌더가 해당 정책에 HttpPipeline을 생성하고 있습니다. 동시에, 빌더는 HttpPolicyProviders.addBeforeRetryPolicies(policies)HttpPolicyProviders.addAfterRetryPolicies(policies) 행을 사용하여 플러그인이 ‘before retry’와 ‘after retry’ 정책을 추가할 수 있도록 허용합니다:

public ConfigurationAsyncClient build() {
    ...

    // Closest to API goes first, closest to wire goes last.
    final List<HttpPipelinePolicy> policies = new ArrayList<>();
    policies.add(new UserAgentPolicy(AzureConfiguration.NAME, AzureConfiguration.VERSION, buildConfiguration));
    policies.add(new RequestIdPolicy());
    policies.add(new AddHeadersPolicy(headers));
    policies.add(new AddDatePolicy());
    policies.add(new ConfigurationCredentialsPolicy(buildCredentials));
    HttpPolicyProviders.addBeforeRetryPolicies(policies);
    policies.add(retryPolicy);
    policies.addAll(this.policies);
    HttpPolicyProviders.addAfterRetryPolicies(policies);
    policies.add(new HttpLoggingPolicy(httpLogDetailLevel));

    ...
}

DO 추적 범위와 함께 제공해야 하는 추가 메타데이터를 설정하려면 Azure Core TracerProxy API를 사용하십시오. 특히 setAttribute(String key, String value, Context context) 메서드를 사용하여 추적 컨텍스트에서 새 키/값 쌍을 설정합니다.

DO 서비스 메서드 인수로 받은 Context 객체 전부를 모든 경우에 생성된 서비스 인터페이스 메서드로 전달하십시오.

테스팅

DO 사용 가능한 모든 HTTP 클라이언트와 서비스 버전을 사용하려면, 적용 가능한 모든 단위 테스트를 매개변수화하십시오. 모든 테스트의 매개변수화된 실행은 라이브 테스트의 일부로 이루어져야 합니다. Netty와 최신 서비스 버전으로 구성된 더 짧은 실행은 PR 유효성 검사가 발생할 때마다 실행할 수 있습니다.

TODO(추가 예정) refer to cases where this is done in Java today