[프로젝트] 개발자 포트폴리오 - 8. [백엔드] Request, Response DTO 생성

개발자 포트폴리오 프로젝트

8. [백엔드] Request, Response DTO 생성

전체 소스 - https://github.com/vividswan/Portfolio-For-Developers


DetailsType enum, DTO 생성

source commit - b14a433

DetailsType enum

package com.portfolio.backend.portfolio.type;

public enum DetailsType {
    portfolio, project
}

portfolio와 project 모두 TechStack을 일대다로 매핑하고 있기 때문에, 해당 enum으로 컨트롤러에서 둘을 구분한다.

Request DTO 객체

데이터를 생성하거나 수정할 때, 그에 맞는 자료를 DTO를 통해서 컨트롤러로 보내야 된다.
Entity 객체를 직접 보내거나 수정을 하면 데이터베이스에 예상치 못한 일이 발생할 수도 있고, 원하는 자료만 보낼 수도 있으며, Validation 처리를 할 수 있기 때문에 Request 객체를 만들어주자.
포트폴리오의 경우엔 Account가 생성될 때 그 계정의 포트폴리오를 만들어주기 때문에 생성 시의 Request는 필요 없다.
그러므로 수정 시에 필요한 PortUpdateRequest.java 객체만 만들어주었다.
커리어와 관련된 Request 객체는 CareerDataRequest.java 객체를 통해 생성과 수정을 할 수 있게 해주었으며, Career를 상속받는 Project, School, Company의 칼럼도 이 객체의 필드에 담았다.
기술 스택의 경우엔 TechStackCreateRequest.java로 생성을 하고 수정은 따로 만들지 않을 것이므로 해당 Request로 생성만 하게 해준다.
또한 서비스나 컨트롤러 객체에 코드가 몰리지 않도록 DTO 영역에 넘겨줄 수 있다.(CareerDataRequest.java 참고)

PortUpdateRequest.java

package com.portfolio.backend.portfolio.dto;

// ... import 생략

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class PortUpdateRequest {

    @NotBlank
    private String name;

    private String introduction;

    private String gitId;

    private String bojId;

    private String blogUrl;

    private String occupation;

    private String location;

    private String profileImage;

}

Portfolio와 일대다로 매핑된 커리어나 기술 스택의 업데이트는 해당 컨트롤러와 서비스 객체를 통해서 할 것이기 때문에 그 외 필드들을 선언하여 Request 객체를 만든다.

CareerDataRequest.java

package com.portfolio.backend.portfolio.dto;

// import 생략

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class CareerDataRequest {

    @NotNull
    private LocalDate startDate;

    private LocalDate endDate;

    @NotNull
    private String name;

    private String contents;

    private String url;

    private String department;

    private String major;

    private boolean graduate;

    public Project getProjectEntity(Account account, Portfolio portfolio) {
        Project project = Project.builder()
                .account(account)
                .portfolio(portfolio)
                .startDate(this.startDate)
                .endDate(this.endDate)
                .name(this.name)
                .contents(this.contents)
                .url(this.url)
                .build();
      return project;
    }

    public Company getCompanyEntity(Account account, Portfolio portfolio){
        Company company = Company.builder()
                .account(account)
                .portfolio(portfolio)
                .startDate(this.startDate)
                .endDate(this.endDate)
                .name(this.name)
                .contents(this.contents)
                .department(this.url)
                .build();
        return company;
        }

        public School getSchoolEntity(Account account, Portfolio portfolio){
            School school = School.builder()
                    .account(account)
                    .portfolio(portfolio)
                    .startDate(this.startDate)
                    .endDate(this.endDate)
                    .name(this.name)
                    .contents(this.contents)
                    .major(this.major)
                    .graduate(this.graduate)
                    .build();
            return school;
        }
} 

Career 객체의 경우엔 publc 한 getXXXEntity 메소드를 통해 빌더 패턴으로 엔티티 객체를 생성하는 일을 DTO 영역에 구현하였다.
Service 객체에서 해당 메소드를 사용하여 객체를 생성하고 Repository의 save 메소드로 Entity를 저장하여 커리어를 생성할 것이다.

TechStackCreateRequest.java

package com.portfolio.backend.portfolio.dto;

// ... import 생략

@Getter
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class TechStackCreateRequest {

    @NotNull
    private TechType techType;

    @NotNull
    private Long requestId;

    private DetailsType detailsType;


}

@NotNull과 같은 방식으로 해당 Request 객체를 통해 필수 값, 글자 수 등 필요한 Validation을 선언할 수 있다.

Response DTO 객체

api 요청에 대한 응답 역시 Entity 객체를 리턴하는 것보다 Response DTO 객체를 만들어서 클라이언트에게 전달해 주는 것이 좋다.
그렇지 않으면 이 역시 데이터베이스에 의도치 않은 영향이 발생할 수 있고, 보내고 싶은 데이터를 Response 객체로 선별하여 보낼 수 있다.
또한 양방향 참조를 막을 수 있는 좋은 방법이기도 하다.
어노테이션 선언이나 Entity 자체에서 일대다로 매핑된 필드의 Getter를 protected 접근제어자로 은닉화 시켜 엔티티를 리턴할 때 양방향 참조를 막는 방법도 있지만, Response DTO 객체를 만들어 해당 필드는 제외하는 것도 양방향 참조를 막을 수 있는 방법이다.
Portfolio Response, TechStack Response를 만들고 커리어의 경우엔 각각 Project, Company, School로 나누어 Response DTO를 만들었다.

PortfolioResponse.java

package com.portfolio.backend.portfolio.dto;

// ... import 생략

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class PortfolioResponse {

    private Long id;

    private Account account;

    private String accountNickname;

    private String name;

    private String introduction;

    private String email;

    private String gitId;

    private String bojId;

    private String blogUrl;

    private String occupation;

    private String location;

    private String profileImage;

    private Set<TechStack> techStacks;

    private List<Project> projects;

    private List<School> schools;

    private List<Company> companies;

    public PortfolioResponse(Portfolio portfolio){
        this.name = portfolio.getName();
        this.introduction = portfolio.getIntroduction();
        this.gitId = portfolio.getGitId();
        this.blogUrl = portfolio.getBlogUrl();
        this.occupation = portfolio.getOccupation();
        this.location = portfolio.getLocation();
        this.profileImage = portfolio.getProfileImage();
        this.techStacks = portfolio.getTechStacks();
        this.projects = portfolio.getProjects();
        this.schools = portfolio.getSchools();
        this.companies = portfolio.getCompanies();
    }

}

위와 같이 필요한 필드들을 선언하고 생성자에 Entity를 주입받는 방식으로 Response DTO를 만들었다.

this.techStacks = portfolio.getTechStacks();

처럼 필요한 일대다 매핑 필드를 할당하면 api 요청 시 그대로 일대다 객체가 리턴된다.

ProjectResponse.java, CompanyResponse.java, SchoolResponse도 위와 동일한 방식으로 만들었고, 각자가 리턴 받아야 할 데이터만 필드로 선언했다.

TechStackResponse.java

package com.portfolio.backend.portfolio.dto;

// ... import 생략

@Getter
@NoArgsConstructor
@AllArgsConstructor
public class TechStackResponse {

    private Long id;

    private TechType techType;

    public TechStackResponse(TechStack techStack){
        this.id = techStack.getId();
        this.techType = techStack.getTechType();
    }
}

TechStack의 경우에는 api의 응답 값으로 데이터를 받을 때 다대일로 연관돼있는 Portfolio나 Project를 다시 리턴해줄 필요가 없으므로 위와 같이 필드에서 제외하여 Response DTO를 만들었다.