이 글에서는 이전에 작성했던 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에 들어가 내부적으로 어떻게 돌아가는지 코드를 확인하며 개발해가는 것이 가장 좋은 방법이지 않나 싶다.