StreamJsonRpc에 RPC 인터페이스에 대해 문서화된 일반적인 지침 및 제한 사항을 따릅니다.
또한 다음 지침은 조정된 서비스에 적용됩니다.
메서드 서명
모든 메서드는 CancellationToken 매개 변수를 마지막 매개 변수로 사용해야 합니다. 일반적으로 이 매개 변수는 선택적 매개 변수가 아니어야 호출자가 실수로 인수를 생략할 가능성이 적습니다. 메서드의 구현이 간단해야 하는 경우에도 CancellationToken 제공하면 클라이언트가 서버로 전송되기 전에 자체 요청을 취소할 수 있습니다. 또한 나중에 취소를 옵션으로 추가하도록 메서드를 업데이트하지 않고도 서버의 구현이 더 비싼 것으로 발전할 수 있습니다.
RPC 인터페이스에서 동일한 메서드의 여러 오버로드를 피하는 것이 좋습니다. 오버로드 해석은 보통 제대로 작동하며(작동 여부를 검증하기 위한 테스트가 작성되어야 함), 각 오버로드의 매개 변수 유형에 따라 가 인수를 역직렬화할 것을 시도하므로, 오버로드를 선택하는 과정에서 처음 기회 예외가 일반적으로 throw됩니다. 성공 경로에서 발생하는 첫 기회 예외의 수를 최소화하기 위해, 주어진 이름의 메서드를 하나만 사용하는 것이 선호됩니다.
매개 변수 및 반환 형식
RPC를 통해 교환되는 모든 인수 및 반환 값은 데이터입니다. 모두 직렬화되어 유선으로 전송됩니다. 이러한 데이터 형식에서 정의하는 모든 메서드는 데이터의 로컬 복사본에서만 작동하며 데이터를 생성한 RPC 서비스와 다시 통신할 방법이 없습니다. 이 serialization 동작의 유일한 예외는 StreamJsonRpc가 특별히 지원하는 특수한 형식입니다.
ValueTask<T>가 더 적은 할당을 발생시키기 때문에 메서드의 반환 형식으로 Task<T>보다 ValueTask<T>을 사용하는 것을 고려하세요.
제네릭이 아닌 다양성(예: Task 및 ValueTask)을 사용하는 경우 덜 중요하지만 ValueTask 여전히 바람직할 수 있습니다.
해당 API에 설명된 대로 ValueTask<T> 대한 사용 제한 사항에 유의하세요. 이 블로그 게시물 및 비디오 사용할 유형을 결정하는 데 도움이 될 수 있습니다.
사용자 지정 데이터 형식
모든 데이터 형식을 변경할 수 없도록 정의하는 것이 좋습니다. 이렇게 하면 복사하지 않고도 프로세스 간에 데이터를 더 안전하게 공유할 수 있으며, 다른 RPC를 배치하지 않고는 쿼리에 대한 응답으로 받은 데이터를 변경할 수 없다는 소비자의 생각을 강화할 수 있습니다.
Newtonsoft.Json을 사용할 때 class를 이용하면 struct 대신 ServiceJsonRpcDescriptor.Formatters.UTF8로 데이터 형식을 정의하십시오. 이렇게 하면 (잠재적으로 반복적인) boxing 비용을 피할 수 있습니다.
boxing은ServiceJsonRpcDescriptor.Formatters.MessagePack 사용할 때 발생하지 않으므로 해당 포맷터를 고수할 경우 구조체가 적합한 옵션이 될 수 있습니다.
IEquatable<T> 구현하고 데이터 형식에서 GetHashCode() 및 Equals(Object) 메서드를 재정의하여 클라이언트가 다른 시간에 받은 데이터와 같은지 여부에 따라 수신된 데이터를 효율적으로 저장, 비교 및 재사용할 수 있습니다.
DiscriminatedTypeJsonConverter<TBase> 사용하여 JSON을 사용하여 다형 형식 직렬화를 지원합니다.
수집품
잠재적으로 더 효율적인 역직렬화를 허용하는 구체적인 형식(예: IReadOnlyList<T> 또는 List<T>)이 아닌 RPC 메서드 서명(예: T[])에서 읽기 전용 컬렉션 인터페이스를 사용합니다.
IEnumerable<T>를 피하세요.
Count 속성이 없으면 비효율적인 코드가 발생하며 RPC 시나리오에 적용되지 않는 데이터 생성이 지연되는 것을 의미합니다.
순서가 지정되지 않은 컬렉션에 IReadOnlyCollection<T> 사용하거나 순서가 지정된 컬렉션에 IReadOnlyList<T> 대신 사용합니다.
IAsyncEnumerable<T>을 고려하십시오. 다른 모든 컬렉션 유형 또는 IEnumerable<T> 전체 컬렉션이 하나의 메시지로 전송됩니다. IAsyncEnumerable<T> 사용하면 작은 초기 메시지가 허용되고 수신자가 컬렉션에서 원하는 만큼 항목을 가져오고 비동기적으로 열거할 수 있는 수단을 제공합니다. 이 새로운 패턴대해 자세히 알아보세요.
관찰자 패턴
인터페이스에서 관찰자 디자인 패턴 사용하는 것이 좋습니다. 이는 클라이언트가 다음 섹션에 설명된 기존 이벤트 모델에 적용되는 많은 문제 없이 데이터를 구독하는 간단한 방법입니다.
관찰자 패턴은 다음과 같이 간단할 수 있습니다.
Task<IDisposable> SubscribeAsync(IObserver<YourDataType> observer);
위에서 사용된 IDisposable 및 IObserver<T> 형식은 StreamJsonRpc에서 이국적인 형식 중 두 가지이므로 단순한 데이터로 직렬화되지 않고 특별히 마샬링된 동작을 얻습니다.
이벤트
이벤트는 여러 가지 이유로 RPC를 통해 문제가 될 수 있으며 위에서 설명한 관찰자 패턴을 대신 권장합니다.
서비스와 클라이언트가 별도의 프로세스에 있을 때 클라이언트가 연결한 이벤트 처리기 수를 서비스에 표시하지 않습니다. JsonRpc 항상 클라이언트에 이벤트를 전파할 책임이 있는 정확히 하나의 처리기를 연결합니다. 클라이언트에는 먼 쪽에 연결된 처리기가 0개 이상 있을 수 있습니다.
대부분의 RPC 클라이언트는 처음 연결되었을 때 이벤트 처리기가 설정되어 있지 않습니다. 클라이언트가 인터페이스에서 "Subscribe*" 메서드를 호출하여 이벤트를 받을 수 있는 관심과 준비 상태를 나타낼 때까지 첫 번째 이벤트를 발생시키는 것을 방지합니다.
이벤트가 상태의 변화를 나타내는 경우(예: 컬렉션에 새 항목이 추가됨), 클라이언트가 이벤트 처리 코드만으로 '동기화'할 수 있도록 구독할 때 모든 과거 이벤트를 발생시키거나 현재의 모든 데이터를 새로운 것처럼 이벤트 인수에 설명하는 것이 좋습니다.
클라이언트가 이러한 알림을 전달하는 데 필요한 네트워크 트래픽 및 CPU를 줄이기 위해 데이터 또는 알림의 하위 집합에 관심을 표시하려는 경우 위에서 언급한 "Subscribe*" 메서드에 대한 추가 인수를 수락하는 것이 좋습니다.
변경 알림을 받기 위해 이벤트를 노출하는 경우 현재 값을 반환하는 메서드를 제공하지 않거나 클라이언트가 이벤트와 함께 사용하지 못하도록 적극적으로 권장하지 않는 것이 좋습니다. 데이터에 대한 이벤트를 구독하고 현재 값을 가져오는 메서드를 호출하는 클라이언트는 해당 값의 변경 내용과 경합을 벌이거나 변경 이벤트를 누락하거나 한 스레드의 변경 이벤트를 다른 스레드에서 가져온 값과 조정하는 방법을 모릅니다. 이 문제는 RPC를 초과할 때뿐만 아니라 모든 인터페이스에 일반적입니다.
명명 규칙
- RPC 인터페이스에서
Service접미사와 간단한I접두사를 사용합니다. - SDK의 클래스에는
Service접미사를 사용하지 마세요. 라이브러리 또는 RPC 래퍼는 "서비스"라는 용어를 사용하지 않고 정확히 수행하는 작업을 설명하는 이름을 사용해야 합니다. - 인터페이스 또는 멤버 이름에서 "remote"이라는 용어를 사용하지 않습니다. 중개 서비스는 원격 시나리오뿐만 아니라 로컬 시나리오에서도 이상적으로 적용됩니다.
버전 호환성 문제
주어진 중개 서비스가 다른 확장에 노출되거나 Live Share를 통해 노출될 때, 앞뒤 버전과 호환되기를 바랍니다. 이는 클라이언트가 서비스보다 오래되었거나 새로울 수 있으며, 해당 기능이 두 버전 중 더 적은 기능을 가진 버전과 대체로 동일해야 함을 전제해야 한다는 것을 의미합니다.
먼저, 브레이킹 체인지(breaking change) 용어를 검토해 보겠습니다.
이진 호환성이 손상되는 변경: 이전 버전의 어셈블리에 대해 컴파일된 다른 관리 코드가 런타임에 새 코드에 바인딩되지 않도록 하는 API 변경입니다. 예를 들면 다음과 같습니다.
- 기존 공용 멤버의 서명 변경
- 공용 멤버의 이름을 변경합니다.
- 공용 형식을 제거합니다.
- 형식에 추상 멤버를 추가하거나 인터페이스에 멤버를 추가합니다.
그러나 다음은 이진 손상 변경에 속하지 않습니다.
- 비추상 멤버를 클래스 또는 구조체에 추가합니다.
- 전체(추상이 아닌) 인터페이스 구현을 기존 형식에 추가합니다.
프로토콜 호환성이 손상되는 변경: 원격 당사자가 제대로 역직렬화하고 처리할 수 없도록 일부 데이터 형식 또는 RPC 메서드 호출의 직렬화된 형식 변경입니다. 예를 들면 다음과 같습니다.
- RPC 메서드에 필수 매개 변수 추가
- 이전에 null이 아닌 것으로 보장된 데이터 형식에서 멤버를 제거합니다.
- 메서드 호출을 다른 기존 작업 앞에 배치해야 한다는 요구 사항을 추가합니다.
- 해당 멤버에 있는 데이터의 직렬화된 이름을 제어하는 필드 또는 속성에서 특성을 추가, 제거 또는 변경합니다.
- (MessagePack): 기존 멤버의 DataMemberAttribute.Order 속성 또는
KeyAttribute정수 변경
그러나 다음은 프로토콜을 손상시키지 않는 의 변경입니다.
- 데이터 형식에 선택적 멤버 추가
- RPC 인터페이스에 멤버 추가
- 기존 메서드에 선택적 매개 변수 추가
- 정수 또는 부동 소수점 형식을 더 긴 길이 또는 높은 정밀도를 가진 매개 변수 형식으로 변경합니다(예:
int에서long로 또는float에서double로). - 매개 변수 이름을 변경합니다. 기술적으로는 JSON-RPC이라는 명명된 인수를 사용하는 클라이언트가 영향을 받을 수 있지만, ServiceJsonRpcDescriptor을 사용하는 클라이언트는 기본적으로 위치 인수를 사용하기 때문에 매개변수 이름의 변경에 영향을 받지 않습니다. 이는 클라이언트 소스 코드가 명명된 인수 구문을 사용하는지 여부와는 아무런 관련이 없으며, 매개 변수 이름 변경은 소스에 영향을 주는 변경이 될 수 있습니다.
동작 중단 변경: 이전 클라이언트가 제대로 작동하지 않을 수 있도록 동작을 추가하거나 변경하는 중재된 서비스의 구현 변경입니다. 예를 들면 다음과 같습니다.
- 이전에 항상 초기화되었던 데이터 형식의 멤버를 더 이상 초기화하지 않습니다.
- 이전에 성공적으로 완료될 수 있었던 조건에서 예외를 발생시킵니다.
- 이전에 반환된 것과 다른 오류 코드를 사용하여 오류를 반환합니다.
그러나 다음은 행동을 중단시킬 수 없는 변경 사항에 해당하지 않습니다.
- 새 예외 유형을 던집니다(어차피 모든 예외는 RemoteInvocationException으로 래핑되므로).
호환성을 깨는 변경이 필요한 경우 새 서비스 이름을 등록하고 제공하여 안전하게 변경할 수 있습니다. 이 모니커는 동일한 이름을 공유할 수 있지만 버전 번호는 더 높습니다. 이진 호환성이 손상되는 변경이 없는 경우 원래 RPC 인터페이스 다시 사용할 수 있습니다. 그렇지 않으면 새 서비스 버전에 대한 새 인터페이스를 정의합니다. 이전 사용자에게 영향을 끼치지 않도록 이전 버전을 계속 등록, 제공 및 지원합니다.
RPC 인터페이스에 멤버를 추가하는 것을 제외하고 이러한 모든 호환성이 손상되는 변경을 방지하려고 합니다.
RPC 인터페이스에 멤버 추가
많은 클라이언트가 않습니다. RPC 클라이언트 콜백 대상에서 호출할 멤버를 추가해야 하는 경우 새 인터페이스(원래에서 파생될 수 있음)를 정의한 다음, 증분된 버전 번호로 조정된 서비스를 프로파일링하기 위한 표준 프로세스를 따르고 업데이트된 클라이언트 인터페이스 형식이 지정된 설명자를 제공합니다.
RPC 인터페이스에 조정된 서비스를 정의하는 멤버를 추가할 수 있습니다. 이는 프로토콜 호환성이 손상되는 변경이 아니며 서비스를 구현하는 사용자에 대한 이진 호환성이 손상되는 변경일 뿐이지만 새 멤버를 구현하도록 서비스를 업데이트할 수 있습니다. 지침에 조정된 서비스 자체를 제외하고 아무도 RPC 인터페이스를 구현하지 않아야 하며 테스트는 모의 프레임워크를 사용해야 하며, RPC 인터페이스에 멤버를 추가해도 누구도 중단되지 않아야 합니다.
이러한 새 멤버에는 해당 멤버를 처음 추가한 서비스 버전을 식별하는 xml 문서 주석이 있어야 합니다. 새 클라이언트가 메서드를 구현하지 않는 이전 서비스에서 메서드를 호출하는 경우, 해당 클라이언트는 RemoteMethodNotFoundException을 잡을 수 있습니다. 그러나 해당 클라이언트는 오류를 예측하고 처음부터 호출을 피할 수 있습니다. 기존 서비스에 멤버를 추가하는 모범 사례는 다음과 같습니다.
- 서비스 릴리스 내의 첫 번째 변경 사항인 경우: 멤버를 추가하고 새 설명자를 선언할 때 서비스 이름의 부 버전을 올리십시오.
- 새 버전 을 등록하고 제공할 수 있도록 서비스를 업데이트하며, 기존 버전을 계속 사용할 수 있습니다.
- 조정된 서비스의 클라이언트가 있는 경우 클라이언트를 업데이트하여 최신 버전을 요청하고, 최신 버전이 null로 돌아오는 경우 이전 버전을 요청하도록 대체합니다.