Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. Weโ€™ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

๐Ÿš€ 2๋‹จ๊ณ„ - GitHub(HTTP) #105

Merged
merged 13 commits into from
Sep 1, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
# TODO
## Best Practice
## Step1
## Step2

### ์ด๋ฒˆ ๋ฏธ์…˜ TODO
- ์ˆœ์ˆ˜ ์ฝ”ํ‹€๋ฆฐ ๋ชจ๋“ˆ์ธ domain ๋ชจ๋“ˆ์„ ๋งŒ๋“ ๋‹ค.
- ์ˆœ์ˆ˜ ์ฝ”ํ‹€๋ฆฐ ๋ชจ๋“ˆ์ธ data ๋ชจ๋“ˆ์„ ๋งŒ๋“ ๋‹ค.
- data ๋ชจ๋“ˆ์€ domain ๋ชจ๋“ˆ์— ์˜์กดํ•ด์•ผ ํ•œ๋‹ค.
- app ๋ชจ๋“ˆ์€ domain ๋ชจ๋“ˆ์— ์˜์กดํ•ด์•ผ ํ•œ๋‹ค.
- Repository ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ค๋Š” ๊ธฐ๋Šฅ์€ data ๋ชจ๋“ˆ์— ๊ตฌํ˜„๋˜์–ด์•ผ ํ•œ๋‹ค.
- data ๋ชจ๋“ˆ์˜ ๊ตฌํ˜„์ฒด๋Š” ๋ชจ๋‘ internal class๋กœ ์„ ์–ธํ•œ๋‹ค.
- HTTP ์š”์ฒญ์„ ํ†ตํ•ด ๊ฐ€์ ธ์˜ค๋Š” ๊ตฌํ˜„์ฒด์— ๋Œ€ํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋ฅผ ์ž‘์„ฑํ•œ๋‹ค.
- OkHttp MockWebServer ์ด์šฉ
2 changes: 1 addition & 1 deletion app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ android {
}

dependencies {
implementation(project(":data"))
implementation(project(":domain"))

implementation("org.jetbrains.kotlin:kotlin-stdlib:${Version.kotlin}")
implementation("androidx.core:core-ktx:1.9.0")
Expand Down
16 changes: 15 additions & 1 deletion data/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,18 @@ java {

dependencies {
implementation(project(":domain"))
}

implementation("com.squareup.okhttp3:okhttp:4.10.0")
implementation("com.squareup.okhttp3:logging-interceptor:4.10.0")
implementation("com.squareup.retrofit2:retrofit:2.9.0")
implementation("com.squareup.retrofit2:converter-moshi:2.9.0")
implementation("com.squareup.moshi:moshi-kotlin:1.14.0")
implementation("com.squareup.okhttp3:mockwebserver:4.10.0")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.7.3")

// test
testImplementation("junit:junit:4.13.2")
testImplementation("com.google.truth:truth:1.1.3")
testImplementation("io.mockk:mockk:1.13.7")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3")
}
20 changes: 20 additions & 0 deletions data/src/main/java/camp/nextstep/edu/github/data/Mapper.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/**
* @author Daewon on 01,September,2023
*
*/

package camp.nextstep.edu.github.data

import camp.nextstep.edu.github.data.response.GithubRepositoryResponse
import camp.nextstep.edu.github.domain.model.GithubRepository

internal fun List<GithubRepositoryResponse>.toDomainModels(): List<GithubRepository> {
return this.map { it.toDomainModel() }
}

internal fun GithubRepositoryResponse.toDomainModel(): GithubRepository {
return GithubRepository(
fullName = fullName,
description = description
)
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package camp.nextstep.edu.github.data.di

import camp.nextstep.edu.github.data.retrofit.BASE_URL
import camp.nextstep.edu.github.data.retrofit.GithubService
import camp.nextstep.edu.github.data.retrofit.RetrofitNetwork

internal object NetworkProvider {
fun provideGithubService(baseUrl: String = BASE_URL): GithubService {
return RetrofitNetwork
.create(baseUrl)
.create(GithubService::class.java)
}
}
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/**
* @author Daewon on 31,August,2023
*
*/

package camp.nextstep.edu.github.data.network

import camp.nextstep.edu.github.data.retrofit.GithubService
import camp.nextstep.edu.github.data.retrofit.networkResult
import camp.nextstep.edu.github.data.toDomainModels
import camp.nextstep.edu.github.domain.Result
import camp.nextstep.edu.github.domain.model.GithubRepository
import camp.nextstep.edu.github.domain.repository.NetworkRepository


internal class DefaultNetworkRepository(
private val githubService: GithubService
) : NetworkRepository {
override suspend fun getRepositories(): Result<List<GithubRepository>> =
networkResult { githubService.getRepositories().toDomainModels() }

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package camp.nextstep.edu.github.data.response

import com.squareup.moshi.Json

internal data class GithubRepositoryResponse(
@Json(name = "full_name")
val fullName: String,
@Json(name = "description")
val description: String?
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* @author Daewon on 31,August,2023
*
*/

package camp.nextstep.edu.github.data.retrofit

import camp.nextstep.edu.github.data.response.GithubRepositoryResponse
import retrofit2.http.GET


internal interface GithubService {
@GET("repositories")
suspend fun getRepositories(): List<GithubRepositoryResponse>
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/**
* @author Daewon on 22,April,2023
*
*/

package camp.nextstep.edu.github.data.retrofit

import camp.nextstep.edu.github.domain.Result

internal inline fun <T> networkResult(transform: () -> T): Result<T> = try {
Result.Success(transform.invoke())
} catch (e: Exception) {
Result.Error(e)
}
Comment on lines +10 to +14

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

๊ธฐ์กด์— ์กด์žฌํ•˜๋Š” runCatching ์™€ ๋™์ผํ•œ ๊ตฌ์กฐ๋„ค์š”!
๊ตณ์ด ์ƒˆ๋กœ ๋งŒ๋“คํ•„์š”๋Š” ์—†์„๊ฑฐ๊ฐ™์•„์š”!

Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package camp.nextstep.edu.github.data.retrofit

import com.squareup.moshi.Moshi
import com.squareup.moshi.kotlin.reflect.KotlinJsonAdapterFactory
import okhttp3.OkHttpClient
import okhttp3.logging.HttpLoggingInterceptor
import retrofit2.Converter
import retrofit2.Retrofit
import retrofit2.converter.moshi.MoshiConverterFactory

const val BASE_URL = "https://api.github.com/"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์™ธ๋ถ€์—์„œ ๋…ธ์ถœ๋˜๊ธฐ๋ณด๋‹ค ๋‚ด๋ถ€์ ์œผ๋กœ๋งŒ ํ™œ์šฉ๋˜๋„ ์ข‹์„๊ฑฐ ๊ฐ™์•„์š”!


internal object RetrofitNetwork {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Retrofit์ด ์•„๋‹Œ ๋‹ค๋ฅธ ๋„คํŠธ์›Œํฌ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด, ๋„ค์ด๋ฐ๋„ ๋ณ€๊ฒฝํ•ด์•ผํ• ๊นŒ์š”?
์ข€๋” ์ถ”์ƒ์ ์ธ ๋„ค์ด๋ฐ์„ ์‚ฌ์šฉํ•ด๋„ ์ข‹์„๊ฑฐ๊ฐ™์•„์š”


private fun createOkhttpClient(): OkHttpClient {
return OkHttpClient.Builder().apply {
addInterceptor(HttpLoggingInterceptor())
}.build()
}

private fun createJsonAdapterFactory(): Converter.Factory =
MoshiConverterFactory.create(
Moshi.Builder()
.add(KotlinJsonAdapterFactory())
.build()
)

fun create(baseUrl: String): Retrofit {
return Retrofit.Builder()
.client(createOkhttpClient())
.baseUrl(baseUrl)
.addConverterFactory(createJsonAdapterFactory())
.build()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
/**
* @author Daewon on 01,September,2023
*
*/

package camp.nextstep.edu.github.data

import camp.nextstep.edu.github.data.di.NetworkProvider
import camp.nextstep.edu.github.data.response.GithubRepositoryResponse
import camp.nextstep.edu.github.data.retrofit.GithubService
import com.google.common.truth.Truth.assertThat
import kotlinx.coroutines.test.runTest
import okhttp3.mockwebserver.MockResponse
import okhttp3.mockwebserver.MockWebServer
import org.junit.Before
import org.junit.Test
import java.io.File


class GithubServiceTest {

private lateinit var server: MockWebServer
private lateinit var githubService: GithubService

@Before
fun setUp() {
server = MockWebServer()
githubService = NetworkProvider.provideGithubService(server.url("").toString())
}

@Test
fun `test_json_์š”์ฒญ`() = runTest {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์˜๋ฏธ์žˆ๋Š” ํ…Œ์ŠคํŠธ ์ผ€์ด์Šค๋ฅผ ์ž‘์„ฑํ•ด๋„ ์ข‹์„๊ฑฐ ๊ฐ™์•„์š”!

// given
val response = MockResponse().setBody(File("src/test/resources/repositories.json").readText())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

src/test/resources/repositories/200.json
src/test/resources/repositories/404.json
src/test/resources/repositories/๏ฟฝemptyList.json

๊ฐ™์ด ํ…Œ์ŠคํŠธ๋ณ„๋กœ ์—ฌ๋Ÿฌ ์ผ€์ด์Šค์˜ jsonํŒŒ์ผ์„ ํ™œ์šฉํ•ด๋ด๋„ ์ข‹์•„์š”!

server.enqueue(response)

// when
val actual = githubService.getRepositories()

// then
val expected = GithubRepositoryResponse(
fullName = "mojombo/grit",
description = "**Grit is no longer maintained. Check out libgit2/rugged.** Grit gives you object oriented read/write access to Git repositories via Ruby."
)
assertThat(actual).contains(expected)
}

@Test
fun `test_json_์š”์ฒญ2`() = runTest {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

์ค‘๋ณต๋˜์„œ ๋ถˆํ•„์š”ํ•œ ํ…Œ์ŠคํŠธ ๊ฐ™์•„์š”!
์ฐจ๋ผ๋ฆฌ emptyList ๊ฐ™์€ ์ผ€์ด์Šค๋ฅผ ์ถ”๊ฐ€ํ•ด๋ณด๋ฉด ์–ด๋–จ๊นŒ์š”?

// given
val response = MockResponse().setBody(File("src/test/resources/repositories.json").readText())
server.enqueue(response)

// when
val actual = githubService.getRepositories()

// then
val expected = GithubRepositoryResponse(
fullName = "jnicklas/uploadcolumn",
description = "UploadColumn is no longer maintained, check out CarrierWave for an alternative"
)
assertThat(actual).contains(expected)
}
}
Loading