Servie 계층까지 구현하고 나서, 엔티티가 가지고 있는 필드와 실제 화면에 넘여주고 싶은 데이터 필드 혹은 넘겨 받아야 하는 데이터 필드가 다름을 깨달았다.
엔티티는 DB 설계를 위한 도메인 모델이고, DTO는 클라이언트와 통신을 위한 데이터 모델이다. 엔티티와 DTO를 분리함으로써 엔티티 내부 구조가 외부에 그대로 노출되는 것을 방지하고 API 스펙이 변경되어도 엔티티를 보호할 수 있는 관심사 분리를 실천했다.
클라이언트로부터 요청을 받을 때 필요한 필드만 담은 요청DTO (RequestDto)와 클라이언트에 넘겨줄 필드만 담은 응답DTO (ReponseDto)를 생성하여 리팩토링 하였다.
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class UserJoinRequestDto {
@NotBlank (message = "이름은 필수 입력값입니다.")
private String name;
@NotBlank (message = "이메일은 필수 입력값입니다.")
@Email(message = "이메일 형식이 올바르지 않습니다.")
private String email;
@NotBlank(message = "비밀번호는 필수 입력값입니다.")
@Size(min = 8, message = "비밀번호는 8자 이상이어야 합니다.")
private String password;
}
유효성 검증 (Bean Validation)
@NotBlank@Size등 유효성 검증 어노테이션을 사용하여 데이터가 Service 계층에 도달하기 전 유효한 데이터가 들어왔는지 DTO 레벨에서 검증하도록 설계했다.
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserResponseDto {
private Long userId;
private String name;
private String email;
private String joinedDate;
public UserResponseDto(User user) {
this.userId = user.getId();
this.name = user.getName();
this.email = user.getEmail();
this.joinedDate = user.getJoinedDate().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
}
엔티티의 필드에 있는 값을 DTO 필드에 그대로 넣어서 DTO 를 생성 할 수 있도록 User 엔티티를 매개변수로 받는 생성자를 만들었다.
getJoinedDate().format()메서드를 사용하여 날짜를 원하는 포맷으로 변경하여 DTO에 넣었다.
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class BoardListResponseDto {
private Long boardId;
private String title;
private String writerName;
private String createdDate;
public BoardListResponseDto(Board board) {
this.boardId = board.getId();
this.title = board.getTitle();
this.writerName = board.getUser().getName();
this.createdDate = board.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
}
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class BoardDetailResponseDto {
private Long boardId;
private String title;
private String content;
private String createdDateTime;
private Long userId;
private String writerName;
private String writerEmail;
public BoardDetailResponseDto(Board board) {
this.boardId = board.getId();
this.title = board.getTitle();
this.content = board.getContent();
this.writerName = board.getUser().getName();
this.createdDateTime = board.getCreatedAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
this.userId = board.getUser().getId();
this.writerEmail = board.getUser().getEmail();
}
}
BoardDetailResponseDto - 상세 화면에서는 작성일자를 2026-04-27 12:00 형식으로,
BoardListResponseDto - 목록 화면에서 작성일자는 2026-04-27 으로 표현하였다.
@Getter
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CommentResponseDto {
private Long commentId;
private String content;
private String boardTitle;
private String writerName;
private String createdDateTime;
private Long boardId;
private String writerEmail;
public CommentResponseDto(Comment comment) {
this.commentId = comment.getId();
this.content = comment.getContent();
this.boardTitle = comment.getBoard().getTitle();
this.writerName = comment.getUser().getName();
this.createdDateTime = comment.getCreateAt().format(DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm"));
this.boardId = comment.getBoard().getId();
this.writerEmail = comment.getUser().getEmail();
}
}