OKHttp3, Retrofit2
- square사에서 개발한 오픈소스 라이브러리.
- http 클라이언트로 쉽게 request, response가 가능하게 함.
- Retrofit 특징
- 성능이 좋음. (Volley, AsyncTask에 비해)
- annotation을 사용해 HTTP 작업을 하는 메소드를 정의하기 때문에 개발자들이 행위를 손쉽게 알아볼 수 있음. (가독성 👍)
- 주고받는 데이터(JSON)를 자동으로 파싱해주기 때문에 유지보수가 편리함.
- 캐싱이 불가능함
- image loading이 불가능하여 glide와 같은 라이브러리를 사용해야 함.
OKHttp3 - 내가 사용한 부분
내가 사용한 부분은 클라이언트에서 request를 날릴 때 헤더를 넣어주도록 만들었음.
// header 추가private val okHttpClient = OkHttpClient.Builder().addInterceptor( HttpLoggingInterceptor().apply { level = HttpLoggingInterceptor.Level.NONE }).addInterceptor { val request = it.request() .newBuilder() .addHeader("accessToken", "$token") .build() val response = it.proceed(request) response}.build()private val getRetrofit by lazy { Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create(gson)) .build()}
아래와 같이 getRetrofit 변수를 통해 가져오는 모든 retrofit 객체는 .client(okHttpClient) 코드로 헤더를 추가하게 됨.
Token - Basic, Bearer
Basic은 username, password를 Base64로 인코딩하는 방식. Base64로 단순히 인코딩하는 것이므로 쉽게 원본으로 찾는 복호화가 가능함. Bearer은 OAuth 방식의 인증을 사용함. 토큰에 ID, PW를 사용하지 않고 Stateless, 보안성이 장점임.
Retrofit2 - 내가 사용한 부분
private val getRetrofit by lazy { Retrofit.Builder() .baseUrl(BASE_URL) .client(okHttpClient) .addConverterFactory(GsonConverterFactory.create(gson)) .build()}val getSignUpRetrofitService: SignUpInterface by lazy { getRetrofit.create(SignUpInterface::class.java)}
interface SignUpInterface { // 유저 닉네임 중복 조회 @GET("/user/username/check") suspend fun checkUserNameDuplicated(@Query("username") userName: String): Response<Void> // 유저 회원가입 @POST("/user/sign-up") suspend fun makeAccount(@Body data: SignUpAccountData): Response<Void> // 유저 관심사 수정 @PATCH("/user/interest") suspend fun setInterest(@Body data: UserInterestData): Response<Void> // 유저 정보 확인 @PATCH("/user/info") suspend fun setAdditionalInfo(@Body data: UserAdditionalInfo): Response<Void>}
class SignUpRepository { // 싱글톤 구현 companion object { private var instance: SignUpRepository? = null fun getInstance(): SignUpRepository? { if(instance == null) instance = SignUpRepository() return instance } } suspend fun checkUserNameDuplicated(userName: String): Boolean { val response = ConnectionObject .getSignUpRetrofitService.checkUserNameDuplicated(userName) return if (response.isSuccessful) { true } else { false } } suspend fun makeAccount(signUpData: SignUpAccountData): String? { val response = ConnectionObject .getSignUpRetrofitService.makeAccount(signUpData) return if (response.isSuccessful) { response.headers().get("accessToken") } else { Log.d("make account error", response.errorBody().toString()) null } } suspend fun setInterest(userInterestData: UserInterestData): Boolean { val response = ConnectionObject .getSignUpRetrofitService.setInterest(userInterestData) if(!response.isSuccessful) { Log.d("set account interest", response.errorBody().toString()) } return response.isSuccessful } suspend fun setAdditionalInfo(userAdditionalInfo: UserAdditionalInfo): Boolean { val response = ConnectionObject .getSignUpRetrofitService.setAdditionalInfo(userAdditionalInfo) if(!response.isSuccessful) { Log.d("set additional info", response.errorBody().toString()) } return response.isSuccessful }}
// 실제 사용한 부분 private fun checkUserNameDuplicated(id: String) { viewModelScope.launch { if (signUpRepository.checkUserNameDuplicated(id)) { guideText.value = R.string.signup_ok errorText.value = null } else { guideText.value = null errorText.value = R.string.signup_duplicate } }}
플로우를 설명하면 다음과 같음. 실제로 사용할 때 접근하는 부분은 뷰모델에서 직접적으로 요청하는 것이 아닌 Repository를 통해 플로우 시작. (repository 객체는 뷰에서 넘김.) 리포의 checkUserNameDuplicated 메소드를 요청하고 → 해당 메소드에서는 SignUpInterface로 생성된 retrofit 객체를 통해 http 통신을 시작하게 됨. 이때 사용한 디자인 패턴은 Repository Pattern임.
Repository Pattern

이 이미지를 기준으로 봤을 때 ViewModel에서는 로컬 데이터인지, 리모트 데이터인지 관계 없이 Repository를 통해 접근해서 해당 데이터를 사용함. 즉 ViewModel에서는 직접 Data에 접근하는 것이 아닌 Repository만 접근하게 함. Repository에서 로컬 혹은 리모트 데이터에 접근해서 데이터를 가져와 뷰모델에 전달해줌. 즉 Data Layer가 캡슐화가 됨. 캡슐화로 일어나는 장점에는 여러가지가 있지만 참고했던 레퍼런스 중에 가장 크게 느꼈던 부분은 일관된 인터페이스로 데이터를 요청할 수 있다는 장점임. 단점 또한 프로젝트를 진행하며 느꼈던 부분인데 관리가 필요한 코드와 파일이 증가한다는 점임. 레퍼런스에 적힌 장점으로는 단위 테스트가 가능해진다는 점이 있었음.
RESTful API?
REST API 설계 규칙
- URI는 리소스의 자원을 표현해야 함
- 자원에 대한 행위는 HTTP Method로 표현해야 함
- / 슬래시 구분자는 계층 관계를 나타내는데 사용
- URI 마지막 문자로 / 슬래시 구분자가 들어가서는 안 됨
- 하이픈은 URI 가독성을 높이는데 사용
- _ 밑줄은 URI에 사용하지 않음
- URI 경로에는 소문자가 적합함
HTTP를 잘 활용하기 위한 원칙은 URL로 자원을 표현하는 데에 집중하고 자원의 상태에 대한 정의는 HTTP Method로 하는 것이 REST한 API를 설계하는 규칙. 즉, RESTful하게 설계한다는 것의 의미는 URI로 자원을 표현하고 자원에 대한 행위는 HTTP Method(GET, POST, PUT, DELETE)로 표현
not RESTful API? → CRUD를 모두 POST로 처리하는 API가 그 예시가 될 수 있음.
Reference
'CS > Android, Kotlin' 카테고리의 다른 글
[Android] Hilt (0) | 2023.05.09 |
---|---|
[Android] Base Activity, Fragment (0) | 2023.04.16 |
[Android] Custom View (0) | 2023.04.15 |
[Android] Data Binding (0) | 2023.04.15 |
[Android] MVVM (0) | 2023.04.13 |