Spring RSocket 마스터 — 4 Interaction Models

2026-05-03확률과 통계 마스터 노트

Spring RSocket 마스터 노트 시리즈 2편. 4 Interaction Models의 결정적 차이와 선택 기준, Request-Response가 HTTP와 비슷하지만 다른 점, Fire-and-Forget의 적합·부적합 영역, Request-Stream의 백프레셔 자연스러움, Channel(N:N 양방향)이 풀어내는 채팅·실시간 협업 시나리오, 4 모델별 코드 패턴까지.

이 글은 Spring RSocket 마스터 노트 시리즈의 두 번째 편입니다. 1편(기초)에서 RSocket 큰 그림을 다졌다면, 이번엔 그 핵심 — 4 Interaction Models.

이 4 모델로 거의 모든 통신 패턴 커버. 어떤 모델을 어디에 쓸지가 RSocket 설계의 핵심.

처음 4 Models가 어렵게 느껴지는 이유

처음 이 단원이 어렵게 느껴지는 이유는 두 가지예요. 첫째, Channel과 Request-Stream 차이가 막연합니다. 둘 다 스트림인데? 둘째, Fire-and-Forget이 진짜 필요한가 의문이 듭니다.

해결법은 한 가지예요. "입력 N개 / 출력 N개" 표로 묶기. 입력 1·출력 1=Request-Response, 입력 1·출력 0=FNF, 입력 1·출력 N=Stream, 입력 N·출력 N=Channel. 이 표만 잡으면 끝.

4 모델 한 줄 정리

모델 입력 출력 Mono/Flux
Request-Response 1 1 Mono → Mono
Fire-and-Forget 1 0 Mono → void
Request-Stream 1 N Mono → Flux
Channel N N Flux → Flux

1. Request-Response — HTTP 같음

Client ─request 1─→ Server
       ←─response 1─

전형적인 요청-응답. HTTP GET/POST 비슷.

서버

@Controller
public class UserController {

    @MessageMapping("user.{id}")
    public Mono<User> getUser(@DestinationVariable String id) {
        return userRepo.findById(id);
    }
}

클라이언트

Mono<User> user = requester.route("user.123")
    .retrieveMono(User.class);

user.subscribe(System.out::println);

사용처

  • CRUD 조회
  • 인증 확인
  • 단일 변환·검증

여기서 시험 함정이 하나 있어요. HTTP보다 가볍고 빠름. 단일 연결·바이너리·작은 헤더. 단순 RPC도 RSocket이 효율 좋음.

2. Fire-and-Forget — 응답 없음

Client ─request 1─→ Server
       (응답 X)

서버가 처리하지만 응답 안 함.

서버

@MessageMapping("event.log")
public Mono<Void> logEvent(LogEvent event) {
    return eventService.persist(event);   // Mono<Void>
}

클라이언트

requester.route("event.log")
    .data(event)
    .send()         // .send() — Mono<Void>
    .subscribe();

retrieveMono() 아닌 send() 사용.

사용처

  • 로깅·메트릭 (응답 불필요)
  • 이벤트 발행 (한 번 알리고 끝)
  • 분석 데이터 수집
  • 알림 발송 (성공 여부 무관)

여기서 정말 중요한 시험 함정 — FNF는 보장 X. 네트워크 끊김 시 손실. 보장 필요 = Request-Response (ack 받음). 또는 Kafka 같은 영속 메시지 큐.

3. Request-Stream — 1:N

Client ─request 1─→ Server
       ←─response 1─
       ←─response 2─
       ←─response 3─
       ...
       ←─complete─

한 요청에 여러 응답. 백프레셔 자연스러움.

서버

@MessageMapping("stocks.watch")
public Flux<StockPrice> watchStock(String symbol) {
    return stockService.priceStream(symbol);   // 무한 또는 종료
}

클라이언트

Flux<StockPrice> prices = requester.route("stocks.watch")
    .data("AAPL")
    .retrieveFlux(StockPrice.class);

prices
    .take(100)        // 100개만
    .subscribe(p -> System.out.println(p));

백프레셔 동작

prices.subscribe(new BaseSubscriber<>() {
    @Override
    protected void hookOnSubscribe(Subscription sub) {
        sub.request(10);   // 10개만 처음 요청
    }
    
    @Override
    protected void hookOnNext(StockPrice price) {
        // 처리
        process(price);
        // 1개 더 요청
        request(1);
    }
});

내부적으로 REQUEST_N 프레임 자동.

사용처

  • 실시간 시세 (주식·암호화폐)
  • 로그 스트리밍
  • 이벤트 구독
  • 검색 결과 대량 조회 (페이징 대신)
  • 알림 채널

여기서 정말 중요한 시험 함정 — Request-Stream = HTTP의 SSE(Server-Sent Events) 대체. 더 효율적·백프레셔 지원·양방향 가능 (Channel로 발전).

4. Channel — N:N 양방향

Client ─request 1─→ Server
       ←─response 1─
Client ─request 2─→
       ←─response 2─
Client ─request 3─→
       ...

양방향 N개 메시지 동시 흐름. 가장 강력한 모델.

서버

@MessageMapping("chat.room.{id}")
public Flux<ChatMessage> chat(
    @DestinationVariable String id,
    Flux<ChatMessage> incoming
) {
    return incoming
        .doOnNext(msg -> log.info("Received: {}", msg))
        .flatMap(msg -> chatRoom.broadcast(id, msg))    // 다른 클라이언트에 전달
        .mergeWith(chatRoom.subscribe(id))               // 자신도 받음
        .delayElements(Duration.ofMillis(10));
}

Flux<T> 입력 + Flux<R> 출력.

클라이언트

Flux<ChatMessage> outgoing = userInputFlux();           // 사용자 입력 스트림

Flux<ChatMessage> incoming = requester.route("chat.room.123")
    .data(outgoing)
    .retrieveFlux(ChatMessage.class);

incoming.subscribe(msg -> displayMessage(msg));

사용처

  • 채팅·실시간 협업
  • 온라인 게임
  • 실시간 협업 편집 (Google Docs 같은)
  • 양방향 텔레메트리 (IoT)
  • 트레이딩 시스템

여기서 정말 중요한 시험 함정 — Channel = WebSocket의 강력한 대체. WebSocket은 메시지 의미 X, Channel은 Reactive Streams + 백프레셔 + 메타데이터까지.

모델 선택 결정 표

시나리오 선택
단일 조회·생성·수정 Request-Response
로깅·이벤트 발행 (응답 X) Fire-and-Forget
실시간 시세·로그 스트림 Request-Stream
채팅·협업·게임 Channel
대용량 검색 결과 Request-Stream
양방향 IoT 텔레메트리 Channel

결합 — 한 서버에 여러 모델

같은 컨트롤러에 4 모델 모두:

@Controller
public class TradingController {

    @MessageMapping("order.place")        // Request-Response
    public Mono<OrderResult> place(Order order) { ... }

    @MessageMapping("event.log")          // Fire-and-Forget
    public Mono<Void> log(Event event) { ... }

    @MessageMapping("price.subscribe")    // Request-Stream
    public Flux<Price> subscribe(String symbol) { ... }

    @MessageMapping("trade.session")      // Channel
    public Flux<TradeMessage> session(Flux<TradeMessage> input) { ... }
}

같은 RSocket 연결에 4 모델 동시.

백프레셔 자연스러움

// 클라이언트가 천천히 처리
Flux<Event> events = requester.route("events")
    .retrieveFlux(Event.class);

events
    .flatMap(event -> processSlowly(event), 4)   // 동시 4
    // 자동으로 서버에 "4개씩만 보내라"
    .subscribe();

내부적으로 RSocket이 REQUEST_N 프레임 자동 관리.

모델 변경 — 같은 라우트에 다른 모델

여기서 시험 함정이 하나 있어요. 같은 라우트는 한 모델만. @MessageMapping("foo") 메서드가 Mono<X> 반환 = Request-Response 또는 FNF만. Flux 반환 = Stream·Channel만.

다른 모델이 필요하면 다른 라우트.

에러 전파

// 서버
return Flux.error(new BusinessException("Invalid"));

// 클라이언트
flux.doOnError(e -> {
    if (e instanceof BusinessException) {
        // 비즈니스 에러
    }
}).subscribe();

여기서 시험 함정이 하나 있어요. 에러는 ERROR 프레임으로 전송. 일반 RuntimeException → ApplicationErrorException. 비즈니스 에러는 명시적 응답 또는 메타데이터로.

시험 직전 한 번 더 — 자주 헷갈리는 함정 모음

여기까지가 2편의 핵심입니다. 시험 직전 또는 실무에서 헷갈릴 때 다시 펼쳐 볼 수 있게 압축 노트로 마무리할게요.

  • 4 Interaction Models — Request-Response / Fire-and-Forget / Request-Stream / Channel
  • 분류 — 입력 1/N, 출력 0/1/N
  • Request-Response = HTTP 같음, Mono → Mono
  • HTTP보다 가볍고 빠름 (단일 연결·바이너리)
  • Fire-and-Forget = 응답 X, Mono → void
  • .send() 사용 (retrieveMono 아님)
  • FNF는 보장 X — 손실 가능
  • 보장 = Request-Response 또는 Kafka
  • Request-Stream = 1 요청 → N 응답, Mono → Flux
  • 백프레셔 자연스러움
  • HTTP SSE 대체
  • 실시간 시세·로그·이벤트 구독
  • Channel = N:N 양방향, Flux → Flux
  • 가장 강력
  • WebSocket의 강력한 대체 (메시지 의미 + 백프레셔)
  • 채팅·협업·게임·IoT
  • REQUEST_N 프레임이 백프레셔 자동 관리
  • flatMap(concurrency) → 자동으로 서버에 페이스 통보
  • 같은 라우트 = 한 모델만
  • Mono 반환 = RR/FNF / Flux 반환 = Stream/Channel
  • 에러 = ERROR 프레임 → ApplicationErrorException
  • 같은 컨트롤러에 4 모델 모두 가능

시리즈 다른 편

공식 문서: RSocket Interaction Models 에서 더 깊이.

다음 글(3편)에서는 Spring RSocket 서버 — @MessageMapping·@DestinationVariable·라우팅·예외 처리까지 풀어 갑니다.

※ 이 포스팅은 쿠팡 파트너스 활동의 일환으로, 이에 따른 일정액의 수수료를 제공받습니다.

답글 남기기

error: Content is protected !!