DTO에 대한 구성 및 상속 사용


13

단일 페이지 응용 프로그램에 REST API를 제공하는 ASP.NET 웹 API가 있습니다. DTO / POCO를 사용하여이 API를 통해 데이터를 전달합니다.

문제는 이제 이러한 DTO가 시간이 지남에 따라 커지고 있다는 점입니다. 이제 DTO를 리팩토링하려고합니다.

DTO 디자인 방법에 대한 "모범 사례"를 찾고 있습니다. 현재 가치 유형 필드로만 구성된 작은 DTO가 있습니다.

public class UserDto
{
    public int Id { get; set; }

    public string Name { get; set; }
}

다른 DTO는이 UserDto를 구성별로 사용합니다. 예 :

public class TaskDto
{
    public int Id { get; set; }

    public UserDto AssignedTo { get; set; }
}

또한 다른 것들로부터 상속 받아 정의 된 확장 된 DTO가 있습니다. 예 :

public class TaskDetailDto : TaskDto
{
    // some more fields
}

일부 DTO는 여러 엔드 포인트 / 방법 (예 : GET 및 PUT)에 사용되었으므로 시간이 지남에 따라 일부 필드에 의해 점진적으로 확장되었습니다. 상속과 구성으로 인해 다른 DTO도 커졌습니다.

내 질문은 이제 상속과 구성이 모범 사례가 아닌가? 그러나 우리가 그것들을 재사용하지 않으면 동일한 코드를 여러 번 쓰는 것처럼 느낍니다. 여러 엔드 포인트 / 방법에 DTO를 사용하는 것은 좋지 않은가, 아니면 약간의 뉘앙스 만 다른 DTO가 있어야합니까?


6
어떻게해야하는지 말할 수는 없지만, DTO를 사용한 경험, 상속 및 구성은 나중에 당신을 나쁜 커피처럼 사냥하게됩니다. 다시는 DTO를 재사용하지 않습니다. 이제까지. 또한 비슷한 DTO를 DRY 위반으로 간주하지 않습니다. 동일한 표현 을 리턴 하는 두 개의 엔드 포인트는 동일한 DTO를 재사용 할 수 있습니다. 비슷한 표현 을 반환하는 두 개의 끝 점이 동일한 DTO를 반환하지 않으므로 각 DTO에 대해 특정 DTO를 만듭니다 . 내가 강제로 선택한다면, 작곡은 장기적으로 덜 문제가됩니다.
Laiv

@Laiv 이것은 질문에 대한 정답입니다. 왜 댓글로
올리 셨는지 모르겠습니다

2
@Laiv : 대신 무엇을 사용하십니까? 내 경험상,이 문제로 어려움을 겪고있는 사람들은 단순히 그것을 지나치게 생각하고 있습니다. DTO는 단지 데이터의 컨테이너 일뿐입니다.
Robert Harvey

내 주장은 주로 의견에 근거하기 때문에 TheCatWhisperer. 나는 여전히 내 프로젝트에서 이런 종류의 문제를 해결하려고 노력하고 있습니다. @RobertHarvey 사실, 내가 왜 실제보다 더 힘들게 보이는지 모르겠다. 나는 여전히 해결책을 찾고 있습니다. HAL이 이러한 문제를 해결하기위한 모델이라고 확신했지만, 답변을 읽으면서 DTO도 너무 세분화한다는 것을 깨달았습니다. 그래서 당신의 접근 방식을 먼저 실천하겠습니다. 변경 사항이 완전히 HATEOAS로 전환하는 것보다 덜 극적인 것입니다.
Laiv

도움이 될만한 좋은 토론을 위해 이것을 시도해보십시오 : stackoverflow.com/questions/6297322/…
johnny

답변:


10

가장 좋은 방법은 DTO를 가능한 한 간결하게 만들어보십시오. 반품해야 할 내용 만 반납하십시오. 필요한 것만 사용하십시오. 이것이 몇 가지 추가 DTO를 의미한다면 그렇게하십시오.

귀하의 예에서 작업에는 사용자가 포함되어 있습니다. 아마도 전체 사용자 객체가 필요하지 않을 수도 있고 작업이 할당 된 사용자의 이름 일 수도 있습니다. 나머지 사용자 속성은 필요하지 않습니다.

작업을 다른 사용자에게 재 할당하려고한다고 가정합니다. 재 할당 게시물 중에 전체 사용자 개체와 전체 작업 개체를 전달하려는 유혹이있을 수 있습니다. 그러나 실제로 필요한 것은 작업 ID와 사용자 ID입니다. 이것들은 작업을 재 할당하는 데 필요한 유일한 두 가지 정보이므로 DTO를 모델링하십시오. 이는 일반적으로 마른 DTO 모델을 위해 노력하는 경우 모든 휴식 통화에 대해 별도의 DTO가 있음을 의미합니다.

또한 때로는 상속 / 구성이 필요할 수 있습니다. 직업이 있다고 가정 해 봅시다. 작업에는 여러 작업이 있습니다. 이 경우 작업을 가져 오면 해당 작업의 작업 목록도 반환 될 수 있습니다. 따라서 구성에 대한 규칙은 없습니다. 모델링 대상에 따라 다릅니다.


가능한 한 간결한 것은 DTO가 세부 사항이 다르면 DTO를 다시 만들어야한다는 것을 의미합니다.
장교

1
@officer-일반적으로 그렇습니다.
Jon Raynor

2

시스템이 CRUD 작업을 기반으로하지 않는 한 DTO는 너무 세분화됩니다. 비즈니스 프로세스 또는 아티팩트를 구현하는 엔드 포인트를 작성하십시오. 이 접근 방식은 비즈니스 로직 계층과 Eric Evans의 "도메인 기반 디자인"에 잘 맞습니다.

예를 들어 송장에 대한 데이터를 반환하여 최종 사용자에게 화면이나 양식에 표시 할 수있는 끝 점이 있다고 가정합니다. CRUD 모델에서는 이름, 청구서 수신 주소, 운송 주소, 광고 항목 등 필요한 정보를 모으기 위해 엔드 포인트를 여러 번 호출해야합니다. 비즈니스 트랜잭션 컨텍스트에서 단일 엔드 포인트의 단일 DTO는이 모든 정보를 한 번에 리턴 할 수 있습니다.


로버트, 여기서 총근이 의미합니까?
johnny

현재 당사의 DTO는 양식에 대한 전체 데이터를 제공하도록 설계되었습니다 (예 : 할 일 목록을 표시 할 때 TodoListDTO에는 TaskDTO 목록이 있음). 그러나 TaskDTO를 재사용하고 있기 때문에 각 UserDTO도 있습니다. 따라서 문제는 백엔드에서 쿼리되어 유선으로 전송되는 대량의 데이터입니다.
장교

모든 데이터가 필요합니까?
Robert Harvey

1

"유연한"또는 DTO와 같은 추상에 대한 모범 사례를 확립하기는 어렵습니다. 기본적으로 DTO는 데이터 전송의 개체 일 뿐이지 만 대상 또는 전송 이유에 따라 다른 "모범 사례"를 적용 할 수 있습니다.

Martin Fowler의 Enterprise Application Architecture 패턴을 읽는 것이 좋습니다 . DTO가 실제로 자세한 섹션을 얻는 패턴에 관한 전체 장이 있습니다.

원래는 고가의 원격 호출에 사용되도록 "설계"되어 있으며,이 경우 로직의 다른 부분에서 많은 데이터가 필요할 수 있습니다. DTO는 한 번의 호출로 데이터를 전송합니다.

저자에 따르면, DTO는 지역 환경에서 사용하기위한 것이 아니지만 일부 사람들은 그 용도를 찾았습니다. 일반적으로 다른 POCO의 정보를 GUI, API 또는 다른 계층의 단일 엔티티로 수집하는 데 사용됩니다.

이제 상속을 통해 코드 재사용은 주요 목표가 아니라 상속의 부작용과 유사합니다. 반면에 구성은 코드 재사용을 주요 목표로 사용하여 구현됩니다.

어떤 사람들 은 둘 다의 강점을 사용하고 그들의 약점을 완화하려고 노력하면서 구성과 상속을 함께 사용하는 것이 좋습니다. 다음은 새로운 DTO 또는 그 문제에 대한 새로운 클래스 / 객체를 선택하거나 만들 때의 정신 과정의 일부입니다.

  • 동일한 계층 또는 동일한 컨텍스트 내에서 DTO로 상속을 사용합니다. DTO는 POCO에서 상속받지 않으며 BLL DTO는 DAL DTO에서 상속받지 않습니다.
  • DTO에서 필드를 숨기려고하면 리팩터링하고 컴포지션을 대신 사용합니다.
  • 기본 DTO와 다른 필드가 거의 필요한 경우 범용 DTO에 넣습니다. 범용 DTO는 내부적으로 만 사용됩니다.
  • 기본 POCO / DTO는 거의 모든 논리에 사용되지 않으므로 기본은 자녀의 요구에만 응답합니다. 만약베이스를 사용해야한다면, 아이들이 절대로 사용하지 않을 새로운 필드를 추가하지 마십시오.

그들 중 일부는 "최상의"관행이 아닐 수도 있고, 내가 작업 한 프로젝트에서 잘 작동하지만 크기가 모두 맞지 않는다는 것을 기억해야합니다. 범용 DTO의 경우 조심해야합니다. 내 메소드 서명은 다음과 같습니다.

public void DoSomething(BaseDTO base) {
    //Some code 
}

메소드 중 하나에 자체 DTO가 필요한 경우 상속을 수행하고 일반적으로 변경해야 할 유일한 매개 변수는 매개 변수이지만 때로는 특정 경우에 대해 더 깊이 파고들 필요가 있습니다.

귀하의 의견에 따르면 중첩 된 DTO를 사용하고 있습니다. 중첩 된 DTO가 다른 DTO 목록으로 만 구성된 경우 가장 좋은 방법은 목록 포장 풀기입니다.

표시하거나 작업해야하는 데이터의 양에 따라 데이터를 제한하는 새로운 DTO를 만드는 것이 좋습니다. 예를 들어, UserDTO에 많은 필드가 있고 1 또는 2 만 필요한 경우 해당 필드만으로 DTO를 갖는 것이 좋습니다. DTO의 계층, 컨텍스트, 사용법 및 유틸리티를 정의하면 DTO를 설계 할 때 많은 도움이됩니다.


답변에 3 개 이상의 링크를 추가 할 수 없습니다. 여기에는 로컬 DTO에 대한 추가 정보 가 있습니다. 나는 매우 오래된 정보를 알고 있지만 그 중 일부는 여전히 관련이 있다고 생각합니다.
IvanGrasp

1

DTO에서 컴포지션을 사용하는 것은 완벽하게 좋은 습관입니다.

구체적인 유형의 DTO 간의 상속은 나쁜 습관입니다.

우선 C #과 같은 언어에서 자동 구현 속성은 유지 관리 오버 헤드가 거의 없으므로 복제 (정말 중복을 피하는)가 종종 그렇게 해롭지는 않습니다.

한가지 이유 DTO간에 구체적인 상속을 사용 하지 않는 는 특정 도구가이를 행복하게 잘못된 유형으로 매핑하기 때문입니다.

예를 들어 Dapper와 같은 데이터베이스 유틸리티를 사용하는 경우 (권장하지 않지만 인기가 있습니다) class name -> table name 추론 파생 된 유형을 계층의 어딘가에 구체적인 기본 유형으로 저장하여 데이터를 잃거나 더 나빠질 수 있습니다. .

DTO간에 상속을 사용 하지 않는 더 깊은 이유 는 명백한 "is a"관계가없는 유형간에 구현을 공유하는 데 사용해서는 안된다는 것입니다. 내 생각에 TaskDetail의 하위 유형처럼 들리지 않습니다 Task. 그것은 쉽게 쉽게의 속성이 될 수 있습니다Task 이 될 수 있습니다.Task .

이제 걱정해야 할 것은 다양한 관련 DTO의 속성 이름유형 간에 일관성을 유지하는 것입니다 .

결과적으로 구체적인 유형의 상속은 이러한 일관성을 유지하는 데 도움이되지만 인터페이스 (또는 C ++의 순수 가상 기본 클래스)를 사용하여 이러한 종류의 일관성을 유지하는 것이 훨씬 좋습니다.

다음 DTO를 고려하십시오

interface IIdentity
{
    int Id { get; set; }
}

interface INamed
{
    string Name { get; set; }
}

public class UserDto: IIdentity, INamed
{
    public int Id { get; set; }

    public string Name { get; set; }

    // User specific properties
}

public class TaskDto: IIdentity
{
    public int Id { get; set; }

    // Task specific properties
}
당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.