프로젝트 및 개발적 고민/사색

Entity, DTO, DAO, VO, Repository와 효율과 책임에 대한 고민

최지철 2024. 8. 20. 11:30
728x90
반응형

고민의 시작

요새 프로젝트들 마다 클린 아키텍처를 적용하여, 진행중인데 문뜩 각 레이어에 대한 개념정리와 용어정리 그리고 혼자만의 고민이 있어 정리해야겠다는 생각이 들어, 해당 포스트를 작성한다.

 

클린 아키텍처(Clean Architecture)에서는 각 레이어가 명확한 책임을 가지고 있으며, 그 책임에 따라 다양한 개념들이 사용됩니다.

 

클린 아키텍처를 구성하는 핵심 레이어는 크게 Data, Domain, Present 이렇게 3개로 나뉘고 그 다음 UseCase, Interface, Repostiory등이 안에 위치해있습니다..

이번에는 제목과 같이 Entity, DTO, DAO, VO, Repository에 대해 적을려고 합니다.

 

1. Entity

Entity는 도메인 모델의 핵심을 이루는 비즈니스 객체. 이 객체들은 시스템의 상태를 나타내며, 비즈니스 로직을 포함하고 있으며. 엔터티는 변경 가능하며, 시스템의 수명 동안 지속될 수 있는 상태를 가지고 있습니다.

  • 예시: 사용자를 나타내는 User 엔터티는 이름, 이메일, 비밀번호와 같은 속성을 가지며, 비즈니스 규칙(예: 비밀번호 유효성 검사)을 포함할 수 있습니다.
  • 위치: 도메인 레이어

2. DTO (Data Transfer Object)

DTO(Data Transfer Object)는 계층 간의 데이터 교환을 위해 사용되는 객체. DTO는 엔터티의 일부분을 전달하거나, 여러 엔터티의 데이터를 조합하여 전달할 때 사용됩니다. DTO는 일반적으로 로직을 포함하지 않으며, 순수하게 데이터를 담는 역할을 한다.

  • 예시: 네트워크 API에서 서버로부터 받아온 JSON 데이터를 UserDTO로 변환하여 유스케이스 레이어로 전달합니다.
  • 위치: Data Layer

3. DAO (Data Access Object)

DAO(Data Access Object)는 데이터베이스나 파일 시스템, 네트워크 등을 통해 영속성 계층에 접근하기 위한 객체입니다. DAO는 데이터를 CRUD(Create, Read, Update, Delete)할 수 있는 메서드를 제공하며, 엔터티와 데이터를 직접적으로 변환하거나 조작하는 역할을 합니다.

  • 예시: UserDAO는 사용자 정보를 검색하거나 업데이트하는 메서드를 가질 수 있습니다.
  • 위치: Data Layer

4. VO (Value Object)

VO(Value Object)는 엔터티와 달리 불변(immutable) 객체이며, 고유한 식별자가 없습니다. VO는 두 개 이상의 속성을 묶어 하나의 의미 있는 값을 표현하기 위해 사용됩니다. VO는 일반적으로 작은 단위의 불변 객체로, 값의 동등성(equality)을 판단하는 기준으로 사용됩니다.

  • 예시: Money 객체는 amount와 currency를 가지며, 이를 사용해 계산을 할 수 있습니다. Money는 VO로서 금액과 통화를 나타냅니다.
  • 위치: Domain Layer

5. Repository

Repository는 엔터티의 집합처럼 보이도록 추상화된 객체입니다. Repository는 엔터티를 저장하거나 검색하는 데 사용되며, 도메인 모델과 데이터베이스 간의 중재자 역할을 합니다. Repository는 DAO와 달리 도메인 객체를 처리하며, 비즈니스 로직과 데이터 소스를 분리하기 위한 인터페이스를 제공합니다.

  • 예시: UserRepository는 User 엔터티를 검색, 저장하는 메서드를 제공하며, DAO와 상호작용하여 데이터를 영속화합니다.
  • 위치: 도메인 레이어와 데이터 계층 간의 중간인 Interface Layer

레이어 간 관계

  • Entity는 시스템의 핵심 비즈니스 로직을 담고 있으며, 이 로직은 유스케이스 레이어를 통해 사용됩니다.
  • DTO는 인터페이스 어댑터 레이어나, 데이터 레이어 주로 사용되며, 유스케이스와 프레젠테이션 레이어 간의 데이터 전송을 담당합니다.
  • DAO는 데이터 레이어에서 영속성 계층과 상호작용하며, 데이터베이스와의 CRUD 작업을 수행합니다.
  • VO는 엔터티의 속성을 의미 있게 묶기 위해 사용되며, 도메인 레이어에서 엔터티와 함께 사용됩니다.
  • Repository는 DAO를 감싸며, 도메인 엔터티와 데이터 소스 간의 변환 및 비즈니스 로직을 처리합니다.

 

효율성과 책임 대한 고민

struct MoviesResponseDTO: Decodable {
    private enum CodingKeys: String, CodingKey {
        case page
        case totalPages = "total_pages"
        case movies = "results"
    }
    let page: Int
    let totalPages: Int
    let movies: [MovieDTO]
}
extension MoviesResponseDTO {
    func toDomain() -> MoviesPage {
        return .init(page: page,
                     totalPages: totalPages,
                     movies: movies.map { $0.toDomain() })
    }
}

클린 아키텍처를 적용한 프로젝트마다 데이터 흐름을 명확히 하고 책임을 분리하기 위해, 일반적으로 DTO -> Entity로의 맵핑을 통해 데이터를 처리합니다. 예를 들어, 네트워크에서 받아온 데이터를 DAO에서 DTO로 변환하고, 이를 다시 Entity로 변환한 뒤 Repository를 통해 유스케이스나 리엑터/뷰모델로 전달하는 방식입니다.

그러나 최근 들어 이러한 접근 방식에 대해 살짝 의문이 생겼습니다.

DTO와 Entity가 구조적으로 똑같을 때, 굳이 Entity를 써야 하는지 말입니다. 
그 이유는 당근마켓에서는 별다른 비지니스 로직이 없는 상황이라면, 유스케이스를 생략하기도 한다더라..(진짜.. 카더라...ㅎ) 라는 말을 듣고 효율 및 복잡성에 대해 고민을 하면서 이런 고민도 하게 된게 아닌가 싶습니다.

DTO와 Entity 맵핑의 단점

1. 불필요한 변환 비용

  • 단순히 데이터를 전달하는 경우, DTO와 Entity 간 변환이 불필요한 오버헤드가 될 수 있습니다. 예를 들어, 네트워크에서 받아온 데이터를 그대로 사용하는 경우라면, DTO를 직접 사용해도 충분할 수 있습니다. 특히 비즈니스 로직이 거의 없거나 간단한 경우, DTO에서 Entity로의 변환 과정은 오히려 비효율적일 수 있습니다.

2. 코드 복잡도 증가

  •  DTO와 Entity가 똑같은 구조를 가지면서, 이를 변환하기 위한 맵핑 코드가 추가되면, 코드베이스가 불필요하게 복잡해질 수 있습니다. 클린 아키텍처의 목적 중 하나는 유지보수성을 높이고 의존성을 줄이는 것인데, 작은 프로젝트나 단순한 데이터 구조에서는 이와 반대로 불필요한 복잡성을 초래할 수 있습니다.

DTO와 Entity 맵핑의 장점

1. 데이터가 변경되면 DTO만 변경하고, 실제 여러 계층에서 의존성을 갖는 엔티티 객체는 바꾸지 않아도 된다

  • Entity는 도메인 로직을 캡슐화하고, 시스템의 비즈니스 규칙을 표현하는 핵심 객체입니다. 이 엔티티가 변경된다는 것은 비즈니스 로직에 영향을 미치는 변경이 발생했다는 것을 의미합니다. 따라서, DTO와 Entity를 분리하면, 데이터 구조의 변경이 발생해도 Entity는 그대로 두고 DTO만 수정하면 되기 때문에, 불필요한 엔티티 변경으로 인한 영향을 최소화할 수 있습니다.
  • 예를 들어, API 응답 형식이 변경되어 필드가 추가되거나 삭제된다고 해봅시다. DTO를 통해 이러한 변경 사항을 흡수하고, Entity는 그대로 유지할 수 있습니다. 이렇게 하면, Entity를 사용하는 모든 계층에서의 수정 작업을 피할 수 있습니다.

2. Entity를 어떤 계층에도 영향받지 않는 독립적인 객체로 유지할 수 있다.

  • Entity는 도메인 계층의 가장 중요한 구성 요소로, 어떤 외부 의존성이나 변경에도 영향받지 않도록 독립적으로 유지해야 합니다. 이는 Entity를 통해 도메인 모델을 표현하고, 이 모델을 기반으로 시스템의 핵심 비즈니스 로직이 동작하기 때문이죠.
  • 예를 들어, 네트워크 계층에서 새로운 필드를 추가했다고 생각해봅시다. Entity가 독립적으로 유지되기 때문에, 이 새로운 필드가 Entity에 영향을 미치지 않게 할 수 있습니다. 이는 프레젠테이션 계층, 도메인 계층에서의 연쇄적인 변경을 막아주는 역할을 할 수 있습니다.

 

고민의 결론

프로젝트의 특성과 복잡성에 따라 DTO와 Entity를 분리하는 것이 이득일 수도, 아닐 수도 있다.
그러나, 프로젝트는 시간이 지남에 따라(사라지지 않는다는 전제하에) 유지보수 및 신규기능이 추가 되기 때문에 점점 비대해질 수 밖에 없다.  DTO와 Entity가 현재 동일한 구조를 가지더라도, 책임의 명확한 분리미래의 확장성을 위해 여전히 Entity를 사용하는 것이 중요한것 같다. DTO와 Entity를 분리해 두면, 각 객체가 자신의 역할에 충실하게 설계되고 유지될 수 있으며, 시스템의 복잡성을 줄이고, 유지보수성을 높일 수 있다. 결국, 이는 코드의 안정성과 확장성을 보장하는 데 큰 도움이 될 것같다. ㅎㅎㅎ..(쓸데 없는 고민이었을지도.)

  1.  
728x90
반응형