Data Transfer Object, 데이터 전송 객체
DTO 는 단독으로 사용되기 보다, 다른 구조적 요소들과 함께 사용되는 경우가 많다.
주로 MVC, Entity, Repository 개념과 함께 사용된다.
간단히 위 개념들을 알아보자.
Model-View-Controller 로 역할을 분리하여 코드 유지보수와 확장성을 높이는 데 사용되는 패턴
MVC 의 동작 흐름
1. 사용자 요청 : Controller 진입
2. Controller 가 Model 을 사용해 데이터 처리
3. 처리 결과를 View 에 전달
4. View 가 사용자에게 결과 화면 렌더링
사용자 정보 데이터 구조를 정의
namespace ConsoleApp1.Model
{
internal class User
{
public readonly int Id;
public readonly string Name;
public readonly string Desc;
public User(int id, string name, string desc)
{
Id = id;
Name = name;
Desc = desc;
}
}
}
사용자에게 데이터를 보여주기 위해 화면에 출력
using ConsoleApp1.Model;
namespace ConsoleApp1.View
{
internal class UserView
{
public void PrintUI(User user)
{
Console.WriteLine($"User Id : {user.Id}");
}
}
}
Model 과 View 를 연결, 사용자 요청을 처리
using ConsoleApp1.Model;
using ConsoleApp1.View;
namespace ConsoleApp1.Controller
{
internal class UserController
{
public void PrintWindow()
{
User user = new User(123, "Kim", "Programmer");
UserView userView = new UserView();
userView.PrintUI(user);
}
}
}
이렇게 MVC 패턴으로 구조를 나눠 작업하니 편리하여
로컬 저장소에 저장하고, 외부에 전달할 수 있게 기능을 추가해보려고 한다.
이때, 사용자 User 데이터를 JSON 으로 변환하기 위해서는
NuGet 을 통해 패키지를 설치해주어야 한다.
C#/.NET 용 패키지 관리 시스템
JsonConvert 를 통해 User 데이터를 String 타입의 Key - Value 쌍으로 변환
using ConsoleApp1.Model;
using Newtonsoft.Json;
namespace ConsoleApp1.Controller
{
internal class UserAPIController
{
public void PrintAPI(User user)
{
Console.WriteLine($"User {user.Id} 를 API 로 제공");
string toJson = JsonConvert.SerializeObject(user);
Console.WriteLine(toJson);
}
}
}
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
UserAPIController userAPIController = new UserAPIController();
User user = new User(id: 321, name: "Choi", desc: "Programmer");
userAPIController.PrintAPI(user);
}
}
}
실제 유저 정보에는 Id 와 Name, Desc, 그리고 비밀번호와 같은 개인 정보도 필수적이다.
User 클래스에 string Password 를 추가해주었다.
internal class User
{
public readonly int Id;
public readonly string Name;
public readonly string Desc;
public readonly string Password;
public User(int id, string name, string desc, string password)
{
Id = id;
Name = name;
Desc = desc;
Password = password;
}
}
이후, 새로운 User 를 생성하여 저장하고 API 를 통해 외부로 반환하면,
User user = new User(id: 321, name: "Choi", desc: "Programmer", password : "asd123");
UserRepository userRepository = new UserRepository();
userRepository.Save(userEntity);
UserAPIController userAPIController = new UserAPIController();
userAPIController.PrintAPI(user);
개인 정보인 Password 도 외부에 노출된다.
따라서, 내부 저장소에 쓰일 데이터(Entity) 와 외부 사용자에게 보여주기 위한 모델(API) 가 따로 필요하다.
데이터베이스의 구조와 동일하게 C# 에서 정의된 객체
내부 저장소에 쓰일 정보를 UserEntity 구조로 사용
namespace ConsoleApp1.Model
{
internal class UserEntity
{
public readonly int Id;
public readonly string Name;
public readonly string Desc;
public readonly string Password;
public UserEntity(int id, string name, string desc, string password)
{
Id = id;
Name = name;
Desc = desc;
Password = password;
}
}
}
외부 사용자에게 보여주기 위한 정보를 UserAPI 구조로 사용
namespace ConsoleApp1.Model
{
internal class UserAPI
{
public readonly int Id;
public readonly string Name;
public readonly string Desc;
public UserAPI(int id, string name, string desc)
{
Id = id;
Name = name;
Desc = desc;
}
}
}
using ConsoleApp1.Controller;
using ConsoleApp1.Model;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
UserEntity userEntity = new UserEntity(321,"Choi","Programmer","asd123");
UserRepository userRepository = new UserRepository();
userRepository.Save(userEntity);
// DB 에서 받아왔다고 가정
UserAPI userAPI = new UserAPI(userEntity.Id, userEntity.Name, userEntity.Desc);
UserAPIController userAPIController = new UserAPIController();
userAPIController.PrintAPI(userAPI);
}
}
}
유저 개인 정보인 Password 는 UserAPI 에는 포함되지 않아 노출을 방지
데이터 접근 로직을 별도의 계층으로 분리
UserEntity 를 DB 저장소에 저장
using ConsoleApp1.Model;
namespace ConsoleApp1.Controller
{
internal class UserRepository
{
public void Save(UserEntity user)
{
Console.WriteLine($"User {user.Id} 를 DB 에 저장");
}
}
}
Controller가 클라이언트(View 또는 API)로 데이터를 전달할 때,
Model 을 그대로 노출하지 않고 필요한 데이터만 담아서 전달하기 위해 DTO 를 활용
Ex) 유저 정보를 전달할 때, 필수 정보만 전달하고
불필요한 정보 Password 등은 DTO 를 통해 숨김
using ConsoleApp1.Model;
namespace ConsoleApp1.Dto
{
internal class UserDto
{
public readonly int Id;
public readonly string Name;
public readonly string Desc;
public readonly string Password;
public UserDto(int id, string name, string desc, string password)
{
Id = id;
Name = name;
Desc = desc;
Password = password;
}
public UserEntity ToEntity()
{
return new UserEntity(id : Id, name : Name, desc : Desc, password : Password);
}
public UserAPI ToAPI()
{
return new UserAPI(id: Id, name: Name, desc: Desc);
}
}
}
internal class UserAPI
{
...
public UserDto ToDto()
{
return new UserDto(Id, Name, Desc, "");
}
}
internal class UserEntity
{
...
public UserDto ToDto()
{
return new UserDto(Id, Name, Desc, Password);
}
}
using ConsoleApp1.Controller;
using ConsoleApp1.Dto;
using ConsoleApp1.Model;
namespace ConsoleApp1
{
internal class Program
{
static void Main(string[] args)
{
// DB
UserEntity userEntity = new UserEntity(321,"Choi","Programmer","asd123");
UserRepository userRepository = new UserRepository();
userRepository.Save(userEntity);
// Dto
UserDto userDto = new UserDto(userEntity.Id, userEntity.Name,
userEntity.Desc, userEntity.Password);
// API
UserAPIController userAPIController = new UserAPIController();
userAPIController.PrintAPI(userEntity.ToDto().ToAPI());
}
}
}
계층 간 데이터 전달, API 응답 등이 필요한 경우,
MVC 와 DTO 를 활용하여 깔끔한 코드와 유지보수가 쉽게 설계해보자.