* 혼자 고민한 과정을 기록 목적으로 적은 포스팅입니다. 읽기 전 참고 부탁드립니다 :)
DTO
DTO는 서버와 통신하는 API의 데이터를 디코딩, 인코딩하기 위한 객체입니다.
{
"id": 1000,
"name": "다이어트",
"continued": 10,
"lastChallenged": "2011-04-10T20:09:31Z"
}
가령 API로 오는 데이터가 위와 같다면,
struct CategoryDTO: Codable {
let id: Int
let name: String
let continued: Int
let lastChallenged: Date
}
CategoryDTO는 id, name, continued, lastChallenged 속성을 가지게 됩니다.
이때 각 속성은 Non-옵셔널이 좋을지, 옵셔널이 좋을지 고민이 필요합니다.
Non-옵셔널이 유리한 경우와 옵셔널이 유리한 경우를 각각 알아봅시다.
Non-옵셔널이 유리한 경우
DTO의 속성이 옵셔널이 아니라면 DTO를 사용할 때 편합니다.
옵셔널 바인딩 등의 처리를 하지 않아도 되기 때문입니다.
DTO를 도메인 모델로 변환한다고 할 때,
init(dto: CategoryDTO) {
self.init(
id: dto.id ?? -1,
name: dto.name ?? "",
continued: dto.continued ?? 0,
lastChallenged: dto.lastChallenged
)
}
위처럼 기묘한 옵셔널 처리를 하지 않아도 됩니다.
DTO 객체를 따로 두지 않는 경우에도 Non-옵셔널이 편리합니다.
도메인 모델로 직접 인코딩, 디코딩하는 경우도 많습니다.
도메인 모델의 속성이 옵셔널 타입이라면 사용하는 곳마다 번거로운 옵셔널 처리를 해야 할 것입니다.
따라서 DTO의 속성을 Non-옵셔널 타입으로 설정하면 매우 편리합니다.
단순한 이유지만 굉장히 중요합니다.
개발자는 귀찮은걸 싫어하니까요 ㅎ
옵셔널이 유리한 경우
DTO의 속성이 옵셔널이라면 안전합니다.
DTO의 속성이 옵셔널이라면 API 응답에 해당 속성이 없어도 런타임 에러가 발생하지 않습니다.
반대로 DTO의 속성이 Non-옵셔널인데 해당 속성이 API Response Body에 없다면 런타임 에러가 발생합니다.
즉, 앱 개발자와 서버 개발자의 약속이 굉장히 중요하며 함부로 수정할 수 없습니다.
따라서 DTO의 속성이 옵셔널이라면 앱의 안전성이 올라가요.
어떤 속성에 어떤 타입을 써야 할까
그럼 DTO의 어떤 속성에 어떤 타입을 써야 할까요?
가장 단순한 방법은 모두 Non-옵셔널 또는 모두 옵셔널 타입으로 설정하는 방법이겠죠.
DTO를 작성할 때마다 고민하는 시간이 아깝다면 적절한 방법일 수 있습니다.
하지만 이 방법은 높은 확률로 최선의 방법은 아닐 겁니다.
단순히 떠오르는 케이스는 아래와 같겠죠?
- 서버가 항상 내려주는(혹은 내려줘야 하는) 속성: Non-옵셔널
- 서버가 내려줄지 안 내려줄지 모르는 속성: 옵셔널
예를 들어, 객체의 ID는 옵셔널일 수 없습니다.
ID가 처음부터 없으면 없었지, 중간에 사라지는 건 흔한 경우는 아니에요.
따라서 ID 속성은 Non-옵셔널이 적절할 수 있습니다.
"마지막으로 업데이트한 날짜" 속성인 lastUpdated는 어떨까요?
속성의 정책을 "업데이트한 적이 없으면 null을 내려주겠다"로 약속했다면 이 속성은 반드시 옵셔널이어야 합니다.
ID와 lastUpdated를 가진 DTO는 Non-옵셔널과 옵셔널 타입을 혼합하여 사용할 것입니다.
앱의 하위 호환성
모든 케이스가 ID, lastUpdated처럼 명확했다면 개발하며 먹고살기 편했을 텐데 아쉽게도 세상은 만만하지 않습니다.
애매한 경우, 예상치 못한 경우가 훨씬 많죠.
이 내용 때문에 이번 포스팅을 작성하기로 결심했습니다.
앱은 하위 호환성을 반드시 고려해야 합니다.
앱의 업데이트 선택권은 사용자에게 있기 때문입니다.
개발자가 고객의 앱 버전을 마음대로 올릴 수 없기 때문에 항상 하위 버전을 고려해야 합니다.
(업데이트를 안 할 경우 앱을 못 쓰게 할 순 있지만... 그런 경우는 제외하도록 합시다.)
이런 상황을 생각해 봅시다.
1.0 버전에서는 "반드시" 내려왔던 imageURL이라는 속성이 있었습니다.
반드시 내려왔기 때문에 Non-옵셔널로 설정했죠.
하지만 5.0 버전에서 이 "반드시"는 깨졌습니다.
기획이 바뀌어서 이미지를 표시하지 않기로 했기 때문이죠.
이제 서버 개발자는 imageURL이 거추장스러워집니다. (앱 개발자도 거추장스럽습니다.)
서버 개발자가 앱 개발자에게 "imageURL을 빼면 안 될까요?"라고 묻는다면
앱 개발자는 미안한 마음을 가지고 "안 돼요..."라고 대답할 수밖에 없습니다.
4.0 버전까지 오면서 사용자가 많아졌고,
5.0 버전에서 imageURL을 제거하면 1.0 ~ 4.0 버전의 앱은 크래시가 발생할 것이기 때문입니다.
앱을 이해하기 싫은 서버 개발자라면 언쟁이 일어날 수도 있고...
만약 imageURL을 빼서 서버의 성능이 나아지는 상황이라면 더 화를 낼 수도 있습니다.
이를 해결하기 위해서
서버 개발자가 API를 새로 파주거나... (legacy API는 나~~중에 제거되겠죠.)
imageURL을 그대로 들고 가거나... (신입이라면 UI에 이미지가 없는데 왜 imageURL이 내려오지? 고민을 할 수도 있습니다.)
강제 업데이트를 시키거나... 등 어떤 걸 선택해도 찜찜한 상황에 놓이게 됩니다 ㅎ;
마무리
이런 예상치 못한 상황 때문에 DTO의 속성을 Non-옵셔널로 할지, 옵셔널로 할지 참 고민이 됩니다.
현재 상황에서는 최선의 선택이었는데, 미래에는 앞길을 막는 선택일 수 있으니까요.
그렇다고 정해지지 않은 미래 때문에 현재의 편리함을 포기하는 것도 큰 손해일 수 있습니다.
현재의 편리함을 챙기고 미래에는 대화와 소통, 기록으로 해결하는 것도 좋은 방법일 수 있겠죠.
정답이 없는 문제고, 제 경험도 얕아 이번 포스팅에서도 결론을 낼 수 없었습니다.
경험이 쌓인다면 좋은 결론을 낼 수 있을까요?
다음엔 확신을 가진 포스팅을 적을 수 있길 기원합니다.
감사합니다.
아직은 초보 개발자입니다.
더 효율적인 코드 훈수 환영합니다!
공감과 댓글 부탁드립니다.