[Spring_4기 본캠프] Spring 심화 - 도메인과 엔티티 | Day 54
1. 도메인 (Domain)
: 비즈니스 규칙, 로직, 사용 사례를 포함한 핵심적인 비즈니스 개념을 모델링한 것
- 도메인 주도 설계(DDD, Domain Driven Design)에서 도메인은 소프트웨어의 중심이며, 비즈니스 가치를 표현한다.
- 시스템의 핵심 규칙과 비즈니스 규칙을 캡슐화 한다.
- 특정 문제 영역에 대한 추상화를 제공한다.
- 도메인 모델(Domain Model)은 비즈니스 로직과 도메인의 개념을 소프트웨어 안에서 구현한 구조이며, 핵심 비즈니스 규칙과 로직이 담겨 있으며, 문제 영역을 소프트웨어로 표현하는 역할을 한다. (ex. Order, Member같은 클래스 등)
- 도메인 모델은 데이터뿐만 아니라 데이터를 처리하는 행위를 포함한다.
2. 엔티티 (Entity)
: 도메인 모델의 한 구성요소로, 고유한 식별자로 구분되는 객체를 의미
- 데이터베이스 테이블의 레코드와의 매핑을 통해 영속성을 처리하기 위해 자주 사용된다.
- 엔티티는 데이터 중심적이며, 비즈니스 로직보다는 데이터 상태를 관리하는 데 초점이 맞춰진다.
- 주로 데이터의 저장, 검색과 관련된 작업을 수행한다.
도메인과 엔티티의 개념이 혼동되기 시작한 것은 팀원마다 소스트리 패키지 구조를 다르게 설계하는 것을 보고난 후부터였다.
어떤 팀원은 도메인패키지 안에 모든 엔티티를 몰아넣기도 하고, 어떤 팀원은 도메인 안에 엔티티 패키지를 모두 나누고 관련 서비스나 레포지토리 클래스를 나누는 것을 보고 도메인이 엔티티와 같은건지 엔티티가 하위 개념인건지 궁금해졌다.
하이버네이트 공식 문서와 여러 블로그 글을 보고, 챗GPT한테 물어봐도 두 개념이 확실하게 분리되고 이해가 되지는 않는다.
그래도 느낌이라도 잡기 위해 코드예시를 들어 기억하도록 하자.
1) 도메인과 엔티티가 분리된 경우
// 도메인 클래스 (비즈니스 로직 중심)
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long orderId;
@OneToMany(mappedBy = "order")
private List<Item> items;
private double totalAmount;
// 비즈니스 로직과 데이터 저장을 모두 처리
public void addItem(Item item) {
items.add(item);
calculateTotal();
}
private void calculateTotal() {
this.totalAmount = items.stream().mapToDouble(Item::getPrice).sum();
}
}
// 엔티티 클래스 (데이터 저장 중심)
@Entity
@Table(name = "orders")
public class OrderEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@OneToMany(mappedBy = "order")
private List<ItemEntity> items;
private double totalAmount;
// 데이터 접근 및 저장만 처리
}
- > 도메인 모델이 엔티티를 포함한 관계
- 엔티티는 데이터의 접근과 저장만을 처리하고, 도메인 모델은 비즈니스 로직을 다룬다.
<장점>
- 엔티티는 데이터베이스와의 영속성 작업에만 집중
- 도메인 모델은 비즈니스 로직에만 집중
- 높은 응집력과 명확한 역할 분리
<단점>
- 설계가 복잡해질 가능성이 있음
- Repository가 엔티티를 직접 다루기 때문에 도메인 모델에서 다시 엔티티에 접근하거나 값을 설정하는 추가 작업이 필요
- 저장 작업을 위해 도메인 모델에서 Repository를 호출해야 할 수도 있어 의존성이 복잡해질 수 있다.
2) 도메인과 엔티티가 같은 클래스로 사용된 경우
@Entity
@Table(name = "orders")
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long orderId;
@OneToMany(mappedBy = "order")
private List<Item> items;
private double totalAmount;
// 비즈니스 로직과 데이터 저장을 모두 처리
public void addItem(Item item) {
items.add(item);
calculateTotal();
}
private void calculateTotal() {
this.totalAmount = items.stream().mapToDouble(Item::getPrice).sum();
}
}
-> 도메인 모델과 엔티티가 동일한 클래스로 정의한 관계
- 데이터베이스 테이블에 매핑
- 도메인 논리(비즈니스 로직)도 포함
- 즉, 이 클래스는 데이터 저장(영속성)과 비즈니스 로직을 모두 처리함
<장점>
- 설계가 간단
- Repository는 이 엔티티 객체를 바로 사용해 CRUD 작업을 수행
<단점>
- 데이터 저장(영속성)과 비즈니스 로직이 같은 클래스에 있어 객체를 데이터베이스에 저장하는 것과 도메인 논리를 다루는 작업이 혼재됨
[정리]
- 간단한 애플리케이션에서는 도메인 모델과 엔티티를 동일한 클래스로 정의해 개발 속도를 높이고 유지보수의 용이성을 챙길 수 있다.
- 복잡한 비즈니스 로직을 다루거나 엔티티와 비즈니스 로직 간 역할 충돌이 우려될 경우 도메인 모델과 엔티티를 분리하는 것이 좋다.