엔티티, 애그리거트, 리포지터리 등 앞에서 살펴봤던 모델은 주문 취소, 배송지 변경과 같이 상태 변경 시 주로 사용.
반면, 이 장에서 설명할 정렬, 페이징, 검색 조건 지정과 같은 기능은 주문 목록, 상품 상세와 같은 조회 기능에 사용.
이러한 이유로 이 장에서 사용하는 예제 코드는 리포지터리(도메인 모델에 속한)와 DAO (데이터 접근을 의미하는)라는 이름을 혼용해서 사용함.
검색 조건이 고정돼있고 단순하면 다음처럼 특정 조건으로 조회하는 기능 사용
public interface OrderDateDao {
Optional<OrderData> findById(OrderNo id);
List<OrderData> findByOrderer(String ordererId, Date fromDate, Date toDate);
}
그런데 다양한 검색 조건을 조합해야 할 때가 있음
필요한 조합마다 find 메서드를 정의할 수 있지만 좋은 방법이 아님.
스펙(Specification)을 사용하면 됨
public interface Specification<T> {
public boolean isSatisfiedBy(T agg);
}
agg 파라미터는 검사의 대상의 되는 객체.
isSatisfiedBy() 메서드는 검사 대상 객체가 조건을 충족하면 true, 그렇지 않으면 false를 리턴한다.
예) Order 애그리거트 객체가 특정 고객의 주문인지 확인하는 스펙
public class OrdererSpec implements Specification<Order> {
private String ordererId;
public OrdererSpec(String ordererId) {
this.ordererId = ordererId;
}
public boolean isSatisfiedBy(Order agg) {
return agg.getOrdererId().getMemberId().getId().equals(ordererId);
}
}
리포지터리나 DAO는 검색 대상을 걸러내는 용도로 스펙을 사용.
만약 리포지터리가 메모리에 모든 애그리거트를 보관하고 있다면 다음과 같이 스펙 사용 가능.
public class MemoryOrderRepository implements OrderRepository {
public List<Order> findAll(Specification<Order> spec) {
List<Order> allOrders = findAll();
return allOrders.stream()
.filter(order -> spec.isSatisfiedBy(order))
.toList();
}
...
}
리포지터리가 스펙을 이용해 검색 대상을 걸러주므로 특정 조건을 충족하는 애그리거트를 찾고 싶으면 원하는 스펙을 리포지터리에 전달해 주면 됨
Specification<Order> ordererSpec = new OrdererSpec("madvirus");
List<Order> orders = orderRepository.findAll(ordererSpec);
하지만 실제 스펙은 이렇게 구현하지 않음.