평상시와 같이 개발을 하던 중 갑자기 이런 생각이 들었다.
왜 엔티티에 비즈니스 로직을 작성할까?
아래는 엔티티에 비즈니스 로직을 넣은 내 코드이다.
@Getter
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Builder
public class ChatMessage extends CommonEntity {
public enum MessageType {
ENTER, TALK
}
private MessageType type;
private String roomName;
private String senderName;
private String message;
public static ChatMessage of(ChatMessageRequestDTO requestDTO){
ChatMessage chatMessage = ChatMessage.builder()
.message(requestDTO.getMessage())
.senderName(requestDTO.getSenderName())
.roomName(requestDTO.getRoomName())
.build();
return chatMessage;
}
public void updateSenderName(String username){
this.senderName = username;
}
public void updateStatus(MessageType type){ this.type = type; }
public void updateRoomName(String roomName){
this.roomName = roomName;
}
}
옛날 김영한 강사님이 “엔티티에 비즈니스 로직을 넣어야 한다.”
라고 말씀을 하셨던 것이 기억이 난다.
분명 이유도 말씀을 함께 해주셨을텐데, 내가 기억력이 안좋아서 그런가 왜 엔티티에 비즈니스 로직을 넣어야 하는지를 완전히 까먹었다.
객체지향에 따라서 일까? 아니면 어떠한 성능적인 이유로 그런 것일까?
해당 주제를 다룬 여러 글들과 말씀들을 통해 이유와 내 생각을 정리해보자.
결합도(coupling)
과응집도(cohesiveness)
는 구분해서 생각해볼 수 있습니다. 예로 드신 유효값 검증에 대한 비즈니스 로직이 UI적 특성 - 예컨대 주민번호를 두 개의 입력으로 나누어 받는다던지 - 하는 내용에 의존한다면 말씀대로 이를 엔티티에 구현하는 것은 계층간의 불필요한 결합도를 증가시킬 것입니다.하지만 유효값 검증이 사용자라는 엔티티라는 개념 자체에 강하게 종속된 개념 - 예들들어 사용자의 아이디에 대한 규칙 - 이라면 그런 개념을 엔티티에서 분리하는 것이 오히려 응집성을 해치는 결과를 가져올 수 있습니다.
응집도가 떨어진다는 것은, 예컨대 연관된 코드를 한 곳에서 파악하기 어렵고 UI 구현을 바꾸었는데 핵심 비즈니스 코드까지 재구현을 해야한다던지 하는 식의 문제를 야기할 수 있습니다.
참고로 보다 적극적으로 엔티티의 속성과 동작을 결합하는 접근에 대해서는 도메인 주도 개발 방법론(DDD - Domain Driven Development)를 참조해보시면 좋습니다.
위 말씀을 요약해보자면 엔티티라는 개념 자체에 강하게 종속된 개념, 즉 엔티티의 필드에 직접적으로 관련된 로직의 경우에는 엔티티에 작성하는 것이 응집도를 높이고,
엔티티보다 UI적 특성에 종속된 개념, 즉 엔티티의 필드와 직접적으로 관련되지 않은 로직의 경우에는 엔티티가 아니라 비즈니스 계층에 작성하는게 계층간 불필요한 결합도를 높일 것이다.
예를 들어서 엔티티의 필드를 유효성 검증하거나, 수정해야하는 경우에는 필드와 직접적으로 관련
이 되어있기에 엔티티에 작성하는 것이 응집도를 높이는 방법이고
UI적 특성, 즉 주민등록번호를 나누어서 작성하는 등 UI 구현에 따라 영향을 자주 받는 로직
들은 엔티티에 작성하면 계층간 결합도를 높여서 유지보수에 영향을 끼칠 것이다.
그 다음은 갓영한 강사님의 해당 주제에 대한 댓글이다.
도메인이 비즈니스 로직의 주도권을 가지고 개발하는 것을 도메인 주도 설계라 합니다. 이렇게 해두면 서비스의 많은 로직이 엔티티로 이동하고, 서비스는 엔티티를 호출하는 정도의 얇은 비즈니스 로직을 가지게 됩니다.
이렇게 하면 information expert pattern을 지키면서 개발할 수 있습니다.
(information expert pattern는 검색해보시면 도움이 되실거에요^^)
반대로 엔티티는 단순히 getter, setter만 제공하고, 서비스에 비즈니스 로직이 모두 있어도 됩니다.
이렇게 되면 서비스 로직이 커지고, 엔티티는 단순히 데이터를 전달하는 역할만 담당하게 됩니다.
전자는 엔티티를 객체로 사용하는 것이고, 후자는 엔티티를 자료 구조로 사용하는 방식이지요.
그러면 항상 전자가 정답인가? 라고 하면 그렇지는 않습니다. 둘다 장단점이 있기 때문에, 상황에 맞는 적절한 방법을 선택하는 것이 중요합니다.
관련해서 클린코드 6. 객체와 자료구조에 둘의 차이가 자세히 설명되어 있으니 한번 읽어보시길 추천드립니다.
감사합니다.
위 말씀을 요약해보자면 도메인에 비즈니스 로직의 주도권을 주고, 도메인에 로직을 작성을 하면 서비스의 많은 로직이 엔티티로 이동하고, 서비스는 엔티티의 로직을 호출하는 얇은 비즈니스 로직을 가지게 된다.
이렇게 하면 엔티티를 객체로 활용을 할 수 있기에 객체지향
프로그래밍에 한발짝 다가서게 된다. 반대로 비즈니스 계층에 비즈니스 로직에 주도권을 주게 된다면 엔티티를 단순히 자료구조
로 사용을 하게 된다.
나는 엔티티를 자료구조로 사용하는 것이 무조건적으로 안좋은 방법은 아니라고 생각한다. 오히려 이렇게 엔티티를 단순히 자료구조로 사용을 하면, 각 계층에 맞는 역할
이 분명해진다고 생각을 했다.
그러면 비즈니스 계층과 도메인 계층의 역할
은 각각 무엇일까?
비즈니스 계층은 비즈니스 로직을 수행하는 것을 주 관심사로 둔다. 마찬가지로 화면에 데이터를 출력하는 방법이나 혹은 데이터를 어디서, 어떻게 가져오는지에 대한 내용은 알고있지 않다. 그저 Persistence Layer에서 데이터를 가져와 비즈니스 로직을 수행하고 그 결과를 Presentation Layer로 전달하면 된다. 대표적인 구성요소는 Service와 Domain Model 등이 있다.
경우에 따라 아래처럼 Service와 Domain Model을 별개의 계층으로 나누거나, 아예 Domain Model을 Layered Architecture와 별개의 것으로 분리하는 경우도 더러 있는 것 같다
위의 말씀 중 경우에 따라 아래처럼 Service와 Domain Model을 별개의 계층으로 나누거나
라는 부분에 집중을 하면, 나는 Service 계층과 Domain 계층을 별개의 계층으로 생각을 했었다.
왜냐하면 Service 계층은 본래의 목적인 비즈니스 로직을 수행하고, Domain 계층은 본래의 목적인 데이터를 표현하는데 집중을 해야한다고 생각을 했었기 때문이다.
그렇다 나는 엔티티를 자료구조로 인식을 하고 있었지만, 엔티티에 비즈니스 로직을 넣으면 좋다는 의견을 내 생각의 필터링 없이 받아들였기에 내 머리속에서 충돌
이 일어났던 것이었다.
그렇다면 나는 어떤 방식으로 구현을 해야할까?
나는 아래의 글을 통해서 내 생각을 최종적으로 정해보았다.
가장 큰 차이점은 객체를 얼마나 활용했는가라고 생각한다.
Service Layer에서 로직을 처리할 때는 객체는 쿼리 결과를 가지고 있는 상자정도로 사용되는 것 같다.
하지만 Domain Model에서 로직을 처리할 때는 각각의 객체들이 자신의 이벤트를 처리하는 것을 볼 수 있다.
즉, Domain Model에서 로직을 처리하는 방식이 객체를 더욱 활용하는 방법이라는 것이다.
하지만 JPA가 아닌 MyBatis를 사용하는 경우에는 Domain Model에서 비즈니스 로직을 처리하기는 방법을 사용하는 것이 쉽지 않아보인다.
결론적으로 두 가지 방법 모두 좋지만 객체지향관점에서는 Domain Model에서 비즈니스 로직을 처리하는 것이 좋다고 생각한다.
위의 말씀 중 핵심적인 부분은 객체를 얼마나 활용했는지
부분 같다. 엔티티에 본인과 관련된 비즈니스 로직을 넣는다면 객체 스스로 본인에 상태에 대해서 책임을 질 수 있기에 더욱 객체지향에 가까워지며, 유지보수에도 더 효과적일 것 같다는 생각이 들었다.
따라서 내 생각은 엔티티에 본인과 관련된 비즈니스 로직을 넣되, UI의 변경에 따라서 자주 변경될 수 있는 로직은 비즈니스 계층이나 DTO의 유효성 검사 로직으로 넣는게 객체지향과 내 생각간 적절한 trade-off
이지 않을까라는 생각을 하게 되었다.
글 잘 읽었습니다!
옛날부터 고민이 있던 것이 있는 데 만약 엔티티 하나에만 의존하는 비즈니스 로직이 아니라 여러 엔티티에 의존하는 비즈니스 로직이 있을 때에는 어떻게 처리하실 건가요??
저는 해당 비즈니스 로직을 최대한 엔티티 하나에만 의존하도록 쪼개서 서비스 레이어에서 엔티티의 비즈니스 로직들을 호출하는 식으로 구현하는 것으로 임시 결론을 내렸습니다.
Kevin님 생각이 궁금하네요.