
여러 요소나 데이터를 하나로 합친 것
소프트웨어 개발에서 관련된 객체들의 집합을 의미한다.
※ aggregation: 집합
예를 들어, Order라는 도메인이 있다고 가정하자.
주문 상품, 주문 배송지, 결제정보 등 여러 객체들이 존재한다.
이러한 객체들을 하나의 애그리거트(Aggregate)로 묶어 처리하면 엄청 효율적이다!
@Getter
public class Order {
private String id;
private List<OrderLine> orderLines;
private OrderState state;
private Money totalAmounts;
private ShippingInfo shippingInfo;
public Order(List<OrderLine> orderLines, ShippingInfo shippingInfo) {
id = UUID.randomUUID().toString();
setOrderLines(orderLines);
setShippingInfo(shippingInfo);
state = OrderState.PAYMENT_WAITING;
}
// 각종 메소드들 (생략) ~~~
이 Order 클래스를 애그리거트 루트(Aggregate Root) 클래스다.
애그리거트를 사용하면, 연관 도메인을 묶기 때문에 모델 관계를 파악하기가 더 쉽다.
또한 애그리거트 단위로 일관성을 관리하면, 코드도 일목조연하게 작성할 수 있다.
코드의 복잡도도 낮아지기 때문에,
유지보수 및 확장, 변경에 들이는 노력이 줄어든다.
Aggregate Root인 Order 클래스는 배송상태(OrderState)를 가진다.
OrderState state = dto.getState(); // get → 배송 상태를 가져옴
state.setState(PREPARING); // set → 배송 상태를 변경
지금 이 코드는 개발자가 어디에서 사용하든, 배송 상태가 변경 가능한 코드다.
이 코드의 문제점은 이렇다.
이러한 상태가 애그리어트의 일관성이 깨진 상태다.
외부에서 직접 필드를 조작해서 규칙을 우회했기 때문이다.
이러한 일관성을 지킬려면 다음과 같은 방법이 있다.
setter는 일반적으로 필드에 값을 할당하기 위해 사용한다.
아무 값이나 자유롭게 넣을 수 있다는 장점이 있지만..
도메인의 의미를 적절하게 표현할 수 없다.
게다가 단순히 특정 필드를 묶은 것처럼 이해될 수 있으며,
그렇게 개발할 가능성이 높아진다.
@Getter
@Setter
public class Order {
private String id;
private List<OrderLine> orderLines;
private OrderState state;
private Money totalAmounts;
private ShippingInfo shippingInfo;
}
그냥 특정 필드만 묶어놓은 것일 뿐...
Order 클래스는 데이터를 넣고 가져오는것 외엔 의미 있는 행위나 책임이 없다.
이러한 로직은 보통 한 곳에 응집되지 않고, 유지보수 시 분석 및 수정에 더 많은 시간을 들여야 한다.
// 결제 후, 상태를 '준비 중'으로 변경하는 메소드
public void payment() {
if (state != OrderState.PAYMENT_WAITING) {
throw new IllegalStateException(ErrorMessages.ALREADY_PAID.getMsg());
}
this.state = OrderState.PREPARING;
}
// '배송 준비 중' -> '배송 중' 으로 변경
public void shipped() {
if (state != OrderState.PREPARING) {
throw new IllegalStateException(ErrorMessages.NOT_READY.getMsg());
}
this.state = OrderState.SHIPPED;
}
하지만 위 코드처럼 setter를 사용하지 않는다면,
의미가 드러나는 메소드 명을 고민하게 되고, 이에 걸맞는 로직을 응집시켜 구현하게 된다.
이러한 방식의 장점은, 코드를 읽는 사람도
"이 객체가 어떤 상황에서 어떻게 동작하지?"를 쉽게 이해할 수 있고,
비즈니스 규칙도 객체 내부에 응집되어 유지보수가 훨씬 쉬워진다.
이러한 방식은 단순히 getter/setter로 필드만 조작하는 것보다
의도를 명확하게 표현하고, 객체지향적 설계 원칙에도 훨씬 부합한다.
Aggregate Root는 애그리거트 내부의 다른 객체를 조합하여 기능을 완성해야 한다.
@Getter
public class Order {
private String id;
private List<OrderLine> orderLines;
private OrderState state;
private Money totalAmounts;
private ShippingInfo shippingInfo;
public Order(List<OrderLine> orderLines, ShippingInfo shippingInfo) {
id = UUID.randomUUID().toString();
setOrderLines(orderLines);
setShippingInfo(shippingInfo);
state = OrderState.PAYMENT_WAITING;
}
/**
* 배송 정보 설정
*/
private void setOrderLines(List<OrderLine> orderLines) {
if (isEmptyLOrderLines(orderLines)) {
throw new IllegalStateException(ErrorMessages.NO_ORDER_LINE.getMsg());
}
this.orderLines = orderLines;
calculateTotalAmounts();
}
(생략)
Order 객체 내에서, OrderLine을 통해 배송 정보를 설정하면 된다.