본문 바로가기

Spring

API DTO 개수 관리하기

1. 문제 상황


프로젝트에서 API 스펙을 위한 DTO를 사용하고 있습니다.

API 스펙을 위한 DTO는 재사용이 불가능합니다. 클라이언트의 요구사항이 바뀌면 API 스펙 또한 바뀌게 되고, API 스펙을 정의한 DTO도 변하게 됩니다.

 

아래 코드를 보면 API 응답으로 JSON으로 반환하게 됩니다.

카페 스터디 목록 조회를 했을 때 응답으로 받는 API 스펙입니다.

카페 스터디에 대한 정보인 cafeStudyInfo, 작성자에 대한 정보인 wirterInfo,  카페에 대한 정보인 cafeInfo 가 존재합니다.

{
  "hasNext": true,
  "nextPage": 1,
  "content": [
    {
      "cafeStudyInfo": {
        "id": 1,
        "name": "스터디",
        "tags": [
          "DEVELOPMENT"
        ],
        "startDateTime": "2000-01-01T12:00:00",
        "endDateTime": "2000-01-01T14:00:00",
      },
      "writerInfo": {
        "id": 2,
        "nickname": "닉네임"
      },
      "cafeInfo": {
        "id": 3,
        "imgUrl": "카페 대표 이미지",
        "name": "스타벅스"
      }
    }
  ]
}

 

이 API 스펙을 DTO로 나타내볼까요?

public class CafeStudySearchListResponse {

    private boolean hasNext;
    private Integer nextPage;

    private CafeStudyInfo cafeStudyInfo;
    private WriterInfo writerInfo;
    private CafeInfo cafeInfo;
    
	.. 생성자 생략
}

public class CafeStudyInfo {

    private Long id;
    private String name;
    private List<Tag> tags;
    
    .. 생성자 생략
}

public class WriterInfo {

    private Long id;
    private String nickname;
    
    .. 생성자 생략
}

public class CafeInfo {

    private Long id;
    private String imgUrl;
    private String name;
    
    .. 생성자 생략
}

 

위 코드를 보면 하나의 API 응답 스펙을 만들기 위해서, API 응답 전체를 감싸는 DTO 1개, 카페 스터디에 대한 정보 1개,  작성자에 대한 정보 1개, 카페에 대한 정보 1개로 총 4개의 DTO를 생성해야합니다.

 

물론 이렇게 나누지 않고, 하나의 DTO 안에 필드로 쭉 나열할 수도 있겠죠? 쭉 나열하는 방식은 API 응답을 받는 클라이언트에서 데이터 관리가 어렵기 때문에 관련된 정보끼리 그룹을 지어 나눴습니다.

 

하나의 API 를 위해 4개의 DTO가 생성되었습니다.

하나의 API 에서 DTO가 4개가 만들어졌는데요. 프로젝트 규모가 커지면 커질 수록 관리해야할 DTO 의 개수는 엄청나게 증가하게 됩니다. 그렇다고 DTO 를 재사용할 수 도 없는 상황이죠? 재사용하게되면 나중에 클라이언트의 요구사항이 변경되었을 때 다른 API 스펙이 변경되는 참사를 만나게 될 거예요.

 

그럼 이제 API 를 위한 DTO 개수도 관리해봅시다.

 

2. 해결책


해결책은 간단합니다.

static class를 사용하겠습니다.

정적 클래스를 이용하여 하나의 클래스 안에서 여러 클래스를 포함하도록 할건데요.

 

변경된 코드는 아래와 같습니다.

public class CafeStudySearchListResponse {

    private boolean hasNext;
    private Integer nextPage;

    private CafeStudyInfo cafeStudyInfo;
    private WriterInfo writerInfo;
    private CafeInfo cafeInfo;
    
	.. 생성자 생략
    
    public static CafeStudySearchListResponse from(CafeStudy cafeStudy) {
        CafeStudySearchListResponse response = new CafeStudySearchListResponse();
        
        response.cafeStudyInfo = new CafeStudyInfo(...)
        response.writerInfo = new WriterInfo(...);
        response.cafeInfo = new CafeInfo(...);

        return response;
    }

    private static class CafeStudyInfo {

        private Long id;
        private String name;
        private List<Tag> tags;
    
        .. 생성자 생략
    }

    private static class WriterInfo {

        private Long id;
        private String nickname;
    
        .. 생성자 생략
    }

    private static class CafeInfo {

        private Long id;
        private String imgUrl;
        private String name;
    
        .. 생성자 생략
    }
}

 

API 응답 스펙을 감싸는 클래스 내부에서 나머지 클래스에 대해 private 정적 클래스로 정의했습니다.

private 으로 정의한 이유는 외부에서 정적 클래스에 대해 접근하지 못하도록 하기 위함입니다.

정적 팩토리 메서드를 이용하여 엔티티를 받아서 API 스펙에 맞는 데이터를 초기화 시키도록 합니다. 

 

이렇게 작성할 경우, 이전보다 API 스펙을 나타내는 클래스 내부가 복잡해집니다.

하지만, 여러개의 DTO를 관리했을 때 보다, 응집도가 높아지게 됩니다. 여러개의 DTO로 관리할 경우에는 여러 클래스로 분리되어있기 때문에 어느 API에서 사용하는 DTO 인지 확인해야합니다. 

 

하나의 DTO에서 정적 클래스로 관리할 경우에는 복잡도는 증가하지만 응집도는 높아집니다. API 스펙이 바뀌어도 한눈에 파악할 수 있는 장점이 존재합니다.