🤔 고민
강의, GPT, 구글링을 하며 혼자서 개발하다보니 계속해서 새로운 문제에 직면하게 되었다.
객체를 생성할때에는 어떤 방법이 더 좋지?
인터페이스를 만들고 구현체를 만들어 역할과 구현을 분리?
Controller계층 즉 표현 계층에는 직접 Entity를 만들지 말고 DTO를 사용해서 값을 전달해줘야 하는데 그럼 DTO의 범위는 어디까지?
...
또 하나를 해결하면 다른 의문들이 떠올라 진행이 어려웠다.
✔ 객체를 생성할 때에는 어떤 방법이 더 좋지?
기존에 생각없이 setter메소드를 통해서 객체를 생성 후 값을 설정해주면서 일관성과 불변성을 해치고있었다.
고민 하던중 builder패턴에 대해서 알게되었다.
🔎 빌더 패턴 정리
[디자인 패턴] builder패턴
🤔 빌더 패턴(Builder Pattern) ? 복잡한 객체의 생성 과정과 표현 방법을 분리하여 다양한 구성의 인스턴스를 만드는 생성 패턴이다. 생성자에 들어갈 매개 변수를 메서드로 하나하나 받아들이고
average1.tistory.com
🔎 @Builder
Lombok의 어노테이션을 사용하여 빌더 패턴을 직접 구현하지 않고 손쉽게 사용할 수 있었다.
코드는 새롭게 빌더 패턴으로 리팩토링한 Member Entity 이다.
@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member extends BaseEntity{
@Id @GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String email;
private String password;
private String name;
@Embedded
private Address address;
@Enumerated(EnumType.STRING)
private Gender gender;
@Builder
public Member(String email, String password, String name, Address address, Gender gender) {
this.email = email;
this.password = password;
this.name = name;
this.address = address;
this.gender = gender;
this.setCreateTime(LocalDateTime.now());
}
}
@NoArgsConstructor : JPA는 프록시 생성을 위해서 기본 생성자를 반드시 하나 생성해야한다. 하지만 제한없이 public한 상태로 기본 생성자를 열어두면 값이 세팅되어 있지 않은 객체가 만들어질 위험이 있기때문에 (access = AccessLevel.PROTECTED) 속성을 통해 접근권한을 최소화 한다.
@Builder : 클래스 위에 어노테이션을 써도 되지만 그럴경우 @AllArgsConstructor어노테이션을 사용하거나 직접 인자가 있는 생성자를 만들어야 한다, 하지만 이 어노테이션은 필드가 선언된 순서에 맞게 생성자를 만들기 때문에 생성자를 사용하게되면 오류를 유발할 우려가 있다.
또한 생성자를 직접 만듦으로써 필요한 값을 직접 추가하거나 제거할 수 있다.
✔ 인터페이스를 만들고 구현체를 만들어 역할과 구현을 분리?
간단한 CRUD 게시판을 만들면서 interface를 만들고 구현체를 만드는 과정에서 계속 interface 를 수정하고 다시 구현체에 수정한것을 추가하는 과정이 여러번 있었다, 이러한 과정에서 interface가 필요한가에 대한 의문이 들었다.
이론적으로는 다형성을 활용하여 인터페이스를 구현한 객체를 실행시점에 유연하게 변경할 수 있고, 이는 클라이언트를 변경하지 않고, 서버의 구현 기능을 유연하게 변경할 수 있다고 이해는 했지만, 실제 이러한 변경 상황을 경험하지 못해 의문이 들었던 것 같다.
또한 인터페이스를 잘 설계할 정도의 실력이 되지 않아 계속해서 인터페이스를 수정하게 되었고 서비스 또한 마찬가지이다.
그러던 중 Spring Data JPA에 대해서 알게 되었다.
CRUD 처리를 위한 공통 인터페이스를 제공하면서 repository 개발 시 인터페이스만 작성하면 실행 시점에 스프링 데이터 JPA가 구현체를 동적으로 생성하여 주입해준다.
구현 클래스 없이 인터페이스만 작성해도 개발을 완료할 수 있도록 지원한다.
public interface MemberRepository extends JpaRepository<Member, Long> {
}
이로인해 데이터 접근 계층은 구현체를 만들지 않고 인터페이스만으로 기본적인 CRUD가 가능해졌다.
그렇다면 서비스 계층은 기존처럼 interface와 구현체 모두 만들어야할까? 라는 생각을 했지만 위의 고민때문에 인터페이스를 만들지 않고 구현체를 먼저 만들고 향후에 필요할 때 리팩토링을 통해서 인터페이스를 도입하는 방법으로 진행하기로 했다.
인터페이스를 만들게되면 추상화 비용이 발생하기 때문에 미래의 확장 가능성이 있다고 생각하면 인터페이스와 구현체를 만드는 것이 좋겠지만 지금 상황에서는 비용이 더 많을것으로 판단하여 구현 클래스만 만들기로 정하였다.
✔ DTO의 범위
Controller계층 즉 표현 계층에는 보안상의 이유로 직접 Entity(도메인)를 노출하지 말고 DTO를 사용해서 값을 전달해줘야 한다고 배웠다
DTO를 어느 레이어까지 전달하고 또 DTO와 Entity간의 변환 작업은 어디에서 수행되어야 할까? 라는 고민을 하게되었다.
Repository레이어는 Entity의 영속성을 관장하는 역할을 하며 이로 인해, DTO의 변환 작업을 Repository에서 하는 것은 지양해야 한다는 의견이 다수 존재한다고 한다.
그렇다면 Controller? Service?
Controller?
DTO의 범위를 Controller까지 정한다면 도메인으로 변환하는 작업을 Controller에서 하게 되고, 여러 종류의 컨트롤러에서 해당 서비스를 사용할 수 있게된다.
하지만 여러 종류의 컨트롤러가 한 종류의 서비스를 사용하기 보단, 한 종류의 컨트롤러가 서비스를 사용하기 때문에 서비스 계층까지 허용해도 된다는 의견도 있다.
Service?
마틴 파울러는 Service 레이어란 어플리케이션의 경계를 정의하고 비즈니스 로직 등 도메인을 캡슐화하는 역할이라고 정의한다, 즉 도메인을 보호한다.
도메인을 Controller에서 사용하는 경우 결합도가 증가하여, 도메인의 변경이 Controller의 변경을 촉발하는 유지보수의 문제로 이어질 수 있다.
나는 Service계층에서 DTO의 변환작업( DTO -> Entity , Entity -> DTO )이 수행되어지는 것이 유지보수 면에서 더 좋을것이라 판단하여 Service레이어에서 작업하기로 했다.
찾아보면 프로그래밍에 정답은 없는것 같다, 프로젝트의 방향,규모 등을 고려하여 더 나은것이 뭔지 고민 해야겠습니다.
📚 Reference :
https://tecoble.techcourse.co.kr/post/2021-04-25-dto-layer-scope/
'Project > SpringBoot+JPA 게시판' 카테고리의 다른 글
[SpringBoot + JPA 게시판 만들기] 게시판 페이징 및 N+1문제 최적화 ( Pageable, join fetch ) (0) | 2024.04.25 |
---|---|
[SpringBoot + JPA 게시판 만들기] 로그인, 로그아웃 기능 구현 및 타임리프 적용 (+스프링 시큐리티) (0) | 2024.03.31 |
[SpringBoot + JPA 게시판 만들기] 게시판CRUD 서비스 로직 작성 + 테스트 코드 추가 (0) | 2024.03.09 |
[SpringBoot + JPA 게시판 만들기] 프로젝트 생성 및 환경 설정 + 엔티티(Entity) 생성 - 2 (0) | 2024.02.13 |
[SpringBoot + JPA 게시판 만들기] 요구사항 및 프로젝트 설계 - 1 (0) | 2024.02.08 |