반응형
집합체(Aggregate)
집합체(Aggregate)란?
- 집합체는 일관성을 유지하면서 하나의 단위로 처리되는 도메인 객체들의 군집입니다. 집합체는 다음과 같은 특징을 가진다
- 경계(Boundary)
- 집합체는 명확한 경계를 가지며, 이 경계 내에서 객체들이 상호작용합니다. 경계는 집합체의 일관성을 유지하는 데 중요한 역할을 한다
- 루트 엔티티(Aggregate Root)
- 집합체 내의 객체들 중 하나는 루트 엔티티로 지정되며, 외부에서 집합체에 접근할 때는 루트 엔티티를 통해서만 접근한다
- 루트 엔티티는 집합체의 일관성을 책임진다
- 일관성
- 집합체 내의 객체들은 항상 일관된 상태를 유지해야 한다
- 집합체의 상태 변경은 트랜잭션 단위로 처리되며, 이는 집합체의 일관성을 보장한다
집합체의 설계 원칙
- 집합체를 설계할 때 고려해야 할 몇 가지 주요 원칙
- 작은 크기
- 집합체는 가능한 작게 유지하는 것이 좋다.
- 작은 집합체는 관리가 용이하고, 성능상의 이점이 있다
- 변경 불가 객체
- 집합체 내의 객체들은 가능한 변경 불가(immutable) 객체로 설계하는 것이 좋다
- 변경 불가 객체는 일관성을 유지하는 데 도움이 된다
- 트랜잭션 경계
- 집합체는 트랜잭션의 경계가 된다
- 트랜잭션은 하나의 집합체 내에서 일어나야 하며, 여러 집합체에 걸쳐 있는 트랜잭션은 피해야 한다
- 루트 엔티티를 통한 접근
- 집합체 내부의 객체에 접근할 때는 항상 루트 엔티티를 통해 접근해야 한다
- 이는 집합체의 일관성을 유지하는 데 중요한 역할을 한다
집합체의 예제
도메인: 주문 시스템
- 주문(Order) 은 하나의 집합체가 될 수 있다.
- 주문 집합체는 주문 정보와 관련된 여러 객체들(예: 주문 항목(OrderItem))을 포함한다
- 주문은 집합체의 루트 엔티티가 된다
@Entity
public class Order {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@Enumerated(EnumType.STRING)
private OrderStatus status;
@OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
@JoinColumn(name = "order_id")
private List<OrderItem> items = new ArrayList<>();
// 루트 엔티티의 메서드를 통해 상태 변경
public void addItem(OrderItem item) {
items.add(item);
}
public void removeItem(OrderItem item) {
items.remove(item);
}
public void changeStatus(OrderStatus newStatus) {
this.status = newStatus;
}
// Getters, setters, equals, hashCode
}
@Entity
public class OrderItem {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String productName;
private int quantity;
private BigDecimal price;
// Getters, setters, equals, hashCode
}
집합체의 사용 예
@Service
public class OrderService {
@Autowired
private OrderRepository orderRepository;
@Transactional
public Order createOrder() {
Order order = new Order();
order.changeStatus(OrderStatus.NEW);
return orderRepository.save(order);
}
@Transactional
public void addItemToOrder(Long orderId, String productName, int quantity, BigDecimal price) {
Order order = orderRepository.findById(orderId)
.orElseThrow(() -> new RuntimeException("Order not found"));
OrderItem item = new OrderItem();
item.setProductName(productName);
item.setQuantity(quantity);
item.setPrice(price);
order.addItem(item);
orderRepository.save(order);
}
}
집합체의 장점
- 일관성 유지
- 집합체는 경계 내에서의 일관성을 보장하여, 시스템의 신뢰성을 높인다
- 모듈화
- 집합체는 시스템을 작은 단위로 나누어 모듈화를 촉진한다.
- 이는 유지보수성과 확장성을 높인다
- 복잡성 관리
- 복잡한 비즈니스 로직을 집합체 내에서 캡슐화하여 관리하기 쉽게 한다
팩터리(Factory)
Factory의 위치 선정
- Factory는 객체 생성 로직을 캡슐화하여 도메인 모델의 일관성을 유지한다
- Factory의 위치를 적절히 선정하는 것은 도메인 모델의 설계와 구현에 중요한 영향을 미친다
위치 선정 지침
- 도메인 모델 내부: 복잡한 객체 생성 로직이 도메인 모델 내부에 위치할 때는 도메인 계층 내부에 Factory를 두는 것이 좋다.
- 도메인 서비스: 객체 생성이 여러 도메인 객체 간의 협력을 필요로 할 때는 도메인 서비스 내에 Factory를 위치시키는 것이 좋다.
- 애플리케이션 계층: 객체 생성이 도메인 로직과 크게 연관이 없을 때는 애플리케이션 계층에 Factory를 두어도 된다.
생성자만으로 충분한 경우
- 객체 생성이 간단하고 초기화 로직이 복잡하지 않은 경우, 생성자만으로 객체 생성을 처리할 수 있다
- 단순한 객체 생성 로직은 생성자로 충분히 처리할 수 있으며, Factory를 사용하지 않아도 코드가 간결하고 이해하기 쉽다
public class Customer {
private final String name;
private final String email;
public Customer(String name, String email) {
this.name = name;
this.email = email;
}
// Getters
}
인터페이스 설계
- Factory 인터페이스를 설계하면, 객체 생성 로직을 더 유연하게 확장할 수 있다
- 인터페이스를 통해 여러 구현체를 사용할 수 있어, 테스트나 다양한 생성 로직을 적용하기 쉽다
public interface CustomerFactory {
Customer create(String name, String email);
}
public class DefaultCustomerFactory implements CustomerFactory {
@Override
public Customer create(String name, String email) {
return new Customer(name, email);
}
}
불변식 로직의 위치
- 의미
- 불변식은 객체가 항상 만족해야 하는 조건을 의미한다
- 불변식 로직은 객체의 일관성을 유지하는 데 중요하며, 이를 어디에 두느냐가 중요하다
- 위치
- 생성자: 생성 시점에 불변식을 검증하여 객체가 항상 일관된 상태로 초기화되도록 한다
- Factory: 복잡한 불변식 검증이 필요할 때는 Factory 내에서 불변식을 검증하여 일관성을 유지한다
public class Order { private final List<OrderItem> items; private Order(List<OrderItem> items) { if (items == null || items.isEmpty()) { throw new IllegalArgumentException("Order must have at least one item"); } this.items = new ArrayList<>(items); } public static Order create(List<OrderItem> items) { return new Order(items); } }
Entity Factory와 Value Object Factory
- Entity Factory
- Entity는 고유한 식별자를 가지며, 복잡한 생성 로직을 포함할 수 있습니다. Entity Factory는 이러한 복잡한 생성 로직을 캡슐화한다
public class OrderFactory { public static Order create(Customer customer, List<OrderItem> items) { return new Order(customer, items); } }
- Value Object Factory
- Value Object는 식별자가 없으며, 불변 객체입니다. Value Object Factory는 복잡한 불변 객체의 생성 로직을 캡슐화한다
public class AddressFactory { public static Address create(String street, String city, String zipcode) { return new Address(street, city, zipcode); } }
저장된 객체의 재구성
- 재구성의 필요성
- 데이터베이스나 다른 저장소에서 객체를 다시 로드할 때, 저장된 데이터를 기반으로 객체를 재구성해야 합니다. 이는 객체의 일관성을 유지하고, 이전 상태를 복원하는 데 중요하다
- 재구성 방식
- 리포지토리 패턴: 리포지토리를 통해 객체를 로드하고, 필요한 경우 Factory를 사용하여 객체를 재구성한다
public class OrderRepository { @Autowired private EntityManager entityManager; public Order findById(Long id) { OrderEntity orderEntity = entityManager.find(OrderEntity.class, id); return OrderFactory.reconstitute(orderEntity); } } public class OrderFactory { public static Order reconstitute(OrderEntity orderEntity) { List<OrderItem> items = orderEntity.getItems().stream() .map(itemEntity -> new OrderItem(itemEntity.getProductName(), itemEntity.getQuantity(), itemEntity.getPrice())) .collect(Collectors.toList()); return new Order(orderEntity.getCustomer(), items, orderEntity.getStatus()); } }
반응형
LIST
'도서' 카테고리의 다른 글
DDD - 에릭 에반스, (11장 분석 패턴의 적용) (0) | 2024.06.29 |
---|---|
DDD - 에릭 에반스, (10장 유연한 설계) (0) | 2024.06.29 |
DDD - 에릭 에반스, (5장 소프트웨어에서 표현되는 모델) (0) | 2024.06.29 |
DDD - 에릭 에반스, (4장 도메인 격리) (0) | 2024.06.29 |
DDD - 에릭 에반스, (2장 의사소통과 언어 사용) (0) | 2024.06.29 |