Writing Connectors

HH·2022년 1월 9일
0

Hyperledger Caliper

목록 보기
12/12

Writing Connectors

개요


커넥터는 캘리퍼의 가장 중요한 모듈이다. 이것은 SUT와 다른 캘리퍼 컴포넌트들(예: 매니저와 워커 프로세스, 또는 워커 모듈들) 의 추상 레이어를 제공한다. 커넥터의 역할은 SUT와의 상호작용을 가능한 한 간소화하는 것이다. 이는 특질들을 API뒤에 숨김으로써 가능하다.

참고

커넥터가 캘리퍼 아키텍처에 어떻게 들어맞는지를 이해하려면 멀티 플랫폼 서포트, 매니저 프로세스, 그리고 워커 프로세스와 관련된 문서 섹션을 확인하자.

캘리퍼는 사전 정의된/내장된 커넥터들을 포함하지만, 일반적으로 커넥터들은 플러그 가능한 컴포넌트들이다(리소스와 TX 모니터, 워크로드 모듈들과 같다). 그러므로 서드파티 커넥터를 구현하고 사용할 수 있다. 하지만 새 커넥터를 구현하기 전에 이 가이드의 모든 부분을 흡수하기를 추천한다.

품질 커넥터 요구사항


커넥터의 복잡성은 일반적으로 SUT(그리고 프로그래밍 모델)의 복잡성에 비례한다. 따라서, 커넥터는 캘리퍼의 다른 확장지점에 비해 무거운 컴포넌트이다.

커넥터 구현에는 몇 가지 주의해야 할 점이 있다. 몇몇은 기술적이고, 몇몇은 사용성에 영향을 준다.

참고

캘리퍼는 테스트할 수 있는 SUT 타입을 제한하지 않는다. 따라서, 다음 포인트들은 복잡한 분산된 시스템을 대상으로 하는 커넥터를 위한 일반적인 가이드라인이다. 이 조언을 따르지 않을 수도 있지만, 그런 결정을 내릴때는 사용자를 놀라게 할 요인을 줄이기 위해 해당 선택을 문서화해야 한다.

  1. 사전 정의된 인터페이스를 유지하자.

    • 캘리퍼 모듈이 커넥터와 상호작용할 수 있도록 주어진 인터페이스들을 반드시 구현해야 한다.
    • 인터페이스 외부에 추가 기능을 노출하는 경우, 워크로드 모듈 개발자의 프로그래밍 모델을 방해하게 된다. 그들이 당신의 추가 API를 별개의 케이스로 통제해야 한다. 이는 크로스 플랫폼 벤치마크 개발을 복잡하게 한다. 하지만 특정한 SUT에 대한 성능 테스트 워크로드 모듈을 구현한다면 이 점을 고려하지 않아도 된다.
    • 당신의 커넥터가 이 가이드의 다른 커넥터와 비슷하게 동작할 경우, 사용자는 당신의 커넥터/SUT에 빠르게 적응하고 사용할 것이다.
  2. SUT의 분산 특성을 고려하자.

    • 분산 시스템은 서로 다른 규칙을 가지고 있기도 한 여러 노드로 구성된다.
    • 당신은 아마 커넥터를 단순히 단일 SUT 노드의 프록시로 사용하고 싶진 않을 것이다. 커넥터는 로드 밸런싱 또는 SUT-특정적 리퀘스트 실행 정책과 같은 부분들을 지원하는 것이 타당한 만큼 많은 SUT노드를 인식해야 한다.
    • 다른 캘리퍼 모듈들, 특히 워크로드 모듈들로부터 가능한 한 많은 네트워크 토폴로지를 숨겨라. 대부분의 경우, 에뮬레이트된 클라이언트는 기능이 실행되는 한 리퀘스트 수신 측을 신경쓰지 않는다.
    • 워크로드 모듈에 특정 노드들을 반드시 노출시켜야 할 경우, 단순한 (텍스트 기반이 좋다) 핸들을 통해 수행해라. 하지만 노드를 나타내는 구현 특정적인 클래스는 노출하지 않아야 한다.
  3. SUT 내부 행위자를 고려해라.

    • 인증과 권한 부여는 대부분 원격 시스템의 초석이다. 따라서 디지털 신원 (많은 형태로 구현될 수 있는)을 다루는 기능은 반드시 커넥터에서 최고 수준의 기능이 되어야 한다.
    • SUT의 노드들과 유사하게, 여기에는 서로 다른 리퀘스트를 시작하는 서로 다른 권한을 가진 많은 행위자와 클라이언트들이 있다. 커넥터는 워크로드 모듈에서 다양한 클라이언트 행위를 할 수 있도록 하기 위해, 다수의 클라이언트를 가장할 수 있어야 한다.
    • 커넥터는 각 리퀘스트에 대해 클라이언트 신원이 쉽게 전환될 수 있도록 해야 한다. 즉, 커넥터는 반드시 워크로드 모듈에 신원들을 노출시켜야 한다. 동일한 조언이 노드에도 적용된다. 단순한 (텍스트 기반이 좋다) 핸들을 사용하되, 신원을 가리키는 구현 특정적 클래스는 노출시키지 않아야 한다.
  4. 바퀴를 재발명하지 마라.

    • 각 시스템은 클라이언트와 커뮤니케이션하기 위한 표준 원격 API를 노출한다. 이 API들은 많은 형태를 가질 수 있다(REST, gRPC 등).
    • 사용된 API 기술에 관계 없이, 아마 그를 위한 성숙한 클라이언트 라이브러리가 있을 것이다. 혹은 더 좋게, 타겟 플랫폼이 고유한 SDK를 가지고 있을 수 있다.
    • 커넥터는 네트워크 수준 통신 및 그와 같은 저수준 디테일을 신경쓰지 않아야 한다. 이러한 작업은 SDK들이나 클라이언트 라이브러리에 위임해야 한다. 이렇게 하면 커넥터가 더 견고해지고, 사용된 라이브러리에 친숙한 추가 기여자를 더 쉽게 구할 수 있다.
  5. 병목이 되지 않도록 해라.

    • 캘리퍼의 목적은 클라이언트의 관점에서 SUT 성능을 테스트하는 것이다.
    • 리퀘스트 실행과 마찬가지로 리퀘스트 조립 및 발송에 시간이 걸린다면, 결과는 (SUT성능을) 대표하지 못할 것이다. 리퀘스트 발송은 커넥터에서 hot path로 간주된다. 그리고 이것은 가능한 한 효과적이어야 한다.
    • SDK들과 널리 알려진 클라이언트 라이브러리를 사용하는 것은 예외이다. 실제 클라이언트 사이트 어플리케이션이 동일한 작업을 할 가능성이 높으므로, 라이브러리 오버헤드가 리퀘스트 지연(latency)에 통합되어야 한다. 단순히 지연 수를 낮추기 위해 고유한 특정 목적 SDK를 작성함으로써 마이크로 분석을 시행하지 마라.
    • hot path의 커넥터 병목은 캘리퍼 워커 프로세스의 리퀘스트 출력 비율에 영향을 미치거나 그것을 제한할 것이다. 만약 SUT에 초당 100건의 리퀘스트를 보내기 위해 10 워커 프로세스를 런치해야 한다면 캘리퍼 사용자들은 행복하지 않을 것이다.

커넥터의 역할은 위 사항을 준수하면서 플랫폼을 모르는 캘리퍼 사이드 API와 고수준 SUT 특정적 클라이언트 라이브러리를 연결하는 것이다.

커넥터 구현


커넥터 구현 프로세스를 본격적인 node.js 프로젝트로 다루어야 한다. 추천 프로젝트 구조는 캘리퍼와의 통합 섹션을 참조하자. 프로젝트 구조를 잠시 제쳐두면, 네 가지 구현 관련 작업이 있다:
1. 커넥터 인터페이스 구현 (사용가능한 유틸리티 기반 클래스를 선택적으로 사용한다)
2. 커넥터 초기화를 위한 팩토리 메서드 구현
3. 네트워크 구성 파일을 위한 스키마 정의
4. 커넥터를 위한 바인딩 구성 제공

커넥터 인터페이스


프로젝트 디펜던시로 @hyperledger/caliper-core 패키지 (또는 그 특정 버전)을 추가하면 노출된 ConnectorInterface 클래스로 액세스할 수 있다. 이는 아래 인터페이스를 선언한다:

class ConnectorInterface extends EventEmitter {
    getType() {}
    getWorkerIndex() {}
    async init(workerInit) {}
    async installSmartContract() {}
    async prepareWorkerArguments(number) {}
    async getContext(roundIndex, args) {}
    async releaseContext() {}
    async sendRequests(requests) {}
}

module.exports = ConnectorInterface;

이 인터페이스는 다음 하위섹션에서 자세히 다룬다. 지금은 다음 사항만 알아두자:
1. 커넥터는 두 가지 다른 환경에서 사용된다: 매니저와 워커 프로세스다. 메서드와 연관된 환경은 인터페이스 레퍼런스 하위섹션에서 다룬다.
2. 커넥터는 반드시 리퀘스트에 대한 특정 이벤트를 노출해야 한다. 그렇지 않으면 캘리퍼 워커에 의해 탐색될 수 없어서 캘리퍼 스케줄링 메커니즘을 파괴하게 된다.
3. sendRequests는 인터페이스에 대한 hot path이다. 이것을 주의깊고 효율적으로 구현하자.
4. 커넥터의 행위 (그리고 실제로 구현하기 위한 메서드들)은 네트워크 구성 스키마의 케이퍼빌리티에 크게 의존한다. 캘리퍼 사이드 네트워크에 더 많은 유연성을 허용할수록, 더 많은 기능을 구현해야 한다. 유연한 커넥터는 벤치마크 시나리오를 설정하기 쉽고, 사용자를 행복하게 만든다.

인터페이스 레퍼런스


getType

  • 설명: 커넥터 타입에 대한 짧은 이름을 반환한다. 이 이름은 대개 SUT를 나타낸다 (예: fast-ledger). 이 이름은 여러 유형의 SUT를 대상으로 할 수 있는 워크로드 모듈에서 사용될 수 있다.
  • 리턴 타입: 문자열
  • 리턴: 커넥터 이름

getWorkerIndex

  • 설명: 커넥터를 인스턴스화하는 영 기반 워커 프로세스 인덱스를 찾는다
  • 리턴 타입: 넘버
  • 리턴: 워커 프로세스 인덱스

init

  • 설명: 이 메서드는 매니저와 (선택적으로) 워커 프로세스에 의해 호출된다. 이것은 커넥터 인스턴스와 잠재적으로 SUT의 특정 측면을 초기화한다. 초기화 작업은 커넥터 특정적이지만, 일반적으로 프로세스로 나눌 수 있다:
    • 매니저 프로세스 인스턴스는 SUT와의 상호작용을 요구하는 초기화 작업들을 한 번만(one-time initialization tasks) 수행한다. 이 작업들은 디지털 신원 생성 또는 다른 SUT 관련 하우스키핑 액션 등을 포함할 수 있다.
    • 워커 프로세스 인스턴스는 대개 로컬 하우스키핑 작업만을 수행한다. 예를 들어 필수 데이터 스트럭처 또는 나중에 필요한 캐시들을 리퀘스트를 보낼 때 생성하는 작업이 있다. 이 단계는 선택적이고, 커넥터 인스턴스 생성 후에 팩토리 메서드를 통해 수행될 수 있다. 만약 워커 프로세스가 이 단계에서 SUT에 접근할 필요가 있다면, 멱등(idempotent) 작업을 통해서(예: 구성 쿼리)이를 수행해야 한다. 이것은 임의의 숫자의 병렬적인 워커 프로세스가 정확히 실행되는 것을 보장한다.
  • 파라미터:
    • workerInit (boolean): 메서드가 워커 프로세스에 의해 호출되는지, 아니면 매니저 프로세스에 의해 호출되는지를 나타낸다.
  • 리턴 타입: 프로미스
  • 리턴: 메서드 완료(completion) 시 resolve되는 프로미스

installSmartContract

  • 설명: 원격으로 허용되는 경우, SUT에서 컨트랙트 배포를 수행하기 위해 매니저 프로세스에 의해 호출된다.
  • 리턴 타입: 프로미스
  • 리턴: 메서드 완료(completion) 시 resolve되는 프로미스

prepareWorkerArguments

  • 설명: 매니저 프로세스에 의해 호출되며, 매니저 프로세스의 커넥터 인스턴스가 워커 프로세스의 커넥터 인스턴스에 데이터를 분산시킬 수 있는지를 확인한다. 이 메서드는, 예를 들어 새롭게 생성한 디지털 신원을 매니저 프로세스 같은 것들을, 나중 사용을 위해 워커 프로세스 인스턴스로 차례차례 반환할 수 있도록, 반환하기 위한 최적의 정소이다.
  • 리턴 타입: Promise<object[]>
  • 리턴: 각 워커에 대한 커넥터 특정적 오브젝트들의 프로미스. 메서드 완료(completion) 시 resolve된다.

getContext

  • 설명: 이 메서드는 각 라운드 전에 워커 프로세스에 의해 호출되고, 커넥터 특정적 오브젝트를 어셈블하는데에 사용된다. 이 오브젝트는 해당 라운드의 워크로드 모듈에 의해 공유된다. 이 메서드는 또한 다음 라운드에 필요한 리소스(원격 로드에 대한 커넥션 생성 등)을 청구하기에 적합한 장소이다.
  • 파라미터:
    • roundIndex (number): 임박한 라운드의 영기반 인덱스
    • args (object): 매니저 인스턴스의 prepareWorkerArguments 메서드에서 워커 인스턴스에 의해 어셈블된(모아진, 조립된?) 오브젝트.
  • 리턴 타입: Promise<object>
  • 리턴: 메서드 완료(completion) 시 resolve되는 커넥터 특정적 오브젝트의 프로미스

releaseContext

  • 설명: 각 라운드 이후에 워커 프로세스에 의해 호출되는 메서드. getContext 메서드에서 청구되는 리소스들을 제공한다.
  • 리턴 타입: 프로미스
  • 리턴: 메서드 완료(completion) 시 resolve되는 프로미스

sendRequests

  • 설명: 커넥터의 hot path이다. 라운드의 워크로드 모듈에 의해 워커 프로세스에서 호출된다. 이 메서드는 반드시 하나 이상의 오브젝트를 받아야 한다. 이는 SUT로 반드시 보내져야 하는 리퀘스트(들)과 관련된 것이어야 한다. SUT 타입이 리퀘스트 배치를 지원하지 않는 한 커넥터는 리퀘스트의 실행 순서를 보존할 필요가 없다. 커넥터는 TxStatus 인스턴스를 통해 최소한 모든 리퀘스트의 시작 시간, 종료시간, 마지막 상태(성공 또는 실패)를 수집해야 한다.

  • 파라미터:

    • requests (object|object[]) : 리퀘스트(들)을 위한 하나 이상의 커넥터 특정적 설정 오브젝트
  • 리턴 타입: Promise<TxStatus|TxStatus[]>

  • 리턴: 메서드 완료(completion) 시 resolve되는, 하나 이상의 리퀘스트 실행 결과.


노출된 이벤트


커넥터는 반드시 다음 이벤트들을 정의된 상수와 매칭되는 이름으로 노출시켜야 한다. 이 이벤트들 없이는 캘리퍼의 스케줄링 메커니즘이 올바르게 작동하지 않고, 다른 컴토넌트들 (TX 모니터 등)이 의존하게 된다.

txsSubmitted

  • 설명: 하나 이상의 리퀘스트가 SUT에 실행을 위해 제출되었을 때 발생해야 한다. 일반적으로 이 이벤트는 모든 개별 리퀘스크에 대해 발생한다.
  • 파라미터
    • count (number) : 제출된 리퀘스트 수

txsFinished

  • 설명: 하나 이상의 리퀘스트가 SUT에 의해 완전히 처리되었을 때 일어난다(즉, 커넥터가 결과를 수신한다).
  • 파라미터:
    • results (TxStatus|TxStatus[]) : 커넥터에 의해 수집된 하나 이상의 리퀘스트 실행 결과

추가적 베이스 클래스


@hyperledger/caliper-core 패키지는 ConnectorBase 클래스 또한 노출시킨다. 이 클래스는 다음 ConnectorInterface 메서드를 위한 합리적인 기본 구현을 제공한다:

  • prepareWorkerArguments: 기본값으로 각 워커에 대해 빈 객체가 반환된다. 즉, 워커 프로세스 인스턴스와 아무것도 공유하지 않는다.
  • sendRequests: 단일 및 복수 리퀘스트가 워크로드 모듈에 의해 제출된 경우를 처리한다. 또한 리퀘스트 전후로 필수 이벤트를 발생시킨다. 이 메서드는 단일 리퀘스트 실행을 _sendSingleRequest에 위임한다 (아래를 보자).
  • constructor: 워커 인덱스와 SUT/커넥터 타입을 파라미터로써 요구하는 생성자를 선언한다.
  • getType: 관련된 생성자 argument에 대한 단순한 getter를 제공한다.
  • getWorkerIndex: 관련된 생성자 argument에 대한 단순한 getter를 제공한다.

커넥터를 위해 이 기본 클래스를 사용하기로 결정한 경우, 반드시 _sendSingleRequest 메서드를 구현해야 한다.

_sendSingleRequest

  • 설명: 이 메서드는 단일 리퀘스트 전송 및 처리만 처리하면 된다.
  • 파라미터:
    • request (object): 리퀘스트를 위한 커넥터 특정적 설정 객체
  • 리턴 타입: Promise<TxStatus>
  • 리턴: 메서드 완료(completion) 시 resolve되는, 리퀘스트 실행 결과 프로미스

팩토리 메서드


커넥터 구현의 진입점(entry point)은 팩토리 메서드가 될 것이다. 매니저와 워커 프로세스는 이 노출된 팩토리 메서드를 커넥터 인스턴스화를 위해 호출할 것이다 (변수명명법(casing)에 주의하자).

ConnectorFactory

  • 설명: 커넥터를 인스턴스화하고 선택적으로 그것을 초기화한다. 매니저 프로세스에서 호출되는 경우(워커 인덱스 -1로 표시된다), 매니저는 initinstallSmartContracts 호출을 처리할 것이다. 초기화는 워커 프로세스에서 선택사항이므로, 필요한 경우 팩토리 메서드가 이것을 처리해야 한다.
  • 파라미터:
    • workerIndex (number): 영기반 워커 프로세스 인덱스, 또는 매니저 프로세스의 경우 -1
  • 리턴 타입: Promise<ConnectorInterface>
  • 리턴: 메서드 완료(completion) 시 resolve되는, ConnectorInterface 인스턴스의 프로미스.

다음은 fast-ledger 커넥터를 위해 가능한 팩토리 메서드 구현이다:

    'use strict';

    const FastLedgerConnector = require('./fast-ledger-connector');
    
    async function ConnectorFactory(workerIndex) {
        const connector = new FastLedgerConnector(workerIndex, 'fast-ledger');
        
        // initialize the connector for the worker processes
        if (workerIndex >= 0) {
            await connector.init(true);
        }
        
        return connector;
    }

    module.exports.ConnectorFactory = ConnectorFactory;

네트워크 구성 파일


네트워크 구성 파일에는 커넥터가 SUT와 통신하기 위해 필요한 정보들과, 커넥터 품질 요구 사항을 충족시키기 위해 필요한 정보들이 모두 포함될 수 있다. 구성 파일은 JSON 또는 YAML 파일이 될 수 있다. YAML이 가독성 및 주석 지원 때문에 선호된다.

네트워크 구성 스키마는 반드시 필수적인(mandatory) 최상위 수준(top-level) 필드를 아래 구조로 가져야 한다:

# mandatory
caliper:
  # mandatory
  blockchain: fast-ledger
  # optional
  commands:
    start: startLedger.sh
    end: stopLedger.sh

caliper.blockchain 속성은 캘리퍼에게 테스트를 위해 어떤 커넥터를 불러와야 하는지 말해준다. 이 속성의 값은 캘리퍼를 커넥터와 통합하기위해 사용할 방법에 달려 있다.

바인딩 구성


캘리퍼의 바인딩 커맨드를 사용하면 (개발하는 동안 커넥터와 패키징되는 대신) 런타임 중에 설치될 주요 커넥터 디펜던시를 지정할 수 있다. SUT SDK들과 다른 클라이언트 라이브러리(즉, SUT와 상호작용을 가능하게 하는 라이브러리)는 대개 이 카테고리에 속한다. 이 라이브러리의 API가 서로 다른 버전에서 일관성이 있는 경우, 단일 커넥터 구현이 여러 SUT 버전을 대상으로 삼을 수 있다.

이 경우, 사용자는 관련 SUT 버전을 대상으로 하는 특정한 SDK버전을 고를 수 있어야 한다. 이것은 커넥터에 바인딩 구성 파일(JSON 또는 YAML)를 제공함으로써 가능하다.

심플 구성


일반적인 바인딩 구성 스키마는 대개 단순하다:

sut:
  fast-ledger:
    1.0: 
      packages: ['fast-ledger-sdk@1.0.0']
    1.4:
      packages: ['fast-ledger-sdk@1.4.5']
    2.0: &fast-ledger-latest
      packages: ['fast-ledger-sdk@2.0.0']
    latest: *fast-ledger-latest 

위 구성에 대한 몇몇 주의사항:
1. SUT 최상위 속성은 캘리퍼가 처리할 구성 섹션을 가리킨다. 어떤 스키마 제약 없이 이 속성 바깥에 임시 YAML 섹션을 작성할 수 있다. 이는 예를 들어 YAML 앵커와 별칭 등을 복잡한 바인딩 사양의 가독성을 높이기 위해 활용할 수 있음을 뜻한다. 예시를 곧 보게 될 것이다.
2. SUT 속성은 커넥터가 바인딩을 지원할 SUT 타입을 식별하는 키들을 가지고 있다. 예시 커넥터에서는 단일 SUT 타입(fast-ledger)를 정의했다.
3. fast-ledger 아래에 커넥터가 지원하는 몇몇 SUT 버전들을 정의할 수 있다. SUT의 시멘틱 버전과 상응하는 키를 사용하는 것을 추천한다. 사용자는 SUT 타입과 SUT 버전을 사용해 바인딩을 지정할 것이다. 예를 들어 --caliper-bind-sut fast-ledger:1.4 커맨드 라인 아규먼트를 캘리퍼에 전달할 수 있다.
4. 모든 SUT 버전은 필요한 packages를 선언해야 한다. 이것은 캘리퍼가 런타임 동안 설치해야 하는 것이다. 다른 SUT 버전들은 일반적으로 설치해야 하는 서로 다른 SDK 버전을 선언한다.
5. SUT 버전으로 1.4를 선언했지만, 캘리퍼에게 1.4.5 SDK 버전을 설치하라고 요청했다. 사용자가 SDK 버전에 대한 최신 버그 fix를 즐길 수 있도록 항상 사용 가능한 최신 패치 릴리즈에 바인딩하는 것이 좋다.
6. 많은 라이브러리 관리 시스템들(NPM이나 DockerHub과 같은)은 최신 릴리즈를 가리키기 위해 latest 태그를 제공한다. 커넥터에 그와 같은 바인딩 버전을 제공할 경우, 유저는 단순화된 --caliper-bind-sut fast-ledger 표기를 사용해 커넥터를 바인딩할 수 있다. YAML 앵커들과 별칭을 사용해 최신으로 간주되는 바인딩 버전을 쉽게 참조할 수 있다. 이를 통해 구성이 더욱 읽고 유지하기 쉬워진다.

심화된 구성


커넥터가 구현 수준에서 여러 SUT 버전을 지원한다 할지라도, 이는 모든 버전이 동일한 환경에서 지원됨을 뜻하지 않는다. 일반적인 예는 해당 SDK 패키지가 새로운 Node.js 버전에서 자동으로 빌드하지 못하는 예전 SUT 버전들을 지원하는 것이다. 바인딩 구성은 이런 패키지들의 설치를 조정할 수 있는 유연성을 제공한다.

빌트인 캘리퍼 바인딩 구성에서 가져온 다음 예시를 고려해 보자. 패브릭 커넥터의 1.1 SDK 바인딩은 특정 grpc 패키지 버전에 따라 다르다. Node.js 10부터는 해당 패키지 버전에 사용가능한 사전 빌드된 바이너리가 없다. 이 패키지는 반드시 소스로부터 컴파일되어야 하고, 이는 자동적으로 일어나지 않는다(예: 대체 메커니즘). 또한 사용되는 컴파일러는 기본적으로 엄격(strict)하므로, 여러 컴파일 오류가 발생한다.

이러한 어려움을 우회하기 위하여, 바인딩 구성 스키마는 커맨드 라인 아규먼트 및 환경 변수 (npm install로 선택되는)를 지정함으로써 설치 프로세스를 땜질할 수 있다. settings 속성 아래 설치 로직을 놓을 수 있다.

sut:
  fast-ledger:
    1.0:
      packages: ['fast-ledger-sdk@1.0.0', 'comm-lib@1.0.0']
      settings:
      # compiling older comm-lib on newer Node.js version
      - versionRegexp: '^((?!v8\.).)*$'
        env:
          CXXFLAGS: '-Wno-error=class-memaccess'
          CFLAGS: '-Wno-error=class-memaccess'
        args: '--build-from-source'

settings 속성은 일반적으로 잠재적으로 적용가능한 설정들의 배열이다. 캘리퍼는 이들을 순서대로 처리하고, 정규 표현식(versionRegexp)이 사용하는 Node.js 버전과 일치하는 첫 설정 객체를 선택할 것이다. 이 예시에서는 더 최신 버전인 Node.js가 사용될 경우(즉, 버전이 v.8.x가 아닐 경우) 설정이 적용된다. 이 케이스에서는 (args에 지정된) 커맨드 라인 아규먼트가 npm install로 전달된다. 또한 env 아래 지정된 환경변수들 (npm install와 실행될 서브커맨드에 의해 선택되는) 을 설정한다.

커넥터는 여러 환경에서 넓은 범위의 SUT/SKD 버전에 대한 서포트를 제공함으로써 이와 같은 심화된 사양을 사용할 수 있다.

커넥터 문서화


커넥터에 대한 적절한 사용자 메뉴얼을 제공하는 것은 퀄리티 있는 구현을 제공하는 것 만큼 중요하다. 그렇지 않으면 유저는 커넥터와의 상호작용에 어려움을 겪게 된다. 예시로 패브릭 커넥터 문서를 섹션별로 살펴볼 것이다.

문서화 개요


커넥터에 대한 짧은 요약을 제공해야 한다. 이는 다음을 포함해야 한다:

  • 지원되는 SUT 타입 및 버전들
  • 커넥터의 케이퍼빌리티(지원되는 SUT 기능들 및 제한사항들)

개요는 사용자가 커넥터에서 기대할 수 있는 것이 무엇인지에 대한 기본적인 설명을 제공한다.

디펜던시 설치


커넥터가 여러 SUT 버전을 바인딩 프로세스를 통해 지원할 경우, 특정 버전에 바인딩하는 필수 단계들을 문서화해야 한다. 바인딩 프로세스는 모든 커넥터에 공통적이므로, 짧은 예제로 충분하다.

하지만 모든 SUT 기능들이 모든 바인딩에서 지원되지 않을 수 있다. 바인딩들의 제한사항을 주의해서 문서화하고, 가능하다면 해결방안을 제공한다.

런타임 설정


네트워크 구성 파일은 SUT 토폴로지 및 관련 아티팩트들을 선언한다. SUT를 모르는(SUT-불가지론적인) 디자인 선택은 커넥터 개발 동안 여전히 일어날 수 있다. 스스로 결정하는 대신, 이와 같은 선택을 캘리퍼의 런타임 구성 메커니즘을 활용해 최종 유저에게 위임해야 한다.

이러한 설정은 일반적으로 커넥터의 운영 모드에 영향을 미친다. 하지만 SUT상호작용의 전반적인 시멘틱은 변화하지 않는다. 커넥터에 대한 모든 사용가능한 런타임 설정을 문서화했는지 확인하자. 또, 이와 같은 설정에 대한 합리적인 기본값을 제공하는 것을 잊지 말자.

리퀘스트 API


커넥터의 주 사용자는 워크로드 모듈 개발자일 것이다. 그들은 주로 sendRequests 메서드를 통해 커넥터와 상호작용할 것이다. 메서드는 사용자가 보내고 싶어하는 리퀘스트와 관련된 단일, 또는 복수의 설정 오브젝트를 허옹한다. 리퀘스트에 어떤 종류의 설정이 사용가능한지 정확히 지정해야 한다. 일반적으로 이는 아래를 포함한다:

  • SUT에서 실행할 작업
  • 작업의 아규먼트
  • 리퀘스트를 제출해야 하는 신원
  • 리퀘스트를 보낼 노드(들)
  • 읽기 전용/쓰기 리퀘스트 간 구분

수집된 리퀘스트 데이터


커넥터는 반드시 기본 실행 데이터를 캘리퍼에 보고해 정확한 리포팅을 보장해야 한다. 하지만 접근해야 하는 어떤 클라이언트 사이드 데이터이든 수집할 수 있다. 어떤 데이터가 유저에게 유용하게 될지 모른다. 수집된 데이터들을 문서화해야 한다(시멘틱들과 데이터 타입 모두).

네트워크 구성 파일


문서에서 아마도 가장 중요한 부분은 커넥터가 처리할 수 있는 네트워크 구성 파일 스키마이다. 네트워크 토폴로지, 참가자 및 필수 아티팩트를 정의하는 직관적인 구조를 제공해라. 서로 다른 설정들에 대한 시멘틱 및 데이터 타입을 문서화해야 한다. 여러 속성(상호 배제, 유효한 값 등) 간에 발생할 수 있는 모든 제약조건을 문서화해야 한다.

예시 네트워크 구성


완전히 지정되고 기능하는 네트워크 구성 예시를 제공해야 한다. 어떤 사람들에게는 참조 형식의 문서보다 구체적인 예를 흡수하는 것이 더 쉽다.

캘리퍼와의 통합


커넥터를 구현하고 나면, 그것을 캘리퍼와 통합하기 위한 두 가지 선택이 있다:
1. 그것을 벤치마크 프로젝트의 일부인 서드파티, 플러그 가능한 컴포넌트로 사용한다.
2. 공식 캘리퍼 코드베이스에 커넥터를 제공한다. 그래서 그것이 캘리퍼와 항상 함께 설치되도록 한다.

서드파티 커넥터


캘리퍼 코드 베이스의 일부가 되지 않고도 쉽게 커넥터를 동적으로 플러그인 할 수 있다. 그 프로세스는 다음과 같다:
1. 프로젝트에 index.js 파일을 생성해 커넥터 팩토리를 노출시킨다. 이 파일은 커넥터에 대한 클린 엔트리 포인트를 제공한다:

'use strict';
module.exports.ConnectorFactory = require('./lib/connectorFactory').ConnectorFactory;
  1. 네트워크 구성 파일에서 caliper.blockchain 속성에 대한 ./fast-ledger/index.js 경로를 설정한다. 이 경로는 캘리퍼 워크스페이스 디렉토리에 대한 상대경로가 되거나, 절대경로여야 한다(휴대성 이유로 추천하지 않는다). 캘리퍼는 모듈 및 팩토리 메서드를 경로에서 로드한다.
  2. 여러 바인딩을 지원할 경우, 커넥터에 대한 바인딩 구성 파일을 준비한다.
  3. 캘리퍼를 실행하면 커넥터 구성이 네트워크 구성 파일을 통해 선택될 것이다.
  4. 커스텀 바인딩 구성을 지정할 수 있다. 예를 들어 커스텀 파일을 가리키는 --caliper-bind-file ./fast-ledger-binding.yaml 커맨드라인 아규먼트를 사용할 수 있다. --caliper-bind-sut fast-ledger:1.0로 바인딩을 지정하는 것도 잊지 말아야 한다.

대안으로, 커넥터를 퍼블리시한 경우 NPM 패키지 이름으로 caliper.blockchain 속성을 지정할 수 있다. 이 경우, 벤치마크 러닝 이전에 패키지가 캘리퍼 워크스페이스 디렉토리에 설치되었는지 반드시 확인해야 한다. 패키지에 대한 추천하는 네이밍 컨벤션은 caliper-sut이다. 예시에서 caliper.blockchain 속성은 caliper-fast-ledger로 지정된다.

참고

캘리퍼가 첫 메이저 버전에 도달하기 전 까지, 의존하는 @hyperledger/caliper-core 버전에 기반해 커넥터 패키지 버전을 지정하는 것을 추천한다.

빌트인 커넥터


커넥터를 코드베이스에 기부함으로써, 필요한 경우 커넥터를 유지할 책임을 가진다. 그렇지 않으면 향후 릴리즈에서 deprecated되고 더 이상 사용되지 않을 수 있다.

커넥터를 더 넓은 유저베이스에 노출시키고 싶다면 코드를 공식 캘리퍼 저장소에 기부할 수 있다. 그러면 커넥터가 빌트인 모듈이 되고, 캘리퍼를 설치한 사람이 즉시 사용할 수 있다.

참고

통합을 도와줄 Rocket.Chat (#caliper-contributors channel) 프로젝트 관리자에게 주저하지 말고 연락하자.

통합은 아래 단계를 따른다(예시로 caliper-ethereum 커넥터를 보자):
1. 리포지토리의 packages 디렉토리에 caliper-fast-ledger 디렉토리를 만든다. 이것이 당신의 커넥터 구현을 포함하게 된다.
2. 자체 package.json 파일에 메타데이터를 업데이트한다. 패키지 이름은 @hyperledger/caliper-fast-ledger와 같이 스코프되어야 한다.
3. 커넥터가 바인딩을 제공하는 경우, devDependencies 섹션에 동적 패키지를 나열해야 한다. 그래서 캘리퍼와 함께 자동으로 설치되지 않도록 (유저가 리바인드할 것이므로) 해야 한다. 또한, 커넥터의 바인딩 사양을 빌트인 바인딩 구성 파일에 추가해야 한다.
4. 루트 lerna.json 파일의 packages 섹션에 새 디렉토리 경로를 추가한다. 이는 당신의 패키지가 다른 개발자들을 위해 제대로 부트스트랩 될 것임을 보장한다.
5. 캘리퍼 CLI 디펜던시에 당신의 새 패키지를 (이름으로) 추가한다.
6. caliper-utils.js 모듈의 BuiltinConnectors 변수 아래에 빌트인 커넥터로서 당신의 커넥터를 나열한다:

const BuiltinConnectors = new Map([
 ['fast-ledger', '@hyperledger/caliper-fast-ledger'],
 // other connectors...
]);
  1. 커넥터에 대한 통합 테스트를 제공하는 것을 강력히 추천한다.
  2. 모든 코드 관련 아티팩트들(대개 .js, .yaml, .md 파일들)이 적절한 라이선스 헤더를 가지고 있는지 확인한다.
  3. 완료되었다. 이제 사용자들이 그들의 구성 파일에서 fast-ledger로 커넥터를 참조할 수 있다. 커넥터 패키지는 모든 머지된 PR마다 자동으로 퍼블리시될 것이다.

라이선스

The Caliper codebase is released under the Apache 2.0 license. Any documentation developed by the Caliper Project is licensed under the Creative Commons Attribution 4.0 International License. You may obtain a copy of the license, titled CC-BY-4.0, at http://creativecommons.org/licenses/by/4.0/.

0개의 댓글