
대규모 전자상거래 플랫폼의 주문 처리 시스템 개발 과정에서, 풍부한 도메인 모델과 빈약한 도메인 모델의 트레이드오프가 발생했습니다.
대량 주문(시간당 100만 건 이상)을 처리해야 하는 상황에서, 초기 설계의 한계와 성능 병목 현상이 드러났습니다.
다음은 초기 설계에 사용된 풍부한 도메인 모델의 예입니다.
public class Order {
private List<OrderItem> items;
private OrderStatus status;
private Money totalAmount;
public void addItem(OrderItem item) {
validateItem(item);
this.items.add(item);
recalculateTotal();
}
public void cancel() {
validateCancellable();
this.status = OrderStatus.CANCELLED;
refundPayment();
notifyCustomer();
}
private void validateCancellable() {
if (status == OrderStatus.SHIPPED) {
throw new OrderCannotBeCancelledException();
}
}
private void recalculateTotal() {
this.totalAmount = items.stream()
.map(OrderItem::getPrice)
.reduce(Money.ZERO, Money::add);
}
}
주요 특징
성능 병목
객체 생성 비용 증가: 대량 주문 처리 시, 도메인 객체의 복잡한 초기화로 인해 GC(가비지 컬렉션) 부하 발생.
메모리 사용량 증가: 모든 비즈니스 로직이 메모리 상에 유지됨으로써 시스템 리소스 소모 증가.
확장성 문제
성능 측정 데이터

다음은 빈약한 도메인 모델로 전환한 코드 예입니다.
@Service
public class OrderService {
private final OrderRepository orderRepository;
private final PaymentService paymentService;
public void cancelOrder(Long orderId) {
Order order = orderRepository.findById(orderId);
if (order.getStatus() == OrderStatus.SHIPPED) {
throw new OrderCannotBeCancelledException();
}
order.setStatus(OrderStatus.CANCELLED);
paymentService.refund(order.getPaymentId());
notificationService.notifyCustomer(order.getCustomerId());
orderRepository.save(order);
}
}
개선된 성능 측정 데이터

도메인 모델과 서비스 레이어의 장점을 모두 활용하기 위해 하이브리드 접근법을 도입했습니다.
설계 코드
public class Order {
// 핵심 비즈니스 로직만 유지
public void addItem(OrderItem item) {
validateItem(item);
this.items.add(item);
}
@Getter
private OrderStatus status;
}
@Service
public class OrderProcessingService {
public void processLargeOrderBatch(List<Order> orders) {
// 대량 처리 최적화 로직
var orderGroups = orders.stream()
.collect(Collectors.groupingBy(Order::getStatus));
// 병렬 처리
orderGroups.forEach((status, orderList) -> {
CompletableFuture.runAsync(() -> processOrderGroup(orderList));
});
}
}
성능 개선 데이터

성능 비교 그래프
아래 그래프는 모델별 성능 데이터를 시각화한 것입니다.

실제 적용 사례
교훈

대규모 애플리케이션에서 성능 테스트는 시스템의 안정성과 확장성을 보장하기 위해 필수적입니다.
성능 테스트를 수행하기 전에, 명확한 목표를 정의해야 합니다.
성능 테스트는 실제 운영 환경과 최대한 비슷한 환경에서 진행해야 합니다.
다양한 성능 테스트 도구를 활용할 수 있습니다.

테스트 시나리오는 실제 사용자 행동을 기반으로 설계합니다.
아래는 JMeter와 k6를 활용한 예제입니다.
1) JMeter 스크립트
<TestPlan>
<ThreadGroup>
<stringProp name="ThreadGroup.num_threads">100</stringProp>
<stringProp name="ThreadGroup.ramp_time">10</stringProp>
<stringProp name="ThreadGroup.duration">300</stringProp>
<HTTPsamplerProxy>
<stringProp name="HTTPSampler.domain">example.com</stringProp>
<stringProp name="HTTPSampler.path">/api/orders</stringProp>
<stringProp name="HTTPSampler.method">POST</stringProp>
<elementProp name="HTTPsampler.arguments">
<collectionProp>
<elementProp>
<name>body</name>
<stringProp name="Argument.value">{ "item": "product1", "quantity": 10 }</stringProp>
</elementProp>
</collectionProp>
</elementProp>
</HTTPsamplerProxy>
</ThreadGroup>
</TestPlan>
2) k6 스크립트
import http from 'k6/http';
import { sleep } from 'k6';
export let options = {
stages: [
{ duration: '10s', target: 100 }, // 10초 동안 100명 동시 접속
{ duration: '5m', target: 1000 }, // 5분 동안 부하 지속
{ duration: '1m', target: 0 }, // 부하 감소
],
};
export default function () {
const url = 'https://example.com/api/orders';
const payload = JSON.stringify({
item: 'product1',
quantity: 10,
});
const params = {
headers: {
'Content-Type': 'application/json',
},
};
http.post(url, payload, params);
sleep(1);
}
성능 테스트 후에는 결과 데이터를 분석해야 합니다.
결과 보고서 예시

성능 테스트는 CI/CD 파이프라인과 통합하여 자동화할 수 있습니다.