-
[궁금증] stateless 에 대해 고민해보자카테고리 없음 2024. 12. 5. 23:24
문제를 생각하게 된 계기
최근 객체지향적 설계에 대해 학습하는 과정에서, stateless 에 대해 여러 방면으로 고민해볼 기회가 있었다.
나는 위와 같이, ServiceConfig 에서 Service 클래스에 의존성 주입 → PromotionService 는 PromotionRepository 인터페이스를 의존 → PromotionRepositoryImpl 에서는 레포지토리를 구현하는 코드를 짰다. 이에 대해 다음과 같은 코멘트를 받게 됐다.
이를 요약해보자면,
1. 서비스 레이어는 무상태성이어야 하지 않나?
2. 레포지토리에서 데이터를 갖고 있다.
3. 결국 서비스 레이어는 stateful 한 것 같다 는 것이었다.
이에 대해 나는 코멘트에 반대해 stateless 한 것 같다는 생각을 했지만, 명확한 근거를 들 수가 없다는 생각이 들어 다시 파보기 시작했다.
Stateful 이란
그렇다면 먼저, Stateful 이 무엇인지부터 다시 알아봐야겠다.
이는 현재 상태를 특정 시점에 대해 저장한 정보가 있다는 것이다. 예를 들어, 특정 웹사이트에 로그인했을 때 장바구니에 상품이 여러 개 들어있는 경우와 같은 것이다.
Stateless 란?
과거와 상호작용을 하지 않는 것이다. Stateful과 완전히 반대로 모든 상호 작용이 매번 처음처럼 동작하게 되는 것이다.
서비스 레이어와 무상태성
왜 서비스 레이어는 stateless 여야 하는가?
우리가 흔히 스프링부트를 이용해 개발할 때에도, 필드로 어떠한 데이터를 넣지는 않는다. 단순히 레포지토리를 필드
로 선언해 외부에서 주입을 할 뿐이다. 왜 서비스 레이어는 이렇게 특정 데이터를 갖고 있지 않게 되는 것일까?
음식을 주문한다고 가정해보자. 이 식당에서는 별다른 주문 시스템이 없다. 한 웨이터가 처음부터 끝까지 손님을 책임져 서빙, 계산 모두 담당한다고 해보자. 웨이터 A 가 와서 주문을 받았다. 그는 노트 패드에 주문을 적었다.
만약 그가 퇴근 시간이 되었고, 주문을 적은 노트 패드를 전달 받지 못한 채로 웨이터 B 가 교대를 했다고 가정해보자.
그는 주문에 대해 아무것도 모르기에 손님은 처음부터 다시 주문을 해야 한다. 여기서 노트를 '상태'라고 볼 수 있다.
이번에는 노트가 아니라, 현대식 문물인 주문 저장 시스템을 도입하게 됐다고 가정해보자. 웨이터 A 는 주문을 받고, 노트에 적는 것이 아니라 바로 주문 저장 시스템에 등록을 하게 된다. 이후에 해당 웨이터가 퇴근을 하게 되어도, 다른 웨이터가 와서 문제 없이 주문을 받아 줄 수 있다. 웨이터마다의 노트가 사라졌기에 이는 '무상태성'이라고 볼 수 있다.
stateless 의 장점
1. 서비스가 수평적으로 확장이 가능해진다.
주문 저장 시스템만 건재하다면, 웨이터는 얼마든지 바뀌어도 된다.
2. '상태'를 외부화해서 데이터를 안전하게 보호할 수 있다.
웨이터가 사라지면 데이터도 같이 사라지는 것이 아니다.
3. 멀티 쓰레드 환경에서 안전하다.
여러 손님이 하나의 웨이터에게 주문을 하게 되면, 노트 패드는 뒤죽박죽이 되어 버린다.
그러나 메모하지 않고 완전히 외부화해서 주문 시스템에 저장하게 되면 안전하다.
stateless 하게 설계하는 방법
1. 의존성 주입을 한다.
객체의 상태를 외부에서 관리하게 되고, 내부에서는 이를 모르기에 해당 객체는 상태와 무관하게 된다.
2. 메서드의 파라미터로 데이터를 넘긴다.
클래스 내부에서는 상태를 모르고, 메서드 단위로만 상태를 알 수 있도록 한다.
3. 상태를 외부화한다.
외부의 주문 저장 시스템을 활용하는 것처럼, 상태를 저장하는 것을 밖으로 빼낸다.
그렇다면, 다시 내 코드로 돌아가보자.
다시 내 코드로 돌아가서 보자. 내 코드는 무상태성일까, 아닐까?
나의 결론은 '아니다'였다! 내가 생각한 이유들은 다음과 같다.
1. '상태'의 소유 주체가 다르다.
현재 서비스 레이어와 레포지토리 레이어는 서로 다른 책임을 지고 있다. 서비스 레이어는 레포지토리 레이어에서 관리하는 데이터들의 상태를 전혀 알지 못한다.
상태를 소유하고 있는 것은 오직 레포지토리 계층이며, 서비스 레이어는 단순히 이를 조회해 결과값을 받거나 저장하고만 있다.
2. 요청 간 독립성이 있다.
서비스 레이어는 현재 각 요청을 독립적으로 처리하고 있다. 즉, 이전 요청의 데이터나 상태가 이후 요청에 전혀 영향을 주고 있지 않다. 이는 결국 stateless 에 대한 정의와 동일하다. 따라서 해당 코드 역시도 무상태성을 지키고 있다고 생각했다.
3. 의존성 주입을 하고 있다.
서비스 레이어는 레포지토리 레이어의 구현체를 알 수가 없다. 현재 서비스 레이어는 인터페이스에만 의존하고 있다.
따라서, 레포지토리가 상태를 갖는다고 하더라도, 서비스 레이어의 무상태성은 완전히 보장해준다.
그렇다면, 레포지토리 레이어는 상태를 가져도 되는가?
서비스 레이어에 대해 계속해서 이야기했는데, 서비스 레이어는 무상태성인 이유가 레포지토리 레이어가 상태를 갖고 있기 때문이다.
그렇다면 레포지토리 레이어는 상태를 가져도 문제가 없을까?
이에 대해서 나는 이 역시 아니라고 결론을 내렸다.
왜 stateless 해야 하는가?
1. 확장성을 위해
다른 데이터베이스 엔진으로 교체된다면 상태에 의존하는 코드는 모두 영향을 받게 된다.
2. 멀티 쓰레드로부터 안전하기 위해
한번에 여러 메서드가 하나의 데이터에 접근하는 경우가 생길 수 있다.
현재 코드를 기반으로 무상태성으로 설계하는 방법
public class OrderRepositoryImpl implements OrderRepository { private List<Order> orders; // DI로 주입받음 public OrderRepositoryImpl(List<Order> orders) { this.orders = orders; } }
1. 캐싱을 한다.
2. static 키워드를 없앤다.
3. 데이터베이스로 상태를 외부화한다.
4. 의존성 주입을 한다.
그렇다면, 현재 레포지토리 레이어의 코드는 잘못되었는가?
- POJO 라는 코드의 특성상, 캐싱을 사용할 수가 없는 환경이다.
- 여러 서비스에서 해당 레포지토리를 주입 받고 있기에 static 키워드를 없애게 되면, 접근을 할 때마다 데이터가 초기화 되기에 정적으로 선언하는 것은 불가피하다. 레포지토리를 싱글톤으로 관리하게 되면 static 키워드는 삭제할 수 있을 것 같다.
- POJO 라는 코드의 특성상, 데이터베이스를 사용할 수 없다.
- static 키워드를 없앨 수 없는 것과 같은 이유로 의존성 주입도 불가능하다.
⇒ 즉, 현재 코드 구현의 상황 상 불가피한 선택이라고 보는 것이 맞다는 결론을 내렸다!
참고 자료
https://newsletter.systemdesigncodex.com/p/stateless-architecture
Stateless Architecture - What's the Deal?
And 3 techniques to build stateless services...
newsletter.systemdesigncodex.com