Search

Spring JPA에서 Entity와 도메인 로직 분리 논의: CoP 정리

회사에서 진행한 CoP(Community of Practice) 모임에서 Spring JPA의 연관관계와 도메인 로직 처리 방식에 대해 논의했습니다. 특히, JPA Entity에 도메인 로직을 포함할지, 별도의 도메인 객체로 분리할지에 대한 의견을 나누었으며, 작성자와 동료의 관점이 달랐습니다. 아래는 논의 내용과 장단점, 그리고 제안된 하이브리드 접근 방식을 정리한 내용입니다.

논의 배경

주제: JPA 연관관계와 도메인 로직 처리 방식.
작성자의 경험:
2개의 JPA 프로젝트와 최근 JPA+MyBatis 혼합 프로젝트를 진행.
연관관계에서 발생한 문제(데이터 유실, toString 무한루프, Cross Join 등)로 인해 JPA 사용에 대한 회의적인 시각.
Lazy Loading, 동적 쿼리(predicate) 사용 시 유지보수 어려움 경험.
신규 프로젝트 목표:
빠른 생산성과 정확한 데이터 제공.
기존 문제 극복을 위해 JPA와 MyBatis 혼합 사용.
설계 원칙:
1.
Service를 인터페이스로 정의해 구현체를 분리.
2.
Entity를 도메인 객체로 변환해 유지보수성 강화.
3.
컨트롤러에서 세션/서블릿 요청 처리 후 순수 데이터만 서비스로 전달.
4.
바운디드 컨텍스트 기반 패키지 구성.
5.
조회는 MyBatis, 쓰기는 JPA 사용.
결과: 프로젝트는 성공적으로 오픈, 6개월간 안정적으로 운영. 하지만 생산성이 기대보다 낮았고, CoP에서 이를 개선하기 위한 논의 진행.

논의 주제: Entity vs. 도메인 객체

CoP에서 논의된 두 가지 주요 접근 방식은 다음과 같습니다:
1.
Entity에 도메인 로직 포함 (동료의 주장).
2.
도메인 객체로 로직 분리 (작성자의 주장).

1. Entity에 도메인 로직 포함

동료는 Entity에 도메인 로직을 포함하는 방식이 더 적합하다고 주장했습니다.

장점

간결함: 도메인 로직이 Entity 내부에 있어 코드가 간단하고 별도 클래스 불필요.
직관성: 데이터와 로직이 함께 있어 도메인 객체의 책임이 명확.
JPA 통합성: 영속성 컨텍스트에서 상태 변경이 즉시 반영되며, 트랜잭션 내에서 데이터베이스 작업 간편.
적은 클래스 수: 소규모 프로젝트에서 관리 부담 감소.

단점

JPA 의존성: 로직이 JPA(Lazy Loading, @Transactional 등)에 종속되어 테스트와 재사용성 제한.
SRP 위배 가능성: 데이터 저장과 로직 처리가 섞여 복잡도 증가.
테스트 어려움: 통합 테스트 위주로 작성되며, 단위 테스트 작성 시 모킹 필요.

동료의 추가 의견

JPA를 MyBatis로 완전히 대체하는 경우는 드물며, 연관관계를 제거하고 도메인 객체로 변환하는 것은 불필요한 오버헤드.
JPA의 연관관계는 적절히 관리하면 충분히 효율적.

2. 도메인 객체로 로직 분리

작성자는 Entity를 데이터 매핑 전용으로 사용하고, 도메인 로직을 별도의 객체로 분리하는 방식을 선호했습니다.

장점

유지보수성: Entity는 데이터 저장, 도메인 객체는 로직 처리로 책임 분리(SRP 준수).
JPA 독립성: 로직이 JPA에 종속되지 않아 단위 테스트가 쉬움.
재사용성: 도메인 로직을 다른 컨텍스트(예: API, 배치)에서 재사용 가능.
복잡한 로직 관리 용이: 서비스 로직과 데이터 액세스 로직 분리로 가독성 향상.

단점

코드 복잡도 증가: 추가 클래스와 매핑 로직(Entity 도메인 객체) 필요.
매핑 오버헤드: DTO/Mapper 사용으로 추가 작업 발생.
생산성 저하: 초기 설계 및 구현 비용 증가.

작성자의 추가 의견

JPA에서 MyBatis로 전환 시 도메인 로직이 분리되어 있어 유지보수 용이.
동적 쿼리(predicate) 사용 시 서비스 로직과 혼재되어 유지보수 어려움 경험 → 별도의 Predicate 클래스 도입으로 개선, 하지만 변경 비용 큼.

하이브리드 접근 제안

논의 결과, 두 접근 방식의 장점을 결합한 하이브리드 방식을 제안했습니다:
조회: MyBatis 사용 (빠른 성능, 유연한 쿼리 처리).
쓰기(Insert, Update, Delete): JPA 사용 (영속성 컨텍스트 활용, 간단한 상태 관리).
도메인 로직:
간단한 로직(예: 상태 변경, 유효성 검사)은 Entity에 포함.
복잡한 로직(예: 외부 시스템 연동, 복잡한 계산)은 별도 도메인 객체 또는 서비스로 분리.

장점

JPA의 강점(영속성 관리)과 MyBatis의 강점(조회 성능)을 모두 활용.
간단한 로직은 Entity에서 처리해 코드 간결성 유지.
복잡한 로직은 도메인 객체로 분리해 유지보수성과 테스트 용이성 확보.

우려사항

과거 프로젝트처럼 복잡도가 증가하거나 유지보수가 어려워질 가능성.
JPA 연관관계 관리와 도메인 로직 분리의 균형 필요.

결론

Entity에 로직 포함: 소규모 프로젝트, 간단한 로직에 적합. JPA의 영속성 컨텍스트를 활용해 빠른 개발 가능.
도메인 객체 분리: 복잡한 로직, 테스트 용이성, 유지보수성이 중요한 대규모 프로젝트에 적합.
하이브리드 방식: MyBatis로 조회, JPA로 쓰기, 간단한 로직은 Entity, 복잡한 로직은 도메인 객체로 분리. 이 방식은 생산성과 유지보수성의 균형을 맞출 수 있음.
주의점: JPA 연관관계 관리(예: 양방향 연관관계, Lazy Loading)와 도메인 로직 분리의 균형을 신중히 설계해야 함. 과도한 복잡도는 피하고, 팀의 JPA 숙련도와 프로젝트 요구사항에 맞게 선택.