2024.10.07.(월)
5주차
질문 CRUD를 작성하는 시간!
질문 도메인 작성
import lombok.*;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter
@Table(name = "questions")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Question extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Column(columnDefinition = "TEXT", nullable = false)
private String subject;
@Column(columnDefinition = "TEXT", nullable = false)
private String content;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "author_id")
private User author;
// TODO: 질문 리스트 매핑
public Question(
String subject,
String content,
User author
) {
// id는 JPA가 자동으로 생성하여 DB에 저장
this.subject = subject;
this.content = content;
this.author = author;
}
}
Entity에 protected 생성자를 만드는 이유
NoArgsConstructor(access = AccessLevel.PROTECTED)
- 기본 생성자의 접근제어를 protected로 설정하여 무분별한 객체 생성에 대해 체크하기 위함이다.
- private가 더 안전하지 않나요?
- JPA 표준 스펙에 디폴트 생성자가 있어야 하므로 private이 될 수 없다.
- JPA가 프록시 기술을 사용할 때 Jpa hibernate가 객체를 생성하는데, private을 사용하면 이러한 방식이 수행될 수 없다.
빌더 패턴 (Builder Pattern)
-
해당 도메인의 경우, 인스턴스 필드가 많지도 않아서 빌더 패턴이 아닌 생성자를 사용하였다. 그러나 빌더 패턴이 익숙하다면 빌더 패턴을 사용해도 된다.
-
빌더 패턴이란?
- 별도의 Builder 클래스를 만들어 메서드를 통해 순차적으로 값을 입력받은 후, 최종적으로 build() 메소드를 통해 하나의 인스턴스를 생성하여 반환하는 패턴이다.
- 별도의 Builder 클래스를 만들어서 사용해도 되지만, Lombok의 @Builder 어노테이션을 사용하자.
- 점층적 생성자 패턴과 자바 빈즈(Java Beans) 패턴의 장점만 취합한 패턴이라고 볼 수 있다.
- 간단한 예시
Question question = new Question.Builder() .subject("질문 있어용") .content("모르게써용") .author("하하") .build();
- 별도의 Builder 클래스를 만들어 메서드를 통해 순차적으로 값을 입력받은 후, 최종적으로 build() 메소드를 통해 하나의 인스턴스를 생성하여 반환하는 패턴이다.
-
빌더 패턴의 장점
- 생성자 오버로딩 열거를 하지 않아도 된다.
- 데이터의 순서에 제약을 받지 않고 객체를 만들 수 있다. -> 생성자 파라미터 순서를 파악하지 않아도 된다.
- 객체의 생성 과정을 일관된 프로세스로 표현할 수 있다.
- 생성자 방식 대신 빌더 패턴을 사용하는 이유이다.
- 생성자 방식으로 객체를 생성하면 파라미터가 많아질수록 가독성이 심히 안좋아진다.
- 빌더 패턴을 적용하면 어떤 데이터에 어떤 값이 들어가는지 쉽게 파악할 수 있다. -> 파라미터에 잘못된 값을 넣는 실수도 방지할 수 있다.
- 디폴트 매개변수 생략을 간접적으로 지원한다.
- 자바는 메소드에 대한 디폴트 매개변수 생략을 지원하지 않는다.
- 참고로 코틀린의 경우 디폴트 파라미터의 값을 생략할 수 있다.
- 빌더 패턴은 디폴트 필드를 제외하고 빌더를 구성하여 객체를 생성할 수 있다.
- 자바는 메소드에 대한 디폴트 매개변수 생략을 지원하지 않는다.
Question question = new Question.Builder() .subject("질문 있어용") .content("모르겠어용") // .author("하하") .build();
-
필수 멤버와 선택적 멤버를 분리할 수 있다.
- 초기화를 반드시 해야하는 변수 혹은 초기화가 선택적인 변수가 있을 수 있다.
- 이러한 변수들을 가지고 생성자 방식으로 구현하려면 복잡해진다.
- 초기화가 필수적인 멤버 변수를 위한 생성자 정의 -> 선택적인 멤버 변수를 위한 생성자 오버로딩 -> 혹은 전체 멤버를 파라미터로 받는 생성자를 선언하되, 파라미터에 null을 받도록 구현…
- 빌더 패턴에서는 빌더의 생성자에 초기화가 필수인 멤버를 넣어주면 된다.
-
멤버 별로 초기화 검증을 분리할 수 있다.
- 오버로딩된 생성자가 잔뜩 있다면, 각 생성자 매개변수에 대한 검증 로직을 메서드마다 구현해야 한다.
- 그러나 빌더의 멤버 설정 메서드에서 검증 과정을 분담하면 이러한 복잡함이 해소된다.
-
멤버에 대한 변경 가능성 최소화를 추구한다.
- 멤버에 값을 할당할 때 Setter 메서드를 사용하는 것은 매우 좋지 않다.
-
개발 시 가장 중요하게 여겨지는 것 중 하나가 불변 객체이다.
-
불변 객체
- 객체 생성 후 내부 상태가 변하지 않는 객체
- 읽기(get) 메서드만 제공
-
대표적으로 자바에서 final을 붙인 변수가 불변 객체에 해당
- 불면 객체를 사용해야 하는 이유
- Thread-Safe하여 동기화를 고려하지 않아도 된다.
- 가변 객체로 작업을 하면 개발하던 중 예외가 발생 시 해당 객체가 불안정한 상태에 빠질 수 있다. -> 또 다른 오류를 유발시킬 위험이 존재한다.
- 불변 객체 사용 시 다른 사람이 개발한 함수를 위험 없이 사용할 수 있다. -> 협업과 유지보수에도 유용하다.
-
- 클래스가 반드시 가변적이어야 하는 합당한 이유가 있지 않다면, 반드시 불변으로 만들어야 한다. -> 불가능하다면 변경 가능성을 최소화해야 한다.
-
빌더 패턴의 단점
-
코드 복잡성 증가
- 기본적으로는 N개의 클래스에 대해 N개의 빌더 클래스를 만들어야 한다. -> 관리해야 할 클래스가 많아지고 구조가 복잡해진다.
-
생성자에 비해 성능이 감소한다.
- 매번 메서드를 호출하고, 빌더를 거쳐 인스턴스화 하기 때문이다.
- 생성 비용 자체는 크지 않지만, 애플리케이션의 성능을 매우 중요하게 고려해야 하는 상황이라면 문제가 될 수 있다.
-
지나친 빌더 남용으로 이어질 수 있다.
- 클래스 필드 개수가 4개보다 적고, 필드의 변경 가능성이 없다면 생성자나 정적 패토리 메서드를 이용하는 것이 좋다.
- 그러나 API는 시간이 지날수록 매개변수가 많아지는 경향이 있어서, 처음부터 빌더 패턴으로 시작하는 편이 나을 때가 많기도 하다.
-
질문 Dto 작성
record로 DTO를 작성해보자.
RECORD
-
값의 집합으로 이루어진 간단한 객체를 개발할 때 사용할 수 있다.
- 코틀린의 data 클래스에 해당한다.
-
불변 데이터(immutable data)를 다루는 클래스 구현에 최적화되어 있다.
-
DTO 특성 클래스 개발 시 개발자가 직접 구현하던 반복적인 작업이 불필요해졌다.
- constructor, accessors(), equals(), hashCode(), toString(), …
-
데이터 전송 용도라는 것을 명시적으로 나타낼 수 있다. -> 코드에 대한 이해도를 높이고 클래스 목적에 맞지 않는 구현을 방지한다.
-
Record 클래스 특징
- final 클래스이다.
- 다른 클래스와의 상속 관계는 생성할 수 없다.
- 자동으로 생성되는 accessor는 인스턴스 멤버 변수 이름과 동일하다.
- 접근 제어자는 public, package-private만 가능하다.
- 생성자의 접근 제어자는 클래스의 접근 제어자보다 제한된 수준일 수 없다.
- final 클래스이다.
-
Record 클래스로 DTO 클래스를 만드는 이유
- 데이터 전송 객체는 단순히 데이터를 전달하기 위해서 사용된다.
- 다양한 집계 연산을 수행하고 결과를 담는다.
- 외부 시스템과 통신할 때 불필요한 데이터를 제거한다.
- 데이터 전송 객체는 단순히 데이터를 전달하기 위해서 사용된다.
public record QuestionRequest(
String subject,
String content
) {
}
@Builder
public record QuestionResponse(
String subject,
String content,
Long authorId,
String author,
List<AnswerResponse> answers,
LocalDateTime modifiedDate
)