무상태 설계(stateless) 필요성
Table of Content
싱글톤 방식의 주의점
싱글톤 패턴이나 스프링 컨테이너 처럼 싱글톤 컨테이너를 사용할때는 객체 인스턴스를 하나만 생성해서 공유하는 방식에서는 상태를 유지(stateful)하게 설계하면 안된다.
→ 무상태(stateless)로 설계해야 한다.
•
특정 클라이언트에 의존적인 필드가 있으면 안된다.
•
특정 클라이언트가 값을 변경할 수 있는 필드가 있으면 안된다.
•
읽기만 가능해야 한다.
•
필드 대신에 자바에서 공유되지 않는 지역변수, 파라미터, ThreadLocal 등을 사용해야 한다.
Stateful (상태유지)상태 유지라 함은 클라이언트와 서버 관계에서서버가 클라이언트의 상태를 보존함을 의미
Stateless (무상태)무상태는 반대로 클라이언트와 서버 관계에서서버가 클라이언트의 상태를 보존하지 않음을 의미버 관계에서서버가 클라이언트의 상태를 보존함을 의미
상태를 유지할 경우(stateful) 발생하는 문제점
어떤 서비스에 주문 기능이 있다고 가정 할 때 다음과 같이 간단하게 볼 수 있다.
package hello.core.singleton;
public class StatefulService {
private int price; //상태를 유지하는 필드
public void order(String name, int price) {
System.out.println("name = " + name + " price = " + price);
this.price = price; // 이 부분이 문제
}
public int getPrice() {
return price;
}
}
Java
복사
테스트 코드를 통해 공유 필드인 price에 문제가 발생한다.
•
A가 10000주문(price에 10000할당)
•
B가 20000주문(price에 20000할당)
•
A주문 가격 조회 시 B가 업데이트해버린 20000이 조회 된다.
◦
동일한 인스턴스가 공유하는 필드인 price가 업데이트 되었기 때문
package hello.core.singleton;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import static org.assertj.core.api.Assertions.assertThat;
class StatefulServiceTest {
@Test
void statefulServiceSingleton() {
//given
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//when
//ThreadA: A사용자 10000원 주문
statefulService1.order("userA", 10000);
//ThreadA: B사용자 20000원 주문
statefulService2.order("userB", 20000);
//ThreadA: 사용자A 주문 금액 조회 하는 사이에 위 B사용자가 주문하면서 20000으로 업데이트 되버림.
// 같은 객체(service)를 바라보고 있어서 동일한 필드인 price에 할당해버림.
int price = statefulService1.getPrice();
System.out.println("price = " + price);
//then
assertThat(statefulService1.getPrice()).isEqualTo(20000);
}
static class TestConfig {
@Bean
public StatefulService statefulService() {
return new StatefulService();
}
}
}
Java
복사
무상태로 (stateless)로 변경
공유 필드를 제거하고 서비스의 주문 메소드 자체에서 주문 금액인 price를 반환하게 한다.
package hello.core.singleton;
public class StatefulService {
//private int price; //상태를 유지하는 필드
public int order(String name, int price) {
System.out.println("name = " + name + " price = " + price);
//this.price = price; // 이 부분이 문제
return price;
}
//public int getPrice() {
// return price;
//}
}
Java
복사
package hello.core.singleton;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.context.annotation.Bean;
import static org.assertj.core.api.Assertions.assertThat;
class StatefulServiceTest {
@Test
void statefulServiceSingleton() {
//given
ApplicationContext ac = new AnnotationConfigApplicationContext(TestConfig.class);
StatefulService statefulService1 = ac.getBean(StatefulService.class);
StatefulService statefulService2 = ac.getBean(StatefulService.class);
//when
//ThreadA: A사용자 10000원 주문
int userAPrice = statefulService1.order("userA", 10000);
//ThreadA: B사용자 20000원 주문
int userBPrice = statefulService2.order("userB", 20000);
//int price = statefulService1.getPrice();
//System.out.println("price = " + price);
//then
//assertThat(statefulService1.getPrice()).isEqualTo(20000);
System.out.println("price A = " + userAPrice);
System.out.println("price B = " + userBPrice);
assertThat(userAPrice).isEqualTo(10000);
assertThat(userBPrice).isEqualTo(20000);
}
static class TestConfig {
@Bean
public StatefulService statefulService() {
return new StatefulService();
}
}
}
Java
복사
이에 따라 같은 스프링 빈을 사용(스프링 컨테이너가 싱글톤으로 관리)하고 있지만 여러 유저들의 주문 금액이 공유 필드에 할당되는 것을 막을 수 있다. 무상태(stateless)
Related Posts
Search