DTO(Data Transfer Object)
데이터 소스를 모델 클래스로 변환하는 과정에서 순수하게 클래스에 담기 위한 중간 전달 객체
DTO를 사용하는 이유
- 서버에서 데이터를 받을 때 잘못된 형식의 데이터를 받더라도 유연하게 처리하기 위해 클라이언트에서 사용하는 방어 수단이다
- json 키의 값의 타입이 한가지가 아니거나, 키가 없어 모델로 정의하기 어려운 경우에 사용하면 좋다
Model 클래스와 차이점
- 모든 필드가 nullable이다
- 타입 선언 시 가능한 상위 타입으로 지정한다
- 정수, 실수인 경우 → num
- 값이 String과 num 둘 다 해당하는 경우 → dynamic
- 직렬화, 역직렬화를 제공한다
DTO를 적용한 후 Model 클래스
- 모든 필드는 non-nullable 상수(final로 지정)
- ==, hashCode, copyWith, toString 메서드만 정의
장점
- 실제 데이터 형태를 몰라도 모델 클래스를 정의할 수 있다
- 내 앱에서 필요한 형태로 모델을 만들 수 있다
- Map -> Model Class 변환시 null 값 등의 예외를 사전에 걸러내기 용이하다
Mapper
순수한 데이터 소스 (DTO) 를 원하는 모델 클래스로 변환하려면
fromJson(), toJson() 처럼 변환 기능이 필요한데, 별도의 mapper 를 통해 변환해야 한다
흐름
- DataSource에서 http 통신을 통해 json 데이터를 가져온다
- 가져온 json을 DTO 객체로 반환한다
- Repository에서 DataSource로부터 받은 DTO를 mapper를 사용하여 Model 클래스로 변환시킨다
- 변환시킨 Model을 사용하여 비즈니스 로직을 구현한다
예시
extension TodoMapper on TodoDto {
Todo toTodo() {
return Todo(
userId: userId?.toInt() ?? -1,
id: id?.toInt() ?? -1,
title: title ?? '',
completed: completed ?? false
);
}
}
extension을 사용하는 이유
- DTO 클래스는 단순 직렬화, 역직렬화 기능에만 집중시킨다(코드 실수 방지를 위해 자동 생성 사용)
- mapper는 복잡한 로직이 포함될 수 있기 때문에 따로 분리하여 관리한다
freezed
모델 클래스 4종 메서드(==, hashCode, toString, copyWith)와 직렬화, 역직렬화를 자동 생성해주는 패키지이다
- json_serializable을 내장하고 있어서 필요 시 fromJson, toJson을 추가하면 된다
예시
import 'package:freezed_annotation/freezed_annotation.dart';
part 'photo_dto.freezed.dart';
part 'photo_dto.g.dart';
@freezed
abstract class PhotoDto with _$PhotoDto {
const factory PhotoDto({
dynamic id,
String? type,
String? title,
String? content,
String? url,
String? caption,
@JsonKey(name: "created_at") String? createdAt,
}) = _PhotoDto;
factory PhotoDto.fromJson(Map<String, dynamic> json) =>
_$PhotoDtoFromJson(json);
}
- @JsonKey는 json key와 필드명을 다르게 하고 싶을 때 사용하면 된다
- freezed.dart 파일은 4종 메서드, g.dart는 직렬화, 역직렬화 파일이다