이번에 운 좋게도 지인을 통해 세차새차라는 세차장 중개 플랫폼을 운영하는 스타트업의 프로젝트에 참여하게 되었는데, 첫 과제로 웹 소켓을 활용한 실시간 통신 기능을 구현하게 되었다.
요구사항
주어진 요구사항을 요약하면 다음과 같다.
사장님이 고객으로부터 전화를 받았을 때, 실시간으로 사장님의 아이패드(=세차장 사장님 인터페이스)에 전화를 건 고객의 정보가 입력된 예약 모달창을 띄워준다.
이 요구사항을 어떻게 구현하느냐? 요구사항 중 '실시간으로'라는 키워드를 충족시키기 위해 우리는 웹 소켓이 필요하다.
일단 알아둬야 할 사전 지식은, 세차새차는 비즈콜이라는 외부 서비스를 통해 사장님과 고객 간의 통화를 중개하고 있다는 점이다.
고객이 전화를 건 시점에 비즈콜이 세차새차의 CDR API를 호출하므로, 백엔드 단에서 CDR API가 호출되었을 때 특정 웹 소켓 경로로 메시지를 보내도록 구현하고, 프론트엔드 단에서 해당 메시지를 수신하도록 구현해주면 실시간으로 고객의 통화 정보를 프론트엔드 단으로 가져올 수 있다.
이러한 과정을 도식화하면 다음과 같이 표현할 수 있다.
여기서 붉은 색 화살표에 해당되는 동작을 백엔드와 함께 구현해야 하는 것이다.
[도메인 지식] 비즈콜(Bizcall)이란?
고객이 업체로 전화를 걸었을 때 고객과 업체 간의 통화를 중개해주고 전화 내역을 데이터로 제공해주는 안심번호 서비스.
백엔드 구현에 대한 명세는 백엔드 개발을 맡은 나의 친구 보름의 블로그에 기재되어 있으니 궁금하신 분은 참고하길 바란다.
const socket = new SockJS(`${API_SERVER}/ws/v1/offline-store-pad`)
이 때 인자로 넣어주는 url는 백엔드 단에서 선언해준 웹소켓 엔드포인트 경로이다.
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfiguration implements WebSocketMessageBrokerConfigurer {
@Value("${front-server.platform}")
private String platformUrl;
@Value("${front-server.auth}")
private String authUrl;
@Value("${front-server.manager}")
private String managerUrl;
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/ws/v1/topic"); // 클라이언트가 구독할 수 있는 목적지(prefix)를 설정합니다.
config.setApplicationDestinationPrefixes("/ws/v1/app"); // 메시지를 보낼 때 사용하는 prefix를 설정합니다.
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws/v1/offline-store-pad")
.setAllowedOriginPatterns(
"http://localhost:3000",
platformUrl,
authUrl,
managerUrl
).withSockJS(); // STOMP 엔드포인트를 설정하고, SockJS 지원을 추가합니다.
}
}
그 다음 stompjs의 Client 객체를 초기화해주는데, 소켓이 연결되었을 때(onConnect), 연결 도중 에러가 발생했을 때(onStompError), 웹소켓 통신이 끊겼을 때(onWebSocketClose) 콜백함수를 각각 정의해주었다.
소켓이 연결되면 stompClient 객체는 '/ws/v1/topic/call-store/${slug}' 경로를 구독한다. 해당 경로로부터 오는 메시지를 수신받기 위해 설정해주는 것이다.
구독한 경로로부터 메세지를 받았을 때 실행할 핸들러 함수도 매핑해준다.(handleReceiveMessage())