Spring Framework 백엔드 개발업무를 진행하면서 기존에 존재했던 코드들을 확인할 때 가장 힘이 들었던 부분은 객체의 생성과 상태 변경 로직들이 여러 비즈니스 로직에 나뉘어 녹아들어 있을 때였습니다.
// 극단적 예시이긴 하지만 홍길동 이름을 가진 Person객체가
// checkPerson 메소드에 매개변수로 전달후 name 출력값을 확인하면
// 철수라는 이름으로 변경됩니다. 즉 checkPerson메소드에서 상태값을
// 변경하는 로직이 있었습니다.
Person person = new Person("홓길동");
checkPerson(person);
String name = person.getName();
// 출력값: 철수
System.out.println(name);
객체의 상태값을 비즈니스 로직 여기저기에서 변경한다면 코드를 이해하기까지 개발자가 너무나 많은 리소스를 투입해야 했었습니다. 이러한 문제점을 뼈저리게 느낀 적이 많아 강박 아닌 강박으로 객체를 생성하거나 상태를 업데이트할 때 비즈니스 로직에 파편화되지 않고 객체 자기 자신이 책임지도록 코드를 작성하고 있는 것 같습니다.
그중 첫 번째 객체 생성을 할 때 가장 많이 사용하는 정적 팩토리 메소드에 대해 이야기해보려고 합니다. GOF 패턴을 공부하다 보면 생성 패턴에서 많이 언급됩니다. 다만 오늘 이야기하고자 하는 정적 팩토리 메소드는 객체지향적인 방법이다라기보다는 실용적인 방법이라고? 생각되는 객체생성방식입니다.
@Getter
public class OrderEvent {
private Long orderId;
private Long paymentId;
private OrderState orderState;
@Builder(access = AccessLevel.PRIVATE)
private OrderEvent(Long orderId,
Long paymentId,
OrderState orderState) {
this.orderId = orderId;
this.paymentId = paymentId;
this.orderState = orderState;
}
public static OrderEvent createRequestOrder(Long orderId, Long paymentId) {
return OrderEvent.builder()
.orderId(orderId)
.paymentId(paymentId)
.orderState(OrderState.REQUEST_ORDER)
.build();
}
public static OrderEvent createCancelOrder(Long orderId) {
return OrderEvent.builder()
.orderId(orderId)
.orderState(OrderState.REQUEST_CANCEL)
.build();
}
}
OrderEvent객체는 주문에 대한 이벤트를 발행하는 객체입니다. 객체 생성을 타이트하게 관리하는 걸 좋아하기에 빌더패턴, 생성자 접근제어를 전부 Private로 관리합니다. OrderEvent는 오로지 정적 팩토리 메소드를 통해 생성합니다. 정적 팩토리 메소드를 사용하면 이름이 있는 생성자로 객체가 어떠한 이유로 생성이 되는지 명확하게 이해할 수 있습니다.
public PgPaymentResponseDto requestPgPayment(PgPaymentRequestDto pgPaymentRequestDto) {
PgPaymentResponseDto responseDto = pgWebclient.requestPayment(pgPaymentRequestDto);
if (!"00".equals(responseDto.getCode())) {
OrderEvent cancelOrderEvent = OrderEvent.createCancelOrder(pgPaymentRequestDto.getOrderId());
kafkaEventProducer.sendEvent(CANCEL_ORDER_TOPIC, cancelOrderEvent);
throw new IllegalArgumentException("결제에 실패하였습니다.");
}
OrderEvent requestOrderEvent = OrderEvent.createRequestOrder(
pgPaymentRequestDto.getOrderId(), pgPaymentRequestDto.getPgPaymentId());
kafkaEventProducer.sendEvent(REQUEST_ORDER_TOPIC, requestOrderEvent);
return responseDto;
}
위 로직은 결제를 완료 후 결제 성공, 실패에 따라 주문 이벤트를 발행하는 로직입니다. 카푸카를 통해 이벤트를 발행할 때 사용되는 OrderEvent 객체를 팩토리 메소드를 통해 주문 취소를 위한 객체생성인지 주문 요청을 위한 객체생성인지를 명확하게 관리할 수 있게 됩니다.
네이밍 | 의미 | 예시 |
of() | 간결하고 직관적인 객체 생성 | List.of(1, 2, 3)`, `EnumSet.of(A, B) |
from() | 변환할 때 사용 | Duration.from(anotherDuration) |
valueOf() | 기존 값을 변환 | Integer.valueOf("123") |
create() | 새 객체를 생성 | Payment.createSuccess(orderId) |
newInstance() | 새 인스턴스를 만들 때 사용 | Class.newInstance() |
getInstance() | 싱글턴이나 캐싱된 객체를 반환 | Logger.getInstance() |
instance() | 단순 객체 반환 | Clock.systemUTC().instant() |
팩토리 메소드 네이밍은 위 표를 참고하여하시면 협업을 할 때 팩토리 메소드를 식별하고 의미를 해석할 때 큰 빛을 발휘하실 겁니다!
두 번째 객체의 상태변경을 어떻게 하면 유지보수하기 좋고 직관적으로 관리할 수 있는지 이야기하고자 합니다. 결국 객체 자기 자신이 상태변경에 책임을 온전히 지게 하는 방법으로 관리하니 유지보수하기 수월했고 로직을 확인할 때 직관적이었습니다.
@Getter
public class Order {
private Long orderId;
private OrderStatus state;
public void completed() {
if (this.status == OrderStatus.CANCELED) {
throw new IllegalStateException("취소된 주문은 완료할 수 없습니다.");
}
this.status = OrderStatus.COMPLETED;
}
public void cancel() {
if (this.status == OrderStatus.COMPLETED) {
throw new IllegalStateException("완료된 주문은 취소할 수 없습니다.");
}
this.status = OrderStatus.CANCELED;
}
}
위 Order객체는 자신의 state 상태값을 변경할 때 오로지 주문이 완료될 때는 completed 주문이 취소될 때는 cancel 메소드를 통해 자기 자신이 책임을 집니다. state를 변경하는 곳은 두 메소드로만 관리됩니다.
객체의 생성, 상태변경을 위 방법으로 진행하다 보니 비즈니스로직에서 분리되어 훨씬 간결하고 깔끔하게 관리할 수 있었습니다. 또한 협업을 진행하는 개발자 분들도 직관적으로 코드를 이해하기 수월했다고 평해주셨습니다.
이 방법이 만능은 아니지만 객체 생성, 상태관리를 최대한 자기 자신이 책임지도록 코드를 작성한다면 유지보수를 할 때 빛을 발휘 할거라고 생각합니다!
오늘은 간단하게 개인적으로 객체 생성, 상태관리를 하는 제 코드 스타일을 이야기해보았습니다.
모두들 행복한 코딩 하세요!
'Spring > experience' 카테고리의 다른 글
SpringFramework 버그에 대한 불안감 떨쳐내기 Spock Test (0) | 2025.02.19 |
---|---|
레거시 Spring Framework Project 패키지 리팩토링 (0) | 2025.02.16 |
Java 부동소수점 계산시 정확한 계산을 위해 왜 BigDesimal을 사용해야하는가? (1) | 2025.02.10 |
SpringFramework NullPointException 방지법 (0) | 2025.01.13 |
Spring Boot Batch 프로젝트에서 Webclient 사용시 주의점 (0) | 2024.12.05 |