BACKEND/JAVA & SPRING

Setter 지양, 그리고 DTO를 써야하는 이유

이-프 2023. 12. 19. 15:56

개발을 진행하다 보면, Entity레벨에 @Setter를 사용하여 데이터를 쉽게 바꾸거나, 지정해주고 싶을 때가 있었다. 하지만 대부분의 강의나 피드백에서 Setter 사용은 적극적으로 지양하고 있다.

처음에는 왜 어노테이션만 붙이면 되는건데 이 쉬운걸 포기해야할까 ? 라는 의문이 들었다.

하지만 여러번의 조사 끝에 왜 Setter을 사용하지 않고, 대신 DTO를 사용해야하는지 깨달을 수 있었다. 오늘은 그 이유에 대해서 작성해보도록 하겠다. 🌱

Entity Level에서 @Setter를 사용하면 안되는 이유

1. 개발자의 의도를 알기 어렵다.

Setter을 사용하면 내가 작성한 코드의 의도를 타 개발자가 파악하기 어렵다.

주로 Entity의 값을 변경하고자 할 때 사용하는데, 왜! 이렇게 변경하려고 하는지 작성한 사람이 아니고는 이해하기 쉽지 않다.

class Account {
    private long balance;
    
    public Account(long balance) {
        this.balance = balance;
    }
    
    public void setBalance(long balance) {
        this.balance = balance;
    }
}
 
Account myAccount = new Account(500);
myAccount.setBalance(1000);

예를 들어, myAccount에 balence를 1000으로 설정하고 있다.

전체코드를 보면 기존에 balence가 500에서 1000으로 변경된 이유가 계좌에 입금을 해서임을 알 수 있다.

하지만, myAccount.setBalance(1000); 만 확인한다면, 입금 or 인출을 해서 인지 정확한 이유를 알 수 없어진다!

2. 메소드 단의 개발로 책임 분산을 덜할 수 있다.

@Setter
class Account {
    private long balance;
}
 
@Service
public class AccountService {
    ...
    
    public void withdraw(long id, long amount) {
        Account account = accountRepository.findById(id).orElseThrow();
        long newBalance = account.getBalance() - amount;
        
        if (newBalance < 0) {
            throw new IllegalArgumentException("잔액이 부족합니다.");
        }
        
        account.setBalance(newBalance);
    }
    
    ...
}
  • 위의 코드는 withdraw 메소드 내부에서 모든 출금 로직을 진행하고 있다.
  • 하지만 코드 자체가 길 뿐만 아니라, 명확한 의도를 가진 메소드가 아님을 느낄 수 있다.
class Account {
    private long balance;
    
    public void withdraw(long amount) {
        if (amount > balance) {
            throw new IllegalArgumentException("잔액이 부족합니다.");
        }
        
        balance -= amount;
    }
}
 
@Service
public class AccountService {
    ...
    
    public void withdraw(long id, long amount) {
        Account account = accountRepository.findById(id).orElseThrow();
        account.withdraw(amount);
    }
    
    ...
}
  • Entity 내부에 비지니스 로직인 withdraw로직을 넣어둔다.
  • 실제 Service layer에서는 withdraw로직만 불러온다.
  • 출금 로직이 변경되더라도, Entity 내부의 비지니스 로직만 수정하면 된다.

⇒ 로직의 의도가 명확해지고, 각 layer단의 책임이 분산되지 않아 수정하기도 편해졌다.

그럼 어떻게 해결할 수 있을까?

1. @Builder 사용하기

@Builder를 사용하여 객체를 초기화해주는 방법이다.

public class Member {

    @Id 
		@GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String student_name;

    public Member(Long id, String student_name) {
				this.id = id;
	    	this.student_name = student_name;
    }
}
Member = member = new Member(1, "양유림");

클래스의 코드 길이는 늘어났지만, 위에 setter로 가득 채우는 것보다 훨씬 코드 길이가 줄어든다.

단, Setter에 비해 가독성이 떨어진다는 단점이 있다.

그러므로 @Builder 어노테이션울 사용하여 이를 보완하도록 하자 ~!

2. DTO단에서 Getter와 Setter 사용하기

DTO에서는 목적 자체에 어떤 로직이 있는 것이 아니다.

DTO의 본 목적은 데이터를 전달하는 것이므로, Getter과 Setter을 자유롭게 사용해도 된다.

https://www.inflearn.com/questions/161417/dto-사용에대해-궁금합니다

🔗 참고 url

https://velog.io/@eunsiver/Entity에서-Setter-사용-지양-그렇다면-DTO에서는

https://dodop-blog.tistory.com/265

https://mjoo1106.tistory.com/entry/Spring-Setter-vs-Builder


Uploaded by N2T