본문 바로가기
Spring/experience

Java 객체가 생성과 상태변경을 책임지는 방법

by include_hoany 2025. 3. 5.

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를 변경하는 곳은 두 메소드로만 관리됩니다.

객체의 생성, 상태변경을 위 방법으로 진행하다 보니 비즈니스로직에서 분리되어 훨씬 간결하고 깔끔하게 관리할 수 있었습니다. 또한 협업을 진행하는 개발자 분들도 직관적으로 코드를 이해하기 수월했다고 평해주셨습니다.

이 방법이 만능은 아니지만 객체 생성, 상태관리를 최대한 자기 자신이 책임지도록 코드를 작성한다면 유지보수를 할 때 빛을 발휘 할거라고 생각합니다!

오늘은 간단하게 개인적으로 객체 생성, 상태관리를 하는 제 코드 스타일을 이야기해보았습니다.
모두들 행복한 코딩 하세요!