[OneStack]ํ ํ๋ก์ ํธ ๊ฐ๋ฐ ๊ณผ์ - WebSocket ์ค์๊ฐ ์ฑํ
๋ณธ ํ๋ก์ ํธ ๊ฐ๋ฐ์ ๊ณผ์ ํ์์ ํ์ด๋ ํ๋ก์ ํธ ๊ธฐํ, ๊ฐ๋ฐ, ๋ฐฐํฌ ๊ณผ์ ์ ๋ด๊ณ ์์ต๋๋ค.
OneStack
์์คํํ์ ๋๋ฌด ์ข์์ ์์ฑ์ : ์์ค๋ฌ์ฝค โญ๏ธ โญ๏ธ โญ๏ธ โญ๏ธ โญ๏ธ
www.onestack.store
๐ WebSocket์ด๋?
WebSocket์ ํด๋ผ์ด์ธํธ์ ์๋ฒ ๊ฐ์ ์๋ฐฉํฅ ํต์ ์ ๊ฐ๋ฅํ๊ฒ ํ๋ ํ๋กํ ์ฝ์ ๋๋ค. HTTP์ ๋ฌ๋ฆฌ ์ฐ๊ฒฐ์ ์ ์งํ๋ฉด์ ์ค์๊ฐ์ผ๋ก ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์๋ ์ฅ์ ์ด ์์ต๋๋ค.
โ WebSocket์ ์ฅ์
- ์ค์๊ฐ ์๋ฐฉํฅ ํต์ : ์๋ฒ์ ํด๋ผ์ด์ธํธ๊ฐ ์์ ๋กญ๊ฒ ๋ฐ์ดํฐ๋ฅผ ์ฃผ๊ณ ๋ฐ์ ์ ์์
- ๋ฎ์ ์ง์ฐ ์๊ฐ: ๊ธฐ์กด HTTP ์์ฒญ/์๋ต ๋ฐฉ์๋ณด๋ค ๋น ๋ฅธ ๋ฐ์ดํฐ ์ ์ก
- ํจ์จ์ ์ธ ์๋ฒ ๋ฆฌ์์ค ์ฌ์ฉ: ์ฐ๊ฒฐ ์ ์ง๋ก ์ธํ ๋ฐ๋ณต์ ์ธ ์์ฒญ ์ค๋ฒํค๋ ๊ฐ์
- ์ต์ ํ๋ ๋ฐ์ดํฐ ์ ์ก: ํค๋ ํฌ๊ธฐ๊ฐ ์์ ๋คํธ์ํฌ ๋น์ฉ ์ ๊ฐ
๐ WebSocket ๊ธฐ๋ฐ ์ค์๊ฐ ์ฑํ ๊ตฌํ
WebSocket์ ํ์ฉํ ์ค์๊ฐ ์ฑํ ์ ๊ตฌํํ๊ธฐ ์ํด ๋ค์๊ณผ ๊ฐ์ ๋จ๊ณ๋ก ์งํ๋ฉ๋๋ค.
1๏ธโฃ WebSocket ์ค์
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig implements WebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
config.enableSimpleBroker("/topic"); // ๊ตฌ๋
๊ฒฝ๋ก ์ค์
config.setApplicationDestinationPrefixes("/app"); // ๋ฉ์์ง ๋ฐํ ๊ฒฝ๋ก ์ค์
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/ws")
.setAllowedOrigins("https://www.onestack.store")
.withSockJS(); // SockJS ์ง์
}
}
๐น STOMP ํ๋กํ ์ฝ์ ์ ์ฉํ์ฌ ๋ฉ์์ง ๋ธ๋ก์ปค๋ฅผ ํ์ฉ
๐น SockJS๋ฅผ ์ง์ํ์ฌ ๋ธ๋ผ์ฐ์ ํธํ์ฑ์ ๋์
2๏ธโฃ ์ฑํ ๋ฉ์์ง ๋๋ฉ์ธ
@Getter @Setter
@NoArgsConstructor
@AllArgsConstructor
public class ChatMessage {
private Long messageId;
private String roomId;
private String sender;
private String nickname;
private String message;
private String type; // ENTER, TALK, LEAVE
private LocalDateTime sentAt;
private String socialType;
private String profileImage;
}
๐น ๋ฉ์์ง ์ ํ (์ ์ฅ, ๋ํ, ํด์ฅ) ์ง์ ๊ฐ๋ฅ
๐น ๋ณด๋ธ ์๊ฐ๊ณผ ํ๋กํ ์ด๋ฏธ์ง ์ ๋ณด ํฌํจ
3๏ธโฃ ์ฑํ ๋ฐฉ ๋๋ฉ์ธ
@Getter @Setter
public class ChatRoom {
private String roomId;
private String roomName;
private int memberNo;
private int proNo;
private int estimationNo;
private int maxUsers;
private LocalDateTime createdAt;
private List<ChatMessage> recentMessages;
private int roomAdmin;
private String estimationContent;
private String memberNickname;
private String proNickname;
private int progress;
private String lastMessage;
private LocalDateTime updatedAt;
private int unreadCount;
}
๐น ๊ฒฌ์ ์์ฒญ๊ณผ ์ฐ๋๋ ์ฑํ ๋ฐฉ ์์ฑ ๊ฐ๋ฅ
๐น ์ฝ์ง ์์ ๋ฉ์์ง ์ ๋ฐ ์ฑํ ๋ฐฉ ์งํ ์ํ ์ ์ฅ
4๏ธโฃ ์ฑํ ์ปจํธ๋กค๋ฌ
@Controller
@Slf4j
public class ChatController {
@Autowired
private ChatService chatService;
@Autowired
private SimpMessagingTemplate messagingTemplate;
@MessageMapping("/chat/message")
public void sendMessage(@Payload ChatMessage message) {
chatService.saveMessage(message);
messagingTemplate.convertAndSend("/topic/chat/room/" + message.getRoomId(), message);
chatService.updateChatRoom(message.getRoomId());
}
@GetMapping("/chat/room/{roomId}")
public String enterRoom(@PathVariable String roomId, Model model, HttpSession session) {
ChatRoom room = chatService.findRoom(roomId);
List<ChatMessage> messages = chatService.getRecentMessages(roomId, 50);
model.addAttribute("room", room);
model.addAttribute("messages", messages);
return "chat/room";
}
}
๐น WebSocket์ ํตํด ๋ฉ์์ง ์ ์ก ๋ฐ ์ฑํ ๋ฐฉ ์ ๋ณด ์ ๋ฐ์ดํธ
๐น ์ฑํ ๋ฐฉ ์ ์ฅ ์ ์ต๊ทผ ๋ฉ์์ง 50๊ฐ ๋ก๋
5๏ธโฃ ์ฑํ ์๋น์ค
@Service
@Slf4j
public class ChatService {
@Autowired
private ChatMapper chatMapper;
public String createRoom(ChatRoom room) {
String roomId = UUID.randomUUID().toString();
room.setRoomId(roomId);
room.setCreatedAt(LocalDateTime.now());
chatMapper.createChatRoom(room);
return roomId;
}
public void saveMessage(ChatMessage message) {
message.setSentAt(LocalDateTime.now());
chatMapper.insertMessage(message);
}
public List<ChatMessage> getRecentMessages(String roomId, int limit) {
return chatMapper.getRecentMessages(roomId, limit);
}
}
๐น DB์ ๋ฉ์์ง๋ฅผ ์ ์ฅํ์ฌ ์ฑํ ๋ด์ญ ์ ์ง
๐น ์ฑํ ๋ฐฉ ์ ์ฅ ์ ์ต๊ทผ ๋ฉ์์ง๋ฅผ ๋ถ๋ฌ์ฌ ์ ์๋๋ก ์ฒ๋ฆฌ
๐ก WebSocket ๊ธฐ๋ฐ ์ค์๊ฐ ์ฑํ ์ ํน์ง
โ ์ค์๊ฐ ํต์
- WebSocket์ ํตํ ์ฆ๊ฐ์ ์ธ ๋ฉ์์ง ์ ๋ฌ
- ์ฑํ ๋ฐฉ ์ฐธ์ฌ์ ๋ชจ๋์๊ฒ ๋์ ์ ์ก
โ ๋ฉ์์ง ์์์ฑ
- DB์ ๋ฉ์์ง ์ ์ฅ์ผ๋ก ์ฑํ ๋ด์ญ ์ ์ง
- ์ฑํ ๋ฐฉ ์ ์ฅ ์ ์ด์ ๋ฉ์์ง ๋ก๋ ๊ฐ๋ฅ
โ ์ฑํ ๋ฐฉ ๊ด๋ฆฌ
- ๊ฒฌ์ ์์ฒญ๊ณผ ์ฐ๋๋ ์ฑํ ๋ฐฉ ์์ฑ ๊ฐ๋ฅ
- ์ฐธ์ฌ์ ๊ถํ ๊ด๋ฆฌ ๋ฐ ์ฝ์ง ์์ ๋ฉ์์ง ์นด์ดํธ ์ ๊ณต
โ ๋ณด์ ๊ฐํ
- SockJS๋ฅผ ํตํ WebSocket ํด๋ฐฑ ์ง์ (๋ธ๋ผ์ฐ์ ํธํ์ฑ ํ๋ณด)
- ํ์ฉ๋ ๋๋ฉ์ธ์์๋ง ์ ๊ทผ ๊ฐ๋ฅ (CORS ์ค์ )
- ์ธ์ ๊ธฐ๋ฐ ์ธ์ฆ ์ ์ฉ ๊ฐ๋ฅ
๐ ๊ฐ์ ๊ฐ๋ฅ ํฌ์ธํธ
- ๋ฉ์์ง ํ ๋์ (๋๋ ๋ฉ์์ง ์ฒ๋ฆฌ ๋ฐ ์๋ฒ ๋ถํ ๋ถ์ฐ)
- ์ค์๊ฐ ์๋ฆผ ๊ธฐ๋ฅ ์ถ๊ฐ (ํธ์ ์๋ฆผ, ์ด๋ฉ์ผ ์๋ฆผ ๋ฑ)
- ํ์ผ ์ ์ก ๊ธฐ๋ฅ ์ง์ (์ด๋ฏธ์ง/ํ์ผ ์ ๋ก๋ ๋ฐ ๋ฏธ๋ฆฌ๋ณด๊ธฐ ์ ๊ณต)
๐ฏ ๋ง๋ฌด๋ฆฌ
WebSocket์ ํ์ฉํ ์ค์๊ฐ ์ฑํ ์์คํ ์ ์ฌ์ฉ์ ๊ฒฝํ์ ํฌ๊ฒ ํฅ์์ํฌ ์ ์์ต๋๋ค.
ํนํ ๊ฒฌ์ ์์ฒญ๊ณผ ์ฐ๋๋ ์ฑํ
๊ธฐ๋ฅ์ ํตํด ์๋ขฐ์ธ๊ณผ ์ ๋ฌธ๊ฐ ๊ฐ์ ์ํํ ์ํต์ด ๊ฐ๋ฅํฉ๋๋ค.
๋ค์ ํฌ์คํ
์์๋ ์ฑํ
๋ฐฉ ๋ด ๊ฒ์ํ๊ณผ ์บ๋ฆฐ๋ ๊ธฐ๋ฅ ๊ตฌํ์ ๋ํด ๋ค๋ค๋ณด๊ฒ ์ต๋๋ค.
๐Spring WebSocket๊ณผ STOMP, ๊ทธ๋ฆฌ๊ณ Kafka๋ฅผ ํ์ฉํ ์ค์๊ฐ ํต์
Spring WebSocket๊ณผ STOMP, ๊ทธ๋ฆฌ๊ณ Kafka๋ฅผ ํ์ฉํ ์ค์๊ฐ ํต์
1. ์น์์ผ(WebSocket)์ด๋?1.1 ์น์ ๊ธฐ๋ณธ ํต์ ๋ฐฉ์: HTTP์ ํ๊ณ์น ์ ํ๋ฆฌ์ผ์ด์ ์์ ์๋ฒ์ ํด๋ผ์ด์ธํธ ๊ฐ์ ๋ฐ์ดํฐ ๊ตํ์ ์ผ๋ฐ์ ์ผ๋ก HTTP๋ฅผ ์ฌ์ฉํ๋ค. ํ์ง๋ง HTTP๋ ๋น์ฐ๊ฒฐ์ฑ(stateless) ํ๋กํ ์ฝ๋ก,
pids.tistory.com