Hyperledger fabric pagination 예제

hop6·2021년 10월 9일
0

이 글에서는 이전에 작성했던 Hyperledger fabric - totalQueryLimit 글에서 언급했던 pagination에 대한 예제를 작성해보려 한다.

https://github.com/hyperledger/fabric-samples/blob/release-1.4/chaincode/fabcar/go/fabcar.go
예제는 fabric-samples에 fabcar를 이용한다.

// 기존 코드
func (s *SmartContract) queryAllCars(APIstub shim.ChaincodeStubInterface) sc.Response {

	startKey := "CAR0"
	endKey := "CAR999"

	resultsIterator, err := APIstub.GetStateByRange(startKey, endKey)
	if err != nil {
		return shim.Error(err.Error())
	}
	defer resultsIterator.Close()

	// buffer is a JSON array containing QueryResults
	var buffer bytes.Buffer
	buffer.WriteString("[")

	bArrayMemberAlreadyWritten := false
	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return shim.Error(err.Error())
		}
		// Add a comma before array members, suppress it for the first array member
		if bArrayMemberAlreadyWritten == true {
			buffer.WriteString(",")
		}
		buffer.WriteString("{\"Key\":")
		buffer.WriteString("\"")
		buffer.WriteString(queryResponse.Key)
		buffer.WriteString("\"")

		buffer.WriteString(", \"Record\":")
		// Record is a JSON object, so we write as-is
		buffer.WriteString(string(queryResponse.Value))
		buffer.WriteString("}")
		bArrayMemberAlreadyWritten = true
	}
	buffer.WriteString("]")

	fmt.Printf("- queryAllCars:\n%s\n", buffer.String())

	return shim.Success(buffer.Bytes())
}

pagination이 적용될 포인트는 getStateByRange(), 또는 GetStateByPartialCompositeKey() 가 될 수 있다.
본 글에서는 fabcar 속 getStateByRange() 에 적용한다.

func (stub *ChaincodeStub) GetStateByRange(startKey, endKey string) (StateQueryIteratorInterface, error) 

func (stub *ChaincodeStub) GetStateByRangeWithPagination(startKey, endKey string, pageSize int32,
	bookmark string) (StateQueryIteratorInterface, *pb.QueryResponseMetadata, error)

가장 먼저, 두 함수의 파라미터와 리턴값을 확인해 본다.
StartKey와 endKey 는 동일하나, pagination 에서는 pageSize, bookmark 가 필요하다.
리턴값에서도 차이가 있는데 pagination은 pb.QueryResponseMetadata를 추가적으로 리턴해준다.

type QueryResponseMetadata struct {
	FetchedRecordsCount  int32    `protobuf:"varint,1,opt,name=fetched_records_count,json=fetchedRecordsCount,proto3" json:"fetched_records_count,omitempty"`
	Bookmark             string   `protobuf:"bytes,2,opt,name=bookmark,proto3" json:"bookmark,omitempty"`
	XXX_NoUnkeyedLiteral struct{} `json:"-"`
	XXX_unrecognized     []byte   `json:"-"`
	XXX_sizecache        int32    `json:"-"`
}
func (m *QueryResponseMetadata) GetFetchedRecordsCount() int32 {
	if m != nil {
		return m.FetchedRecordsCount
	}
	return 0
}

func (m *QueryResponseMetadata) GetBookmark() string {
	if m != nil {
		return m.Bookmark
	}
	return ""
}

Pagination답게 다음 요청에서 어느 데이터부터 받아 올지, bookmark를 리턴값으로 넘겨준다.

이런식으로 운용이 되겠다.

// first query ( bookmark = "" ) 
Iterator1, meta1, err := GetStateByRangeWithPaination(startKey, endKey, int32(5), "")
if err != nil {
	return err
}

nextBookmark := meta1.GetBookmark()

Iterator2, meta2, err := GetStateByRangeWithPagination(startKey, endKey, int32(5), nextBookmark)
if err != nil {
	return err
}

nextBoomark = meta2.GetBookmark()

.
.
.

GetStateByRangeWithPagination 에 대해서 어느정도 알아봤으니 이제 fabcar에 적용을 시켜 본다.

func (s *SmartContract) queryAllCarsWithPagination(APIstub shim.ChaincodeStubInterface, args []string) sc.Response {

	//PageSize int32, Bookmark String
    	tempInt := strconv.Atoi(args[1])
        
    	PageSize := int32(tempInt)
    	Bookmark := args[2]

	startKey := "CAR0"
	endKey := "CAR999"

	resultsIterator, meta, err := APIstub.GetStateByRangeWithPagination(startKey, endKey, PageSize, Bookmark)
	if err != nil {
		return shim.Error(err.Error())
	}
	defer resultsIterator.Close()

	// buffer is a JSON array containing QueryResults
	var buffer bytes.Buffer
	buffer.WriteString("[")

	bArrayMemberAlreadyWritten := false
	for resultsIterator.HasNext() {
		queryResponse, err := resultsIterator.Next()
		if err != nil {
			return shim.Error(err.Error())
		}
		// Add a comma before array members, suppress it for the first array member
		if bArrayMemberAlreadyWritten == true {
			buffer.WriteString(",")
		}
		buffer.WriteString("{\"Key\":")
		buffer.WriteString("\"")
		buffer.WriteString(queryResponse.Key)
		buffer.WriteString("\"")
        
        	buffer.WriteString("{\"nextBookmark\":")
		buffer.WriteString("\"")
        	buffer.WriteString(meta.GetBookmark())
		buffer.WriteString("\"")

		buffer.WriteString(", \"Record\":")
		// Record is a JSON object, so we write as-is
		buffer.WriteString(string(queryResponse.Value))
		buffer.WriteString("}")
		bArrayMemberAlreadyWritten = true
	}
	buffer.WriteString("]")

	fmt.Printf("- queryAllCarsWithPagination:\n%s\n", buffer.String())

	return shim.Success(buffer.Bytes())
}

// 마지막 page인 경우나, 잘못된 PageSzie, Bookmark 등 에러 제어는 생략

hyperledger fabric를 배우며 체인코드를 작성하다 보면 fabric-samples를 정말 많이 찾게 되는데, 예제에 없는 다른 기능들을 구현하려 할 때 자료가 너무 없어 난감했던 기억이 난다.

이럴 때는 hyperledger/fabric github에 들어가 내부적으로 어떻게 돌아가는지 코드를 확인하며 개발해가는 것이 가장 좋은 방법이지 않나 싶다.

0개의 댓글