객체의 불변성을 유지하면서 @RequestBody를 효과적으로 사용하기 위한 패턴과 기술

허진혁·2024년 2월 29일
0

Spring에서 @RequestBody 애노테이션은 클라이언트로부터 받은 JSON 요청 바디를 Java 객체로 변환하는데 사용하고 있어요. 이 과정에서 객체의 불변성을 유지하는 것은 애플리케이션의 안정성, 가독성 및 유지보수성을 높이는 중요한 요소에요. 객체의 불변성을 유지하면서 @RequestBody를 효과적으로 사용할 수 있는 다양한 패턴과 기술을 탐구해봐요 !!

객체의 불변성이 중요한 이유

객체의 불변성은 소프트웨어 개발에서 중요한 개념으로, 객체가 생성된 후 그 상태가 변경되지 않음을 보장하는 것이에요. 그렇다면 불변성은 어떠한 이점이 있을까요?

  • 안정성: 불변 객체는 멀티스레드 환경에서 안전하게 사용될 수 있으며, race condition이나 변조의 위험 없이 데이터 리소스를 공유할 수 있습니다.
  • 예측 가능성: 객체의 상태가 변경되지 않기 때문에, 시스템의 동작을 예측하는 것이 수월해집니다.
  • 디버깅 용이성: 상태 변화의 추적이 필요 없으므로, 오류를 찾고 수정하기가 더 쉬워집니다.

Spring의 @RequestBody와 객체 불변성

Spring에서 @RequestBody 애노테이션은 HTTP 요청의 바디를 Java 객체로 매핑할 때 사용되요.(자세한 과정은 링크를 클릭해주세요.) 이 과정에서 객체의 불변성을 유지하는 것은, 특히 API가 복잡하거나 데이터 무결성이 중요한 경우 더욱 중요해져요.

패턴과 기술

1. 빌더 패턴

빌더 패턴은 객체의 불변성을 유지하면서 복잡한 객체를 생성하는 데 사용되는 디자인 패턴이에요. 이 패턴을 사용하면 필요한 모든 속성을 설정한 후에만 객체를 생성할 수 있으며, 객체가 생성된 이후에는 속성을 변경할 수 없어요. (Lombok 라이브러리가 있다면 @Builder 애노테이션을 클래스에 추가하거나 필요한 파라미터만 받는 생성자 함수에 추가하면 되요.)

public class Product {
    private final String name;
    private final int price;

    private Product(Builder builder) {
        this.name = builder.name;
        this.price = builder.price;
    }

    public static class Builder {
        private String name;
        private int price;

        public Builder setName(String name) {
            this.name = name;
            return this;
        }

        public Builder setPrice(int price) {
            this.price = price;
            return this;
        }

        public Product build() {
            return new Product(this);
        }
    }
}

위의 Product 클래스는 불변이며, Builder 내부 클래스를 통해 인스턴스를 생성해요. 속성이 한 번 설정되면 변경할 수 없으니 객체의 불변성을 보장해요. 빌더 패턴은 객체의 생성 과정을 단계별로 안전하게 분리하여, 최종적으로 완성된 객체만을 사용할 수 있어요. 이러한 접근 방식은 객체가 일관된 상태를 유지하도록 보장하며, 오류 추적과 디버깅 과정을 단순하게 만들어줘요.

2. 생성자를 통한 의존성 주입

객체가 필요로 하는 모든 것을 생성자를 통해 제공함으로써, 객체가 생성 시점에 완전히 초기화될 수 있도록 하는 방식이에요. 이 방식은 객체의 불변성을 강제하고, 객체가 일관된 상태를 유지하도록 해줘요.

public class Order {
    private final String orderId;
    private final List<Product> products;

    public Order(String orderId, List<Product> products) {
        this.orderId = orderId;
        this.products = Collections.unmodifiableList(new ArrayList<>(products));
    }
}

Order 클래스는 생성 시 orderIdproducts 를 받으며 products리스트는 변경 불가능하게 만들어 객체의 불변성을 유지해요.

❗️ 주의할점

Order 클래스 예시에서는 List<Product> 타입의 products 필드를 가지고 있어요. 이 필드는 final로 선언되어 있어, products 필드 자체는 생성 후 변경할 수 없어요. 하지만 List는 변경 가능한 컬렉션으로, 리스트 내의 요소를 추가하거나 삭제하는 등의 변경 작업이 가능해요. 따라서 단순히 final 선언만으로는 리스트의 불변성을 완전히 보장할 수 없다는 치명적인 문제가 발생해요.

이를 해결하기 위해, 생성자에서 Collections.unmodifiableList(new ArrayList<>(products))를 사용하여 products 리스트를 불변 리스트로 만드는 것이에요. 이 방식을 통해, Order 객체가 생성된 후에는 products 리스트에 대한 어떠한 변경도 허용하지 않아요. 즉, 리스트의 내용을 변경하려고 시도할 경우 UnsupportedOperationException이 발생하게 만드는 것이죠. 이는 Order 객체의 불변성을 더욱 강화하는 방법으로, 객체가 가지고 있는 컬렉션 타입의 필드에 대한 변경을 완전히 차단해요.

결론적으로, 객체 내부의 컬렉션과 같은 변경 가능한 필드를 불변으로 만들기 위해서는, 단순히 final 키워드를 사용하는 것뿐만 아니라, 컬렉션을 불변 컬렉션으로 감싸는 등의 추가적인 조치가 필요하다는 것이 제가 전달하고 싶은 메시지에요!

3. record 활용 (Java 14 이상)

레코드 타입은 Java 14부터 도입되어, 데이터를 운반하는 목적의 클래스를 간결하게 표현할 수 있도록 해줘요. 자바에서 객체의 불변성을 유지하고, 특히 DTO 객체를 정의할 때 특히 유용해요. 레코드를 사용하면 필드, 생성자, getter를 한 줄의 코드로 정의할 수 있어 코드의 가독성과 간결성도 크게 향상시켜요.

레코드 타입은 다음과 같은 특징을 가집니다

  • 불변성: 레코드의 모든 필드는 final이며, 객체 생성 후에는 필드를 변경할 수 없습니다.
  • 간결성: 필드, 생성자, 그리고 필드에 대한 getter 메소드가 한 구문으로 정의됩니다.
  • 데이터 중심: 레코드는 데이터 운반에 최적화된 구조를 가지고 있으며, 자동으로 equals(), hashCode(), toString() 메서드를 구현합니다.
public record CustomerDto(@NotNull String name, @NotNull String email) {
	public Customer toEntity() {
			return Customer.build()
			.name(this.name)
			.email(this.email)
			.build();
	}
}

레코드를 사용함으로써, 개발자는 명시적으로 불변성을 유지하는 코드를 작성할 필요 없이, 불변 객체를 손쉽게 생성할 수 있어요. 이는 멀티스레드 환경에서 데이터 공유 시 발생할 수 있는 복잡성과 오류 가능성을 크게 줄여준다는 이점을 만들어요.

결론

객체의 불변성을 유지하는 것은 소프트웨어 개발, 특히 멀티스레드 환경에서의 안정성, 시스템의 예측 가능성 향상, 그리고 디버깅의 용이성 측면에서 중요해요. Spring의 @RequestBody 를 사용하는 컨텍스트에서 이러한 불변성을 유지하는 것은 데이터의 일관성과 무결성을 보장함으로써 애플리케이션의 신뢰성을 높일 수 있어요.

이 글을 통해 소개된 패턴과 기술을 활용하여, 불변성을 필요로 하는 다양한 상황에서 효과적으로 문제를 해결할 수 있기를 기대할게요. 항상 불변성의 원칙을 염두에 두고 개발해서, 더 견고하고 안정적인 소프트웨어를 만드는 데 한 걸음 더 다가가 봐요!

profile
Don't ever say it's over if I'm breathing

0개의 댓글