DTO, DAO, VO가 필요한 이유

나는 기존 JAVA의 Spring boot를 사용해 개발을 하는 과정에서는 배웠던 내용 그대로 dto, dao, vo 등을 사용해 개발을 진행했었다. 하지만 현재 typescript와 nest js를 활용해 개발을 하는 과정에서 빠르게 개발하다보니 단순히 Entity만 만들고 이를 활용한 CRUD 작업을 진행했다.

이때 이상하게 생각했던게 dto나 dao같은 객체 디자인 패턴을 사용하지 않아도 문제없이 코드가 돌아갔기에 이런 패턴들의 필요성에 대해 궁금해졌다.

각 패턴에 대한 간단한 정의부터 알고가자.

개념 정의

DTO란?

DTO는 Data Transfer Object의 준말로 글자 그대로 데이터 전달을 위한 객체를 의미한다.

DTO는 데이터의 구조를 정의 및 검증하여 일관성있는 데이터 전달을 가능하게 한다.

DAO란?

DAO는 Data Access Object의 준말로 글자 그대로 데이터 접근을 위한 객체를 의미한다.

간단하게 데이터베이스 CRUD 작업을 관리하는 객체라고 생각하면 쉽다.

VO란?

VO는 Value Object의 준말로 글자 그대로 값을 표현하기 위한 객체를 의미한다.

필요성

DTO 필요성

  1. 반복적인 데이터 유효성 검사를 한 번에

    DTO를 사용하지 않아도 코드 상에서 직접 데이터 유효성을 검사할 수 있다. 하지만 이런 경우 매번 코드에서 데이터 유효성 검사를 하는 부분을 포함해야한다. 즉, DTO를 통해 반복되는 데이터 유효성 검사를 DTO 하나의 코드로 해결할 수 있는 것이다. 이는 중복 코드를 방지하고 일관성을 유지하는데 도움을 준다.

    ex)

    • DTO 없이 직접 유효성 검사하는 코드
       if (!name || typeof name !== 'string' || name.length > 50) {
       throw new Error('Invalid name');
       }
       if (!email || !email.includes('@')) {
       throw new Error('Invalid email');
       }
       // 이런 유효성 검사를 해당 데이터를 사용할 때마다 진행해야함
      
    • DTO 사용해 유효성 검사하는 코드 ```typescript import { IsString, IsEmail, Length } from ‘class-validator’;

    export class CreateUserDto { @IsString() // 유효성 검사 부분! @Length(1, 50) // 유효성 검사 부분! name: string;

     @IsEmail() // 유효성 검사 부분!
     email: string;  }  ```
    
  2. 민감한 데이터는 숨기기

    기본적으로 ORM을 이용해 데이터베이스를 조회한다고 가정했을 때 Entity라는 실제 데이터베이스 테이블과 1:1로 맵핑되는 객체를 이용하게 된다. 이때 Entity는 실제 테이블의 모든 정보를 받아야하므로 민감한 정보를 숨기기 어렵다.

    하지만 DTO를 사용하면 민감한 정보(보여주고 싶지 않은 컬럼)은 제외하여 설정하면 되므로 보안을 강화할 수 있다.

    ex)

    • password만 숨겨서 확인하기 ```typescript // Entity 코드 중 일부 export class User { id: number; name: string; email: string; password: string; // 민감한 정보 }

    // DTO 코드 중 일부 export class UserResponseDto { id: number; name: string; email: string; // password 필드는 포함하지 않음! } ``` 이렇게 보여줄 필요없는 데이터는 아예 제외하여 설정하면 된다.

  3. API 문서화에 용이

    DTO를 활용하면 Swagger와 같은 API 문서를 손쉽게 만들 수 있도록 되어 있는 경우가 많다. nest js의 경우 @nestjs/swagger의 ApiProperty 데코레이터를 사용해 swagger 파일에 들어갈 내용을 작성할 수 있다.

    ex)

    • nest js swagger 설정
     import { ApiProperty } from '@nestjs/swagger';
    
     export class UserDto {
         @ApiProperty({ example: 1, description: 'The unique identifier of the user' })
         id: number;
    
         @ApiProperty({ example: 'John Doe', description: 'The name of the user' })
         name: string;
    
         @ApiProperty({ example: 'john.doe@example.com', description: 'The email address of the user' })
         email: string;
     }
    

DAO 필요성

DAO를 사용하는 이유는 CRUD 작업과 비즈니스 로직 분리할 수 있기 때문이다. 이때 얻을 수 있는 장점을 설명해본다.

  1. 관심사 분리

    단순 CRUD 작업과 이외의 비즈니스 로직을 분리하여 두 작업을 명확하게 구분해서 볼 수 있다는 장점이 있다.

    ex)

    • DAO 사용해 Service 코드 작성
       import { Injectable } from '@nestjs/common';
       @Injectable()
       export class UserService {
        constructor(private readonly userDao: UserDao) {}
      
        async registerUser(email: string, name: string): Promise<User> {
            // 비즈니스 로직: 이메일 유효성 검사
            const regex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
            if (!regex.test(email);) {
                throw new Error('Invalid email address');
            }
      
            // CRUD 작업
            const existingUser = await this.userDao.findByEmail(email);
            // 비즈니스 로직: 이메일 중복 체크
            if (existingUser) {
                throw new Error('Email already in use');
            }
      
            // CRUD 작업
            const user = new User();
            user.email = email;
            user.name = name;
            user.isActive = true;
            user.createdAt = new Date();
            return this.userDao.createUser(user);
        }
       }
      
  2. 코드 유지보수성 향상

    만약 같은 CRUD 작업을 여러 서비스 코드에서 사용 중일때, 데이터베이스 구조나 쿼리 방식이 변경된다면 모든 서비스 코드에서 해당 CRUD 코드를 수정해야 한다.

    하지만 DAO를 이용해 캡슐화한다면 DAO 코드 하나만 수정하면 된다는 장점이 있다. 또한 중복되는 CRUD 코드를 줄일 수 있다.

results matching ""

    No results matching ""