개요
이전에 주문상태를 계속확인하기 위해 앱에서 지속적으로 일정주기 마다 서버로 요청을 보내는 형식으로 처리를 했고, 찾아보니 이 방식을 Polling 이라고 했었다.
더 좋은 방식은 Long-Polling도 있지만 DB에다가 한번은 주문정보를 등록해야해서 병목현상이 일어날거 같아 굳이 쓰지는 않았다.
무튼 위와 같은 방법을 사용하면 비록 정보 최신화가 약간의 텀이 있는 것과, 서버가 좀 부담이 가는것을 제외하면 괜찮겠다... 싶었는데 계속 생각해보니 이러면 다수의 요청이 들어오면 결국엔 병목현상이 생기겠구나 싶었고 그로인해 찾다가 발견한것이 SSE 방식이다.
(폴링도 이거 찾다가 비교글 있어서 알게되었다.)
SSE
처음에 정리된 글들을 보면서 소켓이랑 방식이 크게 차이가 없는거 같은데 그냥 소켓을 쓸까? 라는 생각이 들정도로 비슷했고, 소켓을 사용해보았던 입장에서 구현이 더 쉽다는 말하나로는 딱히 선택할 이유가 되지는 않았는데
방화벽에 친화적이고 데이터 흐름을 pipe로 관리해 서버코드도 줄고 무엇보다 알림을 받는 클라이언트가 모바일 앱이라 배터리 사용량이 작다는 점이 가장 컷다.
(써보지 못한 기술도 사용해볼겸)
컨트롤러가 생각보다는 해줄게 많이 없어보여서 간단하게 이벤트 구독을 위한 요청을 열어두는 코드이다.
@Sse('listen')
@UseGuards(AuthGuard)
sse(
@Req() request: Request,
@TypedQuery() query: SSEQuery.SSEQueryListenOptions,
) {
try {
request.on('close', () => this.sseService.disconnectSSE(query.listener_email))
return this.sseService.listenSSE(query.listener_email)
} catch(e) { return e }
}
request를 받아 앱에서 연결이 끊어졌을경우 해당 구독자가 연결이 끊어졌다는 사실을 업데이트 해주었다.
listenSSE(
listener_email: string,
) : Observable<MessageEvent<SSESubject>> {
if(!!(this.notifiers.find(user => user.receiver_email === listener_email))) {
throw ERROR.Conflict
}
this.notifiers.push({
receiver_email: listener_email,
subject: this.sub,
observer: this.obs,
})
return this.obs
.pipe(
filter(data => this.notifyFactory(data, listener_email)),
map(data => ({
data,
} as MessageEvent<SSESubject>))
)
}
구독 요청이 들어오면 이미 이벤트를 구독중인지 확인하고 구독중인 사람이 아니라면 데이터를 업데이트 하고 옵저버를 반환해준다.
MessageEvent<> 형식이 관례? 인거 같은데 이는 앱에서 사용중인 rxdart에 있는거 보고 rxjs에도 있겠거니 해서 찾아서 썻다.
필터로 모두에게 전달되어질 메세지 인지, 특정인에게 전달되어질 메세지 인지 판단한다.
private notifyFactory(
data: SSESubject,
listener_email: string,
)
: boolean {
switch(data.notify_type) {
case "event-notify":
case "main-notify":
case "update-notify":
return true
case "user-notify":
const user_sub = data.subject as UserNotifySubject
return this.checkReceiver({
receiver_email: user_sub.receiver_email,
listener_email,
})
case "gift-notify":
const gift_sub = data.subject as GiftNotifySubject
return this.checkReceiver({
receiver_email: gift_sub.gift.to,
listener_email,
})
case "order-notify":
const order_sub = data.subject as OrderNotifySubject
return this.checkReceiver({
receiver_email: order_sub.receiver_email,
listener_email,
})
}
}
위와 같이 처리하였다.
마무리
공부를 계속해 나아가면서 다시 코드를 볼때마다 더 이쁘게 간략하게 직관적이게 작성 할 수 있는데 생각이 계속해서 든다.
현재 프로젝트의 주요기능 구현은 모두 끝나긴 했으나, 아직 해야할게 좀 남아있어서 서버정리 글을 작성 못하고 있다.
(분발하자...)