Skip to content

Commit f3a924c

Browse files
authored
[PC-1315] Gson + preferences datastore => proto datastore로 개선 (#172)
* [PC-1315] proto datastore 버전 카탈로그에 추가 * [PC-1315] proto datastore를 사용하기 위한 의존성 가 * [PC-1315] .profile_preferences, term_preferences .proto 파일 생성 * [PC-1315] term, profile를 proto datastore를 사용하기 위한 파일 생성 - LocalProfileDataSourceProtoImpl - LocalTermDataSourceProtoImpl - ProfileMapper - TermMapper - ProfileDataSerializer - TermDataSerializer * [PC-1315] 테스트를 위해 LocalTermDataSource, LocalProfileDataSource provides로 변경 * [PC-1315] .proto package 변경 및 mapper 가시성 변경 * [PC-1315] benchmarkMacroJunit4 -> benchmark * [PC-1315] benchmark-micro 모듈 생성 * [PC-1315] ProfileDataStoreCompareBenchmark 구현 - 1000번 테스트 * [PC-1315] TermDataStoreCompareBenchmark 구현 - 1000번 테스트 * Revert "[PC-1315] TermDataStoreCompareBenchmark 구현" This reverts commit ebc447c. * Revert "[PC-1315] ProfileDataStoreCompareBenchmark 구현" This reverts commit b2efc9f. * Revert "[PC-1315] benchmark-micro 모듈 생성" This reverts commit 169143e. * [PC-1315] gson + preferences datastore -> proto datastore * [PC-1315] 미사용 코드 및 의존성 제거 * [PC-1315] 로컬 데이터가 비어있을 때 API fallback 및 로컬 저장 테스트 추가 (Profile) * [PC-1315] 로컬 데이터가 비어있을 때 API fallback 및 로컬 저장 테스트 추가 (Term) * [PC-1315] Fallback 테스트 주석 가독성 있게 변경
1 parent 379d33d commit f3a924c

File tree

18 files changed

+584
-138
lines changed

18 files changed

+584
-138
lines changed

core/data/src/main/java/com/puzzle/data/repository/ProfileRepositoryImpl.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import kotlinx.coroutines.flow.firstOrNull
2626
import kotlinx.coroutines.flow.map
2727
import kotlinx.coroutines.launch
2828
import javax.inject.Inject
29+
import javax.inject.Named
2930

3031
class ProfileRepositoryImpl @Inject constructor(
3132
private val imageResizer: ImageResizeProcessor,

core/data/src/main/java/com/puzzle/data/repository/TermsRepositoryImpl.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import com.puzzle.network.model.UNKNOWN_INT
77
import com.puzzle.network.source.term.TermDataSource
88
import kotlinx.coroutines.flow.firstOrNull
99
import javax.inject.Inject
10+
import javax.inject.Named
1011

1112
class TermsRepositoryImpl @Inject constructor(
1213
private val termDataSource: TermDataSource,

core/data/src/test/java/com/puzzle/data/repository/ProfileRepositoryImplTest.kt

Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,10 @@ import com.puzzle.data.FakeSseClient
44
import com.puzzle.data.image.SpyImageResizeProcessor
55
import com.puzzle.data.source.profile.FakeLocalProfileDataSource
66
import com.puzzle.data.source.profile.FakeProfileDataSource
7+
import com.puzzle.data.source.profile.FakeProfileDataSource.Companion.DUMMY_MY_VALUE_PICK_RESPONSE
8+
import com.puzzle.data.source.profile.FakeProfileDataSource.Companion.DUMMY_MY_VALUE_TALK_RESPONSE
9+
import com.puzzle.data.source.profile.FakeProfileDataSource.Companion.DUMMY_VALUE_PICK_RESPONSE
10+
import com.puzzle.data.source.profile.FakeProfileDataSource.Companion.DUMMY_VALUE_TALK_RESPONSE
711
import com.puzzle.data.source.token.FakeLocalTokenDataSource
812
import com.puzzle.data.source.user.FakeLocalUserDataSource
913
import com.puzzle.domain.model.profile.Contact
@@ -284,4 +288,81 @@ class ProfileRepositoryImplTest {
284288
assertEquals(originValueTalks.map { it.id }, storedValueTalks.map { it.id })
285289
assertEquals(storedValueTalks.first { it.id == 1 }.summary, "변경된 요약")
286290
}
291+
292+
@Test
293+
fun `로컬이 비어있을 때 MyValueTalk API fallback이 발생한다`() = runTest {
294+
// given
295+
// localProfileDataSource 현재 비어있음 (FakeLocalProfileDataSource 초기 상태, API Fallback 유도 조건)
296+
profileDataSource.setMyValueTalks(listOf(DUMMY_MY_VALUE_TALK_RESPONSE))
297+
298+
// when
299+
val result = profileRepository.getMyValueTalks()
300+
301+
// then
302+
assertEquals(1, profileDataSource.getMyValueTalksCallCount)
303+
304+
assertEquals(1, result.size)
305+
306+
val saved = localProfileDataSource.myValueTalks.first()
307+
assertEquals(1, saved?.size)
308+
assertEquals(result.first(), saved?.first())
309+
}
310+
311+
@Test
312+
fun `로컬이 비어있을 때 MyValuePick API fallback이 발생한다`() = runTest {
313+
// given
314+
// localProfileDataSource 현재 비어있음 (FakeLocalProfileDataSource 초기 상태, API Fallback 유도 조건)
315+
profileDataSource.setMyValuePicks(listOf(DUMMY_MY_VALUE_PICK_RESPONSE))
316+
317+
// when
318+
val result = profileRepository.getMyValuePicks()
319+
320+
// then
321+
assertEquals(1, profileDataSource.getMyValuePicksCallCount)
322+
323+
assertEquals(1, result.size)
324+
325+
val saved = localProfileDataSource.myValuePicks.first()
326+
assertEquals(1, saved?.size)
327+
assertEquals(result.first(), saved?.first())
328+
}
329+
330+
@Test
331+
fun `로컬이 비어있을 때 ValuePickQuestions API fallback이 발생한다`() = runTest {
332+
// given
333+
// localProfileDataSource 현재 비어있음 (FakeLocalProfileDataSource 초기 상태, API Fallback 유도 조건)
334+
profileDataSource.setValuePicks(listOf(DUMMY_VALUE_PICK_RESPONSE))
335+
336+
// when
337+
val result = profileRepository.getValuePickQuestions()
338+
339+
// then
340+
assertEquals(1, profileDataSource.loadValuePickQuestionsCallCount)
341+
342+
assertEquals(1, result.size)
343+
344+
val saved = localProfileDataSource.valuePickQuestions.first()
345+
assertEquals(1, saved?.size)
346+
assertEquals(result.first(), saved?.first())
347+
}
348+
349+
@Test
350+
fun `로컬이 비어있을 때 ValueTalkQuestions API fallback이 발생한다`() = runTest {
351+
// given
352+
// localProfileDataSource 현재 비어있음 (FakeLocalProfileDataSource 초기 상태, API Fallback 유도 조건)
353+
profileDataSource.setValueTalks(listOf(DUMMY_VALUE_TALK_RESPONSE))
354+
355+
// when
356+
val result = profileRepository.getValueTalkQuestions()
357+
358+
// then
359+
assertEquals(1, profileDataSource.loadValueTalkQuestionsCallCount)
360+
361+
assertEquals(1, result.size)
362+
363+
val saved = localProfileDataSource.valueTalkQuestions.first()
364+
assertEquals(1, saved?.size)
365+
assertEquals(result.first(), saved?.first())
366+
}
367+
287368
}

core/data/src/test/java/com/puzzle/data/repository/TermsRepositoryImplTest.kt

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,16 @@ package com.puzzle.data.repository
22

33
import com.puzzle.data.source.term.FakeLocalTermDataSource
44
import com.puzzle.data.source.term.FakeTermDataSource
5+
import com.puzzle.data.source.term.FakeTermDataSource.Companion.DUMMY_TERM_RESPONSE
56
import com.puzzle.network.model.UNKNOWN_INT
67
import com.puzzle.network.model.terms.TermResponse
78
import kotlinx.coroutines.flow.first
89
import kotlinx.coroutines.test.runTest
10+
import org.junit.jupiter.api.Assertions.assertEquals
911
import org.junit.jupiter.api.Assertions.assertTrue
1012
import org.junit.jupiter.api.BeforeEach
1113
import org.junit.jupiter.api.Test
14+
import kotlin.collections.first
1215

1316
class TermsRepositoryImplTest {
1417
private lateinit var termDataSource: FakeTermDataSource
@@ -87,4 +90,23 @@ class TermsRepositoryImplTest {
8790
}
8891
)
8992
}
93+
94+
@Test
95+
fun `로컬이 비어있을 때 Terms API fallback이 발생한다`() = runTest {
96+
// given
97+
// localTermDataSource 현재 비어있음 (FakeLocalTermDataSource 초기 상태, API Fallback 유도 조건)
98+
termDataSource.setTerms(listOf(DUMMY_TERM_RESPONSE))
99+
100+
// when
101+
val result = termsRepository.getTerms()
102+
103+
// then
104+
assertEquals(1, termDataSource.loadTermsCallCount)
105+
106+
assertEquals(1, result.size)
107+
108+
val saved = localTermDataSource.terms.first()
109+
assertEquals(1, saved?.size)
110+
assertEquals(result.first(), saved?.first())
111+
}
90112
}

core/data/src/test/java/com/puzzle/data/source/profile/FakeProfileDataSource.kt

Lines changed: 77 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,22 +21,52 @@ import com.puzzle.network.source.profile.ProfileDataSource
2121
import java.io.InputStream
2222

2323
class FakeProfileDataSource : ProfileDataSource {
24+
25+
private var myValuePicks = listOf<MyValuePickResponse>()
26+
private var myValueTalks = listOf<MyValueTalkResponse>()
2427
private var valuePicks = listOf<ValuePickResponse>()
2528
private var valueTalks = listOf<ValueTalkResponse>()
2629

27-
fun setValuePicks(picks: List<ValuePickResponse>) {
30+
internal var getMyValuePicksCallCount = 0
31+
internal var getMyValueTalksCallCount = 0
32+
33+
internal var loadValuePickQuestionsCallCount = 0
34+
internal var loadValueTalkQuestionsCallCount = 0
35+
36+
internal fun setMyValuePicks(picks: List<MyValuePickResponse>) {
37+
myValuePicks = picks
38+
}
39+
40+
internal fun setMyValueTalks(talks: List<MyValueTalkResponse>) {
41+
myValueTalks = talks
42+
}
43+
internal fun setValuePicks(picks: List<ValuePickResponse>) {
2844
valuePicks = picks
2945
}
3046

31-
fun setValueTalks(talks: List<ValueTalkResponse>) {
47+
internal fun setValueTalks(talks: List<ValueTalkResponse>) {
3248
valueTalks = talks
3349
}
3450

35-
override suspend fun loadValuePickQuestions(): LoadValuePickQuestionsResponse =
36-
LoadValuePickQuestionsResponse(responses = valuePicks)
51+
override suspend fun getMyValuePicks(): GetMyValuePicksResponse {
52+
getMyValuePicksCallCount++
53+
return GetMyValuePicksResponse(responses = myValuePicks)
54+
}
55+
56+
override suspend fun getMyValueTalks(): GetMyValueTalksResponse {
57+
getMyValueTalksCallCount++
58+
return GetMyValueTalksResponse(responses = myValueTalks)
59+
}
60+
61+
override suspend fun loadValuePickQuestions(): LoadValuePickQuestionsResponse {
62+
loadValuePickQuestionsCallCount++
63+
return LoadValuePickQuestionsResponse(responses = valuePicks)
64+
}
3765

38-
override suspend fun loadValueTalkQuestions(): LoadValueTalkQuestionsResponse =
39-
LoadValueTalkQuestionsResponse(responses = valueTalks)
66+
override suspend fun loadValueTalkQuestions(): LoadValueTalkQuestionsResponse {
67+
loadValueTalkQuestionsCallCount++
68+
return LoadValueTalkQuestionsResponse(responses = valueTalks)
69+
}
4070

4171
override suspend fun updateMyValueTalks(valueTalks: List<MyValueTalk>): GetMyValueTalksResponse {
4272
val responses = valueTalks.map { valueTalk ->
@@ -132,10 +162,10 @@ class FakeProfileDataSource : ProfileDataSource {
132162
valuePicks: List<ValuePickAnswer>,
133163
valueTalks: List<ValueTalkAnswer>
134164
): UploadProfileResponse = UploadProfileResponse(
135-
role = "PENDING",
136-
accessToken = "accessToken",
137-
refreshToken = "refreshToken",
138-
)
165+
role = "PENDING",
166+
accessToken = "accessToken",
167+
refreshToken = "refreshToken",
168+
)
139169

140170
override suspend fun updateProfile(
141171
birthdate: String,
@@ -155,6 +185,41 @@ class FakeProfileDataSource : ProfileDataSource {
155185

156186
override suspend fun disconnectSSE() {}
157187
override suspend fun getMyProfileBasic(): GetMyProfileBasicResponse = throw NotImplementedError()
158-
override suspend fun getMyValueTalks(): GetMyValueTalksResponse = throw NotImplementedError()
159-
override suspend fun getMyValuePicks(): GetMyValuePicksResponse = throw NotImplementedError()
188+
189+
companion object {
190+
val DUMMY_MY_VALUE_TALK_RESPONSE = MyValueTalkResponse(
191+
id = 1,
192+
questionId = 1,
193+
title = "ㅂㅈㄷㄱ",
194+
category = "ㅂㅈㄷㄱ",
195+
answer = "ㅂㅈㄷㄱ",
196+
summary = "ㅂㅈㄷㄱ",
197+
guides = emptyList(),
198+
placeholder = "ㅂㅈㄷㄱ"
199+
200+
)
201+
202+
val DUMMY_MY_VALUE_PICK_RESPONSE = MyValuePickResponse(
203+
id = 1,
204+
questionId = 1,
205+
category = "ㅁㄴㅇㄹ",
206+
question = "ㅁㄴㅇㄹ",
207+
answerOptions = emptyList(),
208+
selectedAnswer = 1
209+
)
210+
val DUMMY_VALUE_TALK_RESPONSE = ValueTalkResponse(
211+
id = 1,
212+
category = "ㅁㄴㅇㄹ",
213+
title = "ㅁㄴㅇㄹ",
214+
placeholder = "ㅁㄴㅇㄹ",
215+
guides = emptyList()
216+
)
217+
218+
val DUMMY_VALUE_PICK_RESPONSE = ValuePickResponse(
219+
id = 1,
220+
category = "ㅋㅌㅊㅍ",
221+
question = "ㅋㅌㅊㅍ",
222+
answers = emptyList()
223+
)
224+
}
160225
}

core/data/src/test/java/com/puzzle/data/source/term/FakeTermDataSource.kt

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,26 @@ import com.puzzle.network.source.term.TermDataSource
77
class FakeTermDataSource : TermDataSource {
88
private var terms = listOf<TermResponse>()
99

10-
fun setTerms(newTerms: List<TermResponse>) {
10+
internal var loadTermsCallCount = 0
11+
12+
internal fun setTerms(newTerms: List<TermResponse>) {
1113
terms = newTerms
1214
}
1315

1416
override suspend fun loadTerms(): LoadTermsResponse {
17+
loadTermsCallCount++
1518
return LoadTermsResponse(responses = terms)
1619
}
1720

1821
override suspend fun agreeTerms(agreeTermsId: List<Int>) {}
19-
}
22+
23+
companion object {
24+
val DUMMY_TERM_RESPONSE = TermResponse(
25+
termId = 1,
26+
title = "ㅁㄴㅇㄹ",
27+
content = "ㅁㄴㅇㄹ",
28+
required = false,
29+
startDate = ""
30+
)
31+
}
32+
}

core/datastore/build.gradle.kts

Lines changed: 22 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
plugins {
22
id("piece.android.library")
33
id("piece.android.hilt")
4+
alias(libs.plugins.protobuf)
45
}
56

67
android {
@@ -12,9 +13,28 @@ android {
1213
}
1314
}
1415

16+
protobuf {
17+
protoc {
18+
artifact = libs.protobuf.protoc.get().toString()
19+
}
20+
generateProtoTasks {
21+
all().forEach { task ->
22+
task.builtins {
23+
task.builtins {
24+
create("java") {
25+
option("lite")
26+
}
27+
create("kotlin")
28+
}
29+
}
30+
}
31+
}
32+
}
33+
1534
dependencies {
1635
implementation(projects.core.domain)
1736

18-
implementation(libs.androidx.datastore)
19-
implementation(libs.gson)
37+
implementation(libs.androidx.datastore.preferences)
38+
implementation(libs.androidx.datastore.core)
39+
implementation(libs.protobuf.kotlin.lite)
2040
}

0 commit comments

Comments
 (0)