How to SCORE #2
DB Abstraction(VarDB, ArrayDB, DictDB)
이번 시리즈를 이해하기 위해서는 Python
언어 및 ICON 이 제공하는 CLI 개발도구인 T-Bears 및 ICON SDK 활용법에 대한 약간의 사전지식이 필요합니다.
첫번째 파트에서는 SCORE 프로젝트 생성부터 멤버 변수 선언 및 초기화까지 살펴보았습니다. 혹시 아직 못보신 분이 있으시면 파트 1을 먼저 보시는 것을 권장합니다.
시리즈의 두번째 파트에서는 SCORE에서 데이터에 접근하기 위해 state DB 를 wrapping 하여 제공하는 DB Abstraction(VarDB, ArrayDB, DictDB)에 대해 살펴보겠습니다.
Table of Contents
DB Abstraction
- LevelDB
- Supported Data Types
VarDB
- Create VarDB Instance
- Methods
ArrayDB
- Create ArrayDB Instance
- ArrayDB vs list
- Methods
DictDB
- Create DictDB Instance
- DictDB vs dict
- Method
Hand-on Exercise
- __init__
- on_install
- hello
DB Abstraction
LevelDB
ICON은 상태를 저장하기 위해 key-value
방식의 NoSQL 데이터베이스인 LevelDB 를 사용하고 있습니다. 이러한 LevelDB 의 한가지 특징은 key-value
로 byte array
를 사용한다는 점입니다.
LevelDB에 대한 보다 자세한 내용은 구글에서 제공하는 문서를 통해 확인하실 수 있습니다.
만약 사용자가 LevelDB를 직접 사용할 경우 key
와 value
를 모두 byte array
로 입력 및 조회해야 되므로, 사용자들이 읽을 수 있는 문자 그대로 사용하기에는 다소 불편한 부분이 있습니다.
ICON은 데이터를 byte array
로 변환하는 다소 불편한 과정을 Plyvel을 사용하여 구현한 VarDB
, ArrayDB
, DictDB
클래스와 같은 DB Abstraction 클래스들을 제공하는 것으로 내부적으로 해당 과정을 대신 수행합니다. 따라서 사용자들은 DB Abstraction 클래스들을 통해 데이터들을 byte array
로 전환하지 않고 LevelDB를 활용할 수 있습니다.
Supported Data Types
ICON의 DB Abstraction 클래스들은 bytes
, int
, str
, bool
과 같은 Python
의 자료형과, Address
와 같은 ICON에서 제공하는 클래스를 지원합니다.
bool
타입의 경우True -> 0x1
,False -> 0x0
으로 변환되어 저장됩니다.
value_type
은 Address
, bytes
, int
, str
, bool
을 모두 지원하며, key
는 bool
을 제외한 Address
, bytes
, int
, str
을 지원합니다.
DB로 부터 데이터를 조회할 때 key
에 해당하는 데이터가 없을 경우 설정되어 있는 value_type
에 따라 반환되는 값이 달라집니다.
value_type
이 int
인 경우에는 0
, str
인 경우에는 “”
, bool
인 경우에는 False
, Address
또는 bytes
인 경우에는 None
을 반환합니다.
Address
클래스에 대해서는 추후 이어지는 시리즈에서 활용법 등과 함께 보다 자세히 살펴볼 예정입니다.
VarDB
key
를 통해 데이터를 저장하고, value_type
에 맞게 변환하여 반환되는 데이터를 조회할 수 있습니다.
Create VarDB Instance
VarDB(var_key, db, value_type)
var_key
: VarDB
의 key
로 Address
, int
, str
, bytes
중 하나의 타입을 사용합니다. VarDB
인스턴스를 생성할때 var_key
값을 바탕으로 LevelDB와 연결되므로, var_key
가 같은 경우 value_type
이 다르더라도 동일한 데이터 소스를 사용하게 됩니다.
value_type
: key
에 해당하는 VarDB
의 value_type
으로 Address
, int
, str
, bytes
, bool
중 하나의 타입을 사용합니다.
Methods
get
:var_key
를 바탕으로 정해진 데이터소스를 바라보는VarDB
인스턴스를 생성하여byte array
로 저장된 데이터를 조회하고value_type
에 맞게 변환하여 결과를 반환합니다.
VarDB(var_key, db, value_type).get()
VarDB
가 바라보는 데이터소스의 값이 b“Hello”
와 같이 byte array
로 설정되어 있다고 가정한다면 출력되는 결과는 다음과 같을 것입니다.
VarDB(var_key, db, value_type = str).get()
# value_type: str -> "Hello"VarDB(var_key, db, value_type = bool).get()
# value_type: bool -> True 또는 0x1
LevelDB에 최종적으로 저장된 데이터는
byte array
형태로, DB Abstraction에서 데이터를 조회할 때value_type
에 따라 데이터를 변형하여 반환합니다.
set
:var_key
를 바탕으로 정해진 데이터소스를 바라보는VarDB
인스턴스를 생성하고, 입력받은 데이터를byte array
로 변환하여 입력 및 수정합니다.
VarDB(var_key, db, value_type).set(value)
remove
:var_key
를 바탕으로 정해진 데이터소스를 바라보는VarDB
인스턴스를 생성하고, 기존에 저장되어 있는 데이터를 삭제합니다.
VarDB(var_key, db, value_type).remove()
ArrayDB
value-type
을 통해 반환되는 데이터의 타입을 지정하며, Python
의 list
자료형과 유사하게 사용할 수 있습니다.
Create ArrayDB Instance
ArrayDB(var_key, db, value_type)
var_key
: ArrayDB
의 key
로 Address
, int
, str
, bytes
중 하나의 타입을 사용합니다. ArrayDB
인스턴스를 생성할때 var_key
값을 바탕으로 LevelDB와 연결되므로, var_key
가 같은 경우 value_type
이 다르더라도 동일한 데이터 소스를 사용하게 됩니다.
value_type
: key
에 해당하는 ArrayDB
의 value_type
으로 Address
, int
, str
, bytes
, bool
중 하나의 타입을 사용합니다.
ArrayDB vs list
iterable
:ArrayDB
는 순서를 가지며,iterable
한 클래스로Python
의list
와 같이iterating
을 통해element
를 모두 조회할 수 있습니다.
# Python List Comprehension을 이용하여 elements를 조회
elements_list = [el for el in ArrayDB(var_key, db, value_type)]# Python for 문을 통해 element를 조회하며 작업 수행
for el in ArrayDB(var_key, db, value_type):
pass
length
:len
메소드를 통해하여Python
의list
와 같이ArrayDB
에 들어있는element
의 수를 확인할 수 있습니다.
length_of_array_db = len(ArrayDB(var_key, db, value_type))
insert
:Python
의list
와 같이 특정index
에element
를 끼워넣는insert
는ArrayDB
에서는 지원하지 않습니다.pop
:ArrayDB
의pop
은 마지막으로 입력된element
를 뽑아내어 반환합니다.Python
의list
와 같이 특정index
의element
를 뽑아내는pop
은ArrayDB
에서는 지원하지 않습니다.
제한되는 기능들을 살펴보니
Python
의list
보다는stack
과 유사한 기능을 제공하는 것 같습니다.
Methods
get
:ArrayDB
로 부터 특정index
의 데이터를 조회하고value_type
에 맞게 변환하여 결과를 반환합니다.index
로-1
을 입력하는 경우 마지막element
를 조회합니다.
ArrayDB(var_key, db, value_type).get(index)
pop
:ArrayDB
로 부터 마지막으로 입력된 데이터를 조회하고value_type
에 맞게 변환하여 결과를 반환하는 것과 동시에 제거합니다.Python
list
의 특정index
를 입력하지 않은pop
과 같은 기능을 수행합니다.
ArrayDB(var_key, db, value_type).pop()
put
:ArrayDB
에 데이터를 추가합니다.Python
list
의append
와 같은 기능을 수행합니다.
ArrayDB(var_key, db, value_type).put(value)
DictDB
value-type
을 통해 반환되는 데이터의 타입을 지정하며, Python
의 dict
자료형과 유사하게 사용할 수 있습니다.
Create DictDB Instance
DictDB(var_key, db, value_type, depth)
var_key
: DictDB
의 key
로 Address
, int
, str
, bytes
중 하나의 타입을 사용합니다. DictDB
인스턴스를 생성할때 var_key
값을 바탕으로 LevelDB와 연결되므로, var_key
가 같은 경우 value_type
이 다르더라도 동일한 데이터 소스를 사용하게 됩니다.
value_type
: key
에 해당하는 DictDB
의 value_type
으로 Address
, int
, str
, bytes
, bool
중 하나의 타입을 사용합니다.
depth
: DictDB
에 저장된 데이터를 조회할 때 사용할 key
들의 수 입니다. DictDB
의 경우 DictDB[key]
와 같은 방식으로 key
에 해당하는 데이터를 조회할 수 있습니다. 만약 depth
의 값이 2
이상인 경우 DictDB[key]
는 depth
가 1
만큼 차감된 새로운 DictDB
를 반환합니다.
DictDB vs dict
Nested Dictionary
: ICON의 DictDB는 입력 가능한value_type
이 정해져 있어depth
를 통해Python
의Nested Dictionary
와 유사한 기능을 제공합니다.
__setitem__
메소드
depth
가1
이 아닌 경우key
의value
를 설정할 수 없습니다.
# Python의 Nested Dictionary 생성
python_dict = {'1st': {'2nd': '2nd_layer_value'}}# Python의 Nested Dictionary의 데이터 조회
python_dict['1st']['2nd'] # '2nd_layer_value' 반환# depth : 1
DictDB(var_key, db, value_type, 1)['1'] = value
DictDB(var_key, db, value_type, 1)['1'] # value_type에 맞는 value 반환"""
depth가 2 이상인 경우, dict[key]는 depth = depth-1 처리된 DictDB를 반환합니다. 따라서 적절하지 않은 depth의 value를 설정하는 경우 exception이 발생합니다.
"""
# depth : 2
DictDB(var_key, db, value_type, 2)['1'] # depth가 1인 DictDB 반환
DictDB(var_key, db, value_type, 3)['1'] = value # exception 발생DictDB(var_key, db, value_type, 2)['1']['2'] = value
DictDB(var_key, db, value_type, 2)['1']['2'] # value 반환# depth : 3
DictDB(var_key, db, value_type, 3)['1']['2']['3'] = value
DictDB(var_key, db, value_type, 3)['1']['2']['3'] # value 반환DictDB(var_key, db, value_type, 3)['1'] # depth가 2인 DictDB 반환
DictDB(var_key, db, value_type, 3)['1']['2'] = value # exception 발생
keys
:Python
의dict
와 같이value
가 할당되어 있는key
들을 한번에 조회하는keys
는DictDB
에서는 지원하지 않습니다.
Method
remove
:depth
가1
인DictDB
로 부터 해당key
의 데이터를 제거합니다.
DictDB(var_key, db, value_type, 1).remove(key)
Hands-on Exercise
지난 파트 1에서 DB Abstraction에 대한 이해 과정을 생략하고 샘플을 따라 일단 VarDB
를 사용해 보았습니다. 이번에는 파트 1 에서 작성한 코드에 이어서 DictDB
, ArrayDB
를 사용하는 부분을 추가로 작성해봅시다.
__init__
DictDB
를 사용할 멤버 변수를 self._dict_db
와 같이 선언합니다.
ArrayDB
의 경우 내부적인 버그로 인해 잠시동안 __init__
에서의 변수 선언이 아닌 property
로 별도로 선언하여, 사용시 마다 ArrayDB
인스턴스를 생성하여 사용하도록 합니다.
from iconservice import *TAG = 'SampleOne'class SampleOne(IconScoreBase):
_OWNER_NAME = "owner_name"
_ARRAY_DB_SAMPLE = "array_db_sample"
_DICT_DB_SAMPLE = "dict_db_sample" def __init__(self, db: IconScoreDatabase) -> None:
super().__init__(db)
self._owner_name = VarDB(self._OWNER_NAME, db, str)
self._dict_db = DictDB(self._DICT_DB_SAMPLE, db, str) @property
def _array_db(self):
return ArrayDB(self._ARRAY_DB_SAMPLE, self.db, str)
on_install
DictDB
에 key-value
로 “Jin” : “Developer”
, “nanaones” : “Developer”
, “ICON” : “Blockchain”
, “SCORE” : “Smart Contract”
를 입력합니다.
DictDB
의 key
를 순회하며 value
를 조회하기 위해 ArrayDB
에 DictDB
의 key
들을 입력합니다.
def on_install(self) -> None:
super().on_install()
self._owner_name.set("Life4honor") self._dict_db["Jin"] = "Developer"
self._array_db.put("Jin") self._dict_db["nanaones"] = "Developer"
self._array_db.put("nanaones") self._dict_db["ICON"] = "Blockchain"
self._array_db.put("ICON") self._dict_db["SCORE"] = "Smart Contract"
self._array_db.put("SCORE")
hello
VarDB
, ArrayDB
, DictDB
로 부터 데이터를 조회한 데이터를 가공하여 str
형태로 반환해 봅시다.
@external(readonly=True)
def hello(self) -> str:
owner = self._owner_name.get()
elements = [(el, self._dict_db[el]) for el in self._array_db]
return f"{owner} : Owner, {elements}"
[el for el in self._array_db]
는ArrayDB
에 저장한key
들을 순회하며 맞춰el
로 가져와value_type
에 맞게 변형하여list
에 추가하고, 최종적으로 생성된list
를 결과로 반환합니다.
self._dict_db[key]
는DictDB
로 부터key
에 해당하는value
들을 조회하고 value_type에 맞게 변형하여 반환합니다.샘플 코드의
[(el, self_dict_db[el]) for el in self._array_db]
는ArrayDB
로 부터 가져온key
들을 순회하며DictDB
로 부터key
에 해당하는value
들을 불러와key
와value
를tuple
형태로 묶어list
에 추가하고, 최종적으로 생성된list
를 결과로 반환합니다.
hello
메소드 호출 결과이것으로 How to SCORE 시리즈의 두번째 파트를 마치겠습니다.
다음 파트에서는 fallback
메소드와 @payable
데코레이터, ICX
전송과 예외 처리에 대해 알아보도록 하겠습니다.
ICON 과 관련된 추가적인 질문사항이 있는 경우, ICON 의 공식 개발자포럼 혹은 페이스북 그룹에 질문하시면 답변을 얻으실 수 있습니다.