서버에서 받아오는 값과, 실제로 사용할 값의 타입이나 값들을 수정하고 싶을때, ViewModel에서 변환시키거나 하는 경우가 대부분 일 것이다. 하지만 더욱 더 일관되게 Model을 사용하고 싶을 때 DTO Model을 사용할 수 있을 것이다.
그러나 어떤 모델은 그냥 사용하고, 어떤 모델은 DTO를 사용하고, 어떤 모델은 Model안에 Computed Property로 값을 수정하였다면 일관성이 없을 것이다. 이번에는 내 DIP를 배우며 떠올린 역제네릭을 활용한 DTO Model의 구조화에 대해 포스팅 할 것이다.
SwiftUI) ContentView 이해하기 (1/2) - some View가 뭔가요?를 참고하자면,
제네릭의 경우 함수 구현 내에서는 값의 실제 타입에 대해서 알 수 없음(숨겨짐)
불투명한 타입의 경우, 외부에서 함수의 반환 값 유형을 정확하게 알 수 없음
다만 함수 내부에서는 어떤 타입을 다루는지 정확히 알고 있음
사실 코드는 되게 간단하다.
protocol DTOModel { }
protocol DTODecodable: Decodable {
associatedtype M: DTOModel
func asDTOModel() -> M
}
이게 전부다.
하나의 모델의 예시를 들자면,
// 서버에서 받아온 모델
struct UserReusultModel: DTODecodable {
let user_id: String
let email: String
let nickname: String
let profileImage: String
let phone: String
let provider: String
let createdAt: String
let token: Token
func asDTOModel() -> UserDTOModel {
let date = DateFormatter.defaultFormatter.date(from: createdAt) ?? Date()
return UserDTOModel(
userID: user_id,
email: email,
nickName: nickname,
profileImage: profileImage,
phone: phone,
provider: provider,
createdAt: date,
accessToken: token.accessToken,
refreshToken: token.refreshToken
)
}
}
// DTO모델
struct UserDTOModel: DTOModel {
let userID: String
let email: String
let nickName: String
let profileImage: String
let phone: String
let provider: String
let createdAt: Date
let accessToken: String
let refreshToken: String
}
// Request Repository 구현부
func validationEmail(query: EmailValidationRequestModel) -> Single<EmailValidationDTOModel> {
Single<EmailValidationDTOModel>.create { single in
self.provider.rx.request(.validationEmail(query: query))
.filterSuccessfulStatusCodes()
.map(EmailValidationResultModel.self)
.subscribe(with: self) { _, result in
single(.success(result.asDTOModel()))
} onFailure: { _, error in
single(.failure(error))
}.disposed(by: self.disposeBag)
return Disposables.create()
}
}
이런식으로 구조화하여 조금 더 깔끔하고 일관성 있는 코드를 작성 할 수 있을 것이다.