C# - DTO

MINO·2025년 9월 10일
0

C#

목록 보기
4/7
post-thumbnail

DTO

Data Transfer Object, 데이터 전송 객체

  • 계층 간 데이터 전달을 위해 데이터를 전환하는 브릿지 역할
  • 로직은 거의 없고, 데이터 필드만 가지고 있다.

DTO 는 단독으로 사용되기 보다, 다른 구조적 요소들과 함께 사용되는 경우가 많다.
주로 MVC, Entity, Repository 개념과 함께 사용된다.
간단히 위 개념들을 알아보자.


MVC

Model-View-Controller 로 역할을 분리하여 코드 유지보수와 확장성을 높이는 데 사용되는 패턴

  • Model (Data) : 데이터와 비즈니스 로직
  • View (UI) : 사용자에게 보여지는 화면
  • Controller (Data ↔ UI) : 사용자 요청 처리, Model 과 View 를 연결

MVC 의 동작 흐름
1. 사용자 요청 : Controller 진입
2. Controller 가 Model 을 사용해 데이터 처리
3. 처리 결과를 View 에 전달
4. View 가 사용자에게 결과 화면 렌더링

MVC - Model

사용자 정보 데이터 구조를 정의

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;
        }
    }
}

MVC - View

사용자에게 데이터를 보여주기 위해 화면에 출력

using ConsoleApp1.Model;

namespace ConsoleApp1.View
{
    internal class UserView
    {
        public void PrintUI(User user)
        {
            Console.WriteLine($"User Id : {user.Id}");
        }
    }
}

MVC - Controller

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 을 통해 패키지를 설치해주어야 한다.


NuGet

C#/.NET 용 패키지 관리 시스템

  • 개발자가 만든 라이브러리, 툴, 패키지를 다른 프로젝트에서 쉽게 설치할 수 있게 도와준다.
  • 패키지 설치 : 라이브러리를 다운로드 하고 프로젝트에 추가
  • 버전 관리 : 프로젝트에서 사용하는 패키지 버전 관리
  • 의존성 관리 : 설치한 패키지가 다른 패키지에 의존할 경우 자동으로 설치

  1. Visual Studio 에서 NuGet 패키지 관리 클릭
  1. 필요한 라이브러리 (JSON) 을 입력하고 설치
  1. 설치된 라이브러리를 확인

NuGet - Newtonsoft.Json

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) 가 따로 필요하다.


Entity

데이터베이스의 구조와 동일하게 C# 에서 정의된 객체

  • DB 데이터를 담는 용도
  • DB 매핑 : 클래스 속성이 테이블 컬럼과 1:1 로 대응
  • 비즈니스 로직 : 필요에 따라 데이터 검증, 계산 등의 로직 포함

User → UserEntity

내부 저장소에 쓰일 정보를 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;
        }
    }
}

User → UserAPI

외부 사용자에게 보여주기 위한 정보를 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;
        }
    }
}

UserEntity , UserAPI

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 에는 포함되지 않아 노출을 방지


Repository

데이터 접근 로직을 별도의 계층으로 분리

  • DB 접근 코드를 모아두고, Repository 를 통해서만 데이터에 접근하도록 유도
  • Controller 나 Service 는 Repository 를 통해서만 데이터에 접근

UserRepository

UserEntity 를 DB 저장소에 저장

using ConsoleApp1.Model;

namespace ConsoleApp1.Controller
{
    internal class UserRepository
    {
        public void Save(UserEntity user)
        {
            Console.WriteLine($"User {user.Id} 를 DB 에 저장");
        }
    }
}

MVC - DTO 관계

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);
    }
}

MVC - DTO

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 를 활용하여 깔끔한 코드와 유지보수가 쉽게 설계해보자.

profile
안녕하세요 게임 개발하는 MINO 입니다.

0개의 댓글