public boolean validateOrder(Order order) {
try {
validateItems(order);
validateTotalPrice(order);
validateCustomerInfo(order);
} catch (InvalidOrderException e) {
log.info(e.getMessage());
return false;
}
return true;
}
private void validateItems(Order order) throws InvalidOrderException {
if (order.hasNoItems()) {
throw new InvalidOrderException("주문 항목이 없습니다.");
}
}
private void validateTotalPrice(Order order) throws InvalidOrderException {
if (order.isPricePositive()) {
throw new InvalidOrderException("올바르지 않은 총 가격입니다.");
}
}
private void validateCustomerInfo(Order order) throws InvalidOrderException {
if (order.hasNoCustomerInfo()) {
throw new InvalidOrderException("사용자 정보가 없습니다.");
}
}
// 커스텀 예외 InvalidOrderException
class InvalidOrderException extends Exception {
public InvalidOrderException(String message) {
super(message);
}
}
모듈(클래스 or 클래스의 집합)이 변경되는 이유는 한 가지여야 한다.
@Service
@RequiredArgsConstructor
public class PaymentService {
private final PaymentRepository paymentRepository;
public void processPayment(final String userId, final double amount) {
// 결제 처리
Payment payment = Payment.builder()
.userId(userId)
.amount(amount)
.status("SUCCESS")
.build();
paymentRepository.save(payment);
// 알림 전송
sendNotification(userId, "결제가 성공적으로 처리되었습니다.");
}
// 알림 전송
private void sendNotification(String userId, String message) {
System.out.println("User " + userId + ": " + message);
}
}
예시로 사용자의 id와 금액을 인자로 받아 결제 처리 및 알림을 전송하는 기능이 있다고 가정해보자.
이 때, 새로운 비즈니스 요구 사항으로 알림 전송 방식을 이메일이나 푸시 알림으로 확장하거나 결제 처리 로직에 추가적인 처리가 필요하다는 요청이 온다면?
PaymentService의 코드는 전면 수정될 것이고 수정된 코드로 인해 영향받는 코드가 생기게 된다.
이러한 문제가 발생하는 이유는 PaymentService가 모듈로써 하나의 책임을 가지지 않기 때문이며, 때문에 결제 처리와 알림 전송 기능 역할을 하는 책임을 분리해야 할 필요성이 생기게 된다.
이를 분리해보자.
// NotificaitonUtil
@Component
public class NotificationUtil {
public void sendNotification(final String userId, final String message) {
// 실제 알림 전송 로직 (간단한 예시를 위해 출력으로만 표기함)
System.out.println("User " + userId + ": " + message);
}
}
// PaymentService
@Service
@RequiredArgsConstructor
public class PaymentService {
private final PaymentRepository paymentRepository;
private final NotificationUtil notificationUtil;
public void processPayment(final String userId, final double amount) {
// 결제 처리 로직
Payment payment = Payment.builder()
.userId(userId)
.amount(amount)
.status("SUCCESS")
.build();
paymentRepository.save(payment);
// NotificationUtil을 주입받아 알림 전송
notificationUtil.sendNotification(userId, "결제가 성공적으로 처리되었습니다.");
}
}
알림을 전송하는 로직과 결제 처리 로직을 분리한 코드이다.
이제 알림을 전송하는 방식이 푸시 알림이나 이메일 전송 방식으로 변경되더라도 NotificationUtil의 실제 알림 전송 로직 한 군데만 변경하면 되기 때문에 변경점이 줄어들게 된다.
결과적으로 책임에 따라 코드를 분리하게되면 새로운 비즈니스 요구 사항이 생겼을 때 변화가 발생하더라도 수정할 대상이 명확해진다는 것이다.
확장에 대해 열려있고 수정에 대해 닫혀있다.
다시 코드로 살펴보자.
// NotificationUtil
@Component
public class NotificationUtil {
public void sendNotification(final String userId, final String message) {
// 알림 전송 로직 (간단한 출력)
System.out.println("User " + userId + ": " + message);
}
}
위에서 살펴봤던 NotificationUtil의 코드이다.