Proto Datastore이란 무엇인가?
public interface DataStore<T> {
public val data: Flow<T>
public suspend fun updateData(transform: suspend (t: T) -> T): T
}
Proto Datastore은 DataStore 인터페이스를 구현하는, 간단한 데이터를 key-value 쌍으로 저장하기 위한 데이터 저장소 솔루션이다. 이전 SharedPreference나 Preference Datastore과는 달리 저장되는 데이터의 Type Safety를 보장한다는 점이 큰 장점이다. 따라서 Primitive Type이나 간단한 Collection 타입의 저장만이 지원되는 SharedPreference나 Preference Datastore과는 달리 우리가 원하는 클래스 객체로 데이터를 넣거나 뺄 수 있다.
*Protocol Buffer을 사용하지만 이 이야기는 너무 깊게 들어가므로 생략하도록 하겠다.
Proto Datastore가 작동하는 방식
Primitive 타입만 저장되는 내부 저장소에 저장하는데 어떻게 Type Safety가 보장되는 것일까? 바로 [그림1]과 같이 Datastore 인터페이스와 내부 저장소 중간에 Serializer Interface의 구현체가 포함되어 있기 때문이다. Datastore에서는 객체를 인자로 받은 다음 Serializer을 이용해 객체를 직렬화 해 String으로 바꾼 다음 내부 저장소에 저장한다.
public interface Serializer<T> {
public val defaultValue: T
public suspend fun readFrom(input: InputStream): T
public suspend fun writeTo(t: T, output: OutputStream)
}
Serailizer은 한 개의 변수와 두개의 메서드로 이루어진 인터페이스이다. 이들 각각의 역할은 다음과 같다.
- defaultValue: 저장소에 데이터가 없을 때 리턴되는 값
- readFrom(input: InputStream) : 역직렬화 하기 위한 메서드
- writeTo(t: T, output: OutputStream) : 직렬화 하기 위한 메서드
이 인터페이스를 사용해 Datastore은 들어오는 객체를 직렬화 하여 내부 저장소에 저장한다. 또한 내부 저장소에서 다시 빼내올 때도 Serializer을 이용해 역직렬화를 한다. 이로 인해 바깥에서는 객체를 저장 할 수 있는 것처럼 보이는 것이다.
이로 인해 DataStore 객체를 얻어올 때 Preference Datastore 과는 다르게 Serializer 객체가 추가된다. 아래 코드는 앞 글에서 보았던 Preference Datastore가 DataStore 객체를 가져올 때 사용하는 메서드이다.
val Context.settingsDataStore: DataStore<Preferences> by preferencesDataStore(name = "settings")
위의 코드와 다르게 Proto Datastore에서는 DataStore 객체를 가져오기 위해 다음과 같은 코드를 사용한다. 중간에 serializer = SettingSerializer가 있는 것을 확인할 수 있다.
val Context.settingDataStore: DataStore<OnBoardingState> by dataStore(
fileName = "settingDataStore.pb",
serializer = SettingSerializer
)
정리
이 글에서는 Proto Datastore이 Type Safety를 보장하기 위해 중간에 Serailizer을 추가하는 것을 알았다. 이를 코드로 보면 매우 복잡해 이해가 어려우니 이 장에서 어떻게 작동하는지에 대해 알고 가도록 하자.
다음 글에서는 Proto Datastore을 사용하는 방법을 살펴보도록 하겠다.