그-실시간-두통-WebSockets이-아닌-당신의-Framework
Source: Dev.to
번역할 텍스트가 제공되지 않았습니다. 번역이 필요한 내용을 알려주시면 한국어로 번역해 드리겠습니다.
소개
몇 년 전, 실시간 주식 티커 대시보드를 개발하는 팀을 이끌고 있던 기억이 납니다. 📈
처음에는 모두의 열정이 엄청나게 높았습니다. 우리는 직접 실시간 애플리케이션을 만드는 것에 신이 났습니다.
하지만 곧 우리는 진흙에 빠진 듯한 상황에 처했습니다. 선택한 기술 스택은 일반 REST API에서는 꽤 잘 동작했지만, WebSocket이 등장하자 모든 것이 알아볼 수 없게 변했습니다.
우리 코드베이스는 두 개의 세계로 나뉘었습니다:
- 메인 애플리케이션 – HTTP 요청을 처리합니다.
- 별도 모듈 – WebSocket 연결을 처리합니다.
이 두 세계 사이에서 상태를 공유하는 것(예: 사용자의 로그인 정보)은 악몽이 되었습니다. 우리는 데이터를 동기화하기 위해 Redis나 메시지 큐와 같은 교묘하지만(오히려 추한) 방법에 의존할 수밖에 없었습니다. 🐛 코드가 점점 복잡해지고 버그가 늘어났습니다. 결국 제품을 출시했지만, 개발 과정은 긴 고통스러운 치아 발치와도 같았습니다. 🦷
이 경험을 통해 나는 깊은 교훈을 얻었습니다: 실시간 상호작용이 필요한 현대 웹 애플리케이션에서는 프레임워크가 WebSocket을 직접 처리하는 방식이 개발 경험과 프로젝트의 궁극적인 성공 혹은 실패를 직접 좌우한다는 점입니다. 많은 프레임워크가 WebSocket을 “지원한다”고 주장하지만, 대부분은 단순히 WebSocket 모듈을 메인 프레임워크에 “붙여넣는” 수준에 불과합니다. 이러한 “이식된” 솔루션이 우리 머리통을 아프게 하는 근본 원인인 경우가 많습니다.
“Grafted” 솔루션의 문제점
Java
// REST endpoint
@Path("/users")
public class UserResource {
@GET
public Response getUser(@Context SecurityContext sc) { … }
}
// WebSocket endpoint
@ServerEndpoint("/chat")
public class ChatEndpoint {
@OnOpen
public void onOpen(Session session) { … }
}
UserResource와ChatEndpoint는 서로 다른 두 세계에 존재합니다.- 각각 고유한 라이프사이클, 어노테이션, 그리고 의존성 주입 메커니즘을 가지고 있습니다.
ChatEndpoint내부에서 현재 사용자의 인증 정보를 얻으려면 보통 기본 HTTP 세션을 파고들어야 하는데, 프레임워크가 이를 쉽게 해주지는 않습니다. 😫
Node.js
// Express HTTP routes
const app = require('express')();
app.get('/profile', authMiddleware, (req, res) => { … });
// WebSocket server (ws)
const { WebSocketServer } = require('ws');
const wss = new WebSocketServer({ noServer: true });
wss.on('connection', (ws, request) => {
// No direct access to Express middleware here
});
app.get와wss.on('connection')은 완전히 별개의 로직 집합입니다.- 미들웨어를 공유하는 것이 번거롭습니다: WebSocket 업그레이드 요청 시 Express 미들웨어를 수동으로 호출해야 합니다.
상태 공유가 악몽이 된다
실시간 애플리케이션의 핵심은 상태입니다:
- 어떤 사용자가 어떤 WebSocket 연결에 해당하는가?
- 사용자가 구독하고 있는 채널은 무엇인가?
분리된 아키텍처에서는:
- REST API가 로그인 처리를 하고 세션 데이터를 HTTP‑세션 저장소에 저장합니다.
- WebSocket 모듈은 해당 세션에 직접 접근할 수 없습니다.
결과: 외부 의존성(예: Redis)을 “상태 중개자”로 도입해야 합니다. 이는 운영 비용을 증가시키고 새로운 장애 지점을 만들게 됩니다. 💔
네이티브하게 통합된 WebSocket 프레임워크: Hyperlane
Hyperlane은 WebSocket 핸들러를 다른 HTTP 라우트 핸들러와 정확히 동일하게 취급합니다—Context 객체를 받는 일반 async 함수입니다. 이들은 먼 친척이 아니라 자연스러운 “형제” 관계입니다.
일관된 API
- 미들웨어 – 한 번만 작성하면 HTTP 및 WebSocket 라우트 모두에 사용할 수 있습니다.
- Context – 요청별 데이터를 보관합니다(예: 인증된 사용자).
- 데이터 전송 – HTTP 본문, SSE 이벤트, 그리고 WebSocket 메시지에 동일한 메서드를 사용합니다:
ctx.send_body().await;
프레임워크는 WebSocket 프로토콜 세부 사항(메시지 프레이밍, 마스킹 등)을 추상화합니다. 비즈니스 페이로드(Vec)만 신경 쓰면 됩니다.
예시: 인증된 WebSocket 라우트
// auth_middleware.rs
pub async fn auth_middleware(mut ctx: Context, next: Next) -> Result {
let token = ctx.request.headers().get("Authorization");
// …validate token…
ctx.state.insert("user", user);
next.run(ctx).await
}
// secure_websocket_route.rs
pub async fn secure_websocket_route(mut ctx: Context) -> Result {
// The user was injected by `auth_middleware`
let user = ctx.state.get::("user").unwrap();
// Upgrade to WebSocket
let mut ws = ctx.upgrade().await?;
// Bind the connection to the user
ws.on_message(move |msg| {
// handle incoming messages, knowing `user`
}).await;
Ok(())
}
- HTTP 업그레이드 요청은 먼저
auth_middleware를 통과합니다. - 토큰이 유효하면 사용자 정보가
ctx.state에 저장됩니다. secure_websocket_route안에서는 별도의 연결 코드 없이 사용자를 가져올 수 있습니다. 😎
채팅 방에 방송하기
Hyperlane 문서에서는 헬퍼 크레이트(예: hyperlane-broadcast)를 사용해 메시지를 방송하는 방법을 보여줍니다. 패턴은 다음과 같습니다:
use hyperlane_broadcast::Broadcaster;
pub async fn chat_handler(mut ctx: Context) -> Result {
let mut ws = ctx.upgrade().await?;
let broadcaster = Broadcaster::new();
ws.on_message(move |msg| {
// Forward the message to all connected clients
broadcaster.broadcast(msg);
}).await;
Ok(())
}
기술 팁: 애플리케이션 시작 시 한 번만 브로드캐스터를 등록하고
Context를 통해 공유하면 여러 인스턴스를 생성하는 일을 방지할 수 있습니다. 이런 “베테랑” 조언은 개발자가 흔히 겪는 함정을 피하는 데 도움이 됩니다. 👍
결론
실시간 기능은 이제 웹 개발에서 “특수한 문제”가 아니라 현대 애플리케이션의 핵심 구성 요소가 되어야 합니다. 만약 여러분의 프레임워크가 여전히 WebSocket을 완전히 별도 방식으로 다루도록 강요한다면, 앞서 설명한 상태 공유, 미들웨어 통합, 운영 복잡성 등의 골칫거리를 피할 수 없게 됩니다.
WebSocket을 일등 시민(first‑class citizens) 으로 취급하는 프레임워크—통합 API, 공유 미들웨어, 단일 Context 객체 제공—는 보일러플레이트를 크게 줄이고 외부 상태 중개자를 없애며, 프로토콜 트릭이 아닌 비즈니스 로직에 집중할 수 있게 해줍니다.
Hyperlane 은 이러한 방식을 깔끔하고 효율적으로 구현하는 방법을 보여줍니다. 한번 사용해 보세요. 실시간 개발이 얼마나 매끄러워지는지 직접 체감할 수 있을 것입니다. 🚀
…조각난 방식이라면 이제는 이 시대에 맞지 않을 수도 있습니다.
진정한 현대 프레임워크는 실시간 통신을 핵심 모델에 매끄럽게 통합해야 합니다. 일관된 API, 공유 가능한 미들웨어 생태계, 그리고 통합된 상태 관리 메커니즘을 제공해야 합니다. Hyperlane 이 그 가능성을 보여줍니다.
따라서 다음에 실시간 기능 개발로 머리가 아플 때, 문제는 WebSocket 자체가 아니라 여전히 “이식된(grafted)” 사고방식으로 운영되는 구식 프레임워크에 있을 수 있다는 점을 고려해 보세요. 변화를 맞이할 때입니다! 🚀
GitHub Home (link to the repository)