목차
개요
저는 기본적으로 자바 11 버전을 사용하고 있는데요. 김영한님의 강의를 듣던 중 자바 17 버전을 사용하게 되어야 해서 인텔리제이의 자바 버전을 변경하는 방법을 알아보았습니다.
Project Structure 설정
File → Project Structure (Ctrl + Alt + Shift + S)를 들어가서 Project 탭에서 SDK를 원하는 버전으로 바꿔줍니다.
1. [File] 탭을 선택한 후 [Project Structure] 탭을 클릭합니다.
2. Project Structure에 들어간 후 [Project] 탭을 클릭한 후 우측의 Edit 버튼을 눌러 원하는 자바 버전을 선택합니다.
3. 만약 원하는 자바 버전이 인텔리제이에 없다면 중앙 상단에 있는 + 버튼을 눌러 추가해줍니다.
4. [Add JDK...] 탭을 클릭하여 원하는 JDK 버전의 폴더를 추가해줍니다. 만약 원하는 JDK 버전의 폴더가 없다면 바로 위의 [Download JDK...] 탭을 클릭하여 편리하게 설치할 수 있습니다.
만약 IntelliJ 자체에서 JDK를 설치해야한다면
설치 및 등록을 완료하였으면 다음은 [Modules] 탭을 클릭한 후 [Sources] 탭을 눌러 Language Level을 버전에 맞게 바꾸어 줍니다. (디폴트면 자동으로 맞춰져있습니다.)
Sources 설정을 마친 후 우측의 [Dependencies] 탭을 클릭합니다. 이동 후 Module SDK의 버전을 맞게 바꾸어 줍니다.
Settings 설정
File → Setting (Ctrl + Alt + S)을 들어가서 Build, Execution, Deployment → Compiler → Java Compiler로 이동 후 Project bytecode version을 설정합니다.
Gradle 설정
마지막으로 본인이 사용하고 있는 빌드 툴의 파일에서 설정한 자바 버전이 올바른지 확인합니다.
저 같은 경우는 Gradle을 사용 중이기에 Gradle 설정을 진행했습니다.
reference
'Java' 카테고리의 다른 글
Arrays.asList() 사용 시 주의할 점 (0) | 2025.04.03 |
---|---|
List에서 특정 조건 만족하는 요소 삭제하기 (List.removeIf 사용하기) (0) | 2025.01.17 |
상속관계의 클래스 일 때, @Builder 사용하기 (0) | 2024.11.16 |
ObjectMapper, Pattern 등의 클래스를 싱글톤으로 사용해도 될지 고민했던 내역 (0) | 2024.11.15 |
Java에서의 HashMap 동작 원리 파악하기 (0) | 2023.07.27 |
김영한님의 JPA 책에서 상속 관련된 내용 읽다가 JPA에 직접 등록하는 것이 아닌 JpaRepository를 이용하는 경우 어떨까라는 궁금증에 글을 써봅니다.
목차
개요
김영한님의 JPA 책 중에 코드에서의 객체와 데이터베이스 사이의 패러다임 불균형을 JPA가 해결해준다라는 내용이 있습니다. 그에 관한 예시 중 상속에 관한 이야기가 있었습니다. '상속은 객체의 대표적인 속성인데 데이터베이스로 표현하려면 불편해진다. 이러한것들을 JPA가 해결해준다.' 라는 내용이었습니다. 이 때 들어준 예시 코드가 JPA를 이용하여 직접 등록해주는 방식 등 정석적인 방법처럼 보였는데 현재 저는 데이터베이스에 저장할 객체는 @Entity를 사용해주고 JpaRepository를 상속시켜 DB와 연동시키는 방법을 사용합니다. 이러한 과정에서 현재 제가 사용하는 방법에서도 상속을 이용한 뒤 DB에 저장시키는 방법론이 동일하게 적용되는지 궁금하여 실험을 해보았습니다.
(빠른 실험을 위해 만든 코드이기에 DTO 클래스도 없고 db 생성 시 데이터 정확한지 확인하는 과정도 없습니다.)
실험 시작
우선 책의 예시대로 부모 클래스를 만들어준 뒤 자식 클래스를 만들었습니다.
@Entity
@Getter
abstract class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int price;
}
@Entity
@NoArgsConstructor
@Getter
public class Album extends Item{
String artist;
}
@Entity
public class Movie extends Item{
String director;
String actor;
}
Album 클래스만 통해서 테스트 해볼 것이기 때문에 Album과 관련하여 Repository, Service, Controller를 생성했습니다.
public interface AlbumRepository extends JpaRepository<Album, Long> {
}
@Service
@RequiredArgsConstructor
public class AlbumService {
private final AlbumRepository albumRepository;
public Album createAlbum(Album album) {
return albumRepository.save(album);
}
}
@RestController
@RequiredArgsConstructor
public class AlbumController {
private final AlbumService albumService;
@PostMapping("/album")
public ResponseEntity postAlbum(@RequestBody Album album) {
albumService.createAlbum(album);
return new ResponseEntity(HttpStatus.CREATED);
}
}
위 과정들을 진행한 후, 코드를 실행하여 DB를 확인해보았습니다.
문제 발생
확인해본 DB 테이블의 결과는 신기했습니다.
'오 진짜로 영한님 책에서 나온 것처럼 DTYPE이 생기네.'
하지만 문제가 발생했음을 알 수 있었습니다.
'어? 책에서는 ITEM 테이블 따로 ALBUM 테이블 따로 생성되고 필요 데이터 조회할 때 Join 된다고 쓰여져있었는데? 왜 ITEM 테이블만 생성되고 한 테이블 안에 모든 칼럼이 들어가있지? 우선 값을 넣어보자'
'이렇게 되면 상황에 따라 null이 많아질텐데 정규화에 문제 생기지 않나?'
그래서 문제의 원인을 찾아보았습니다.
문제 해결
문제 해결법은 @Inheritance라는 애너테이션의 존재였습니다.
@Inheritance를 부모 클래스에 달아주고 상속 전략을 Joined로 선택해주면 모든게 해결되는거였습니다.
@Entity
@Getter
@Inheritance(strategy = InheritanceType.JOINED)
abstract class Item {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
String name;
int price;
}
이렇게 하면 각 Entity가 각 테이블로 잘 쪼개어진 것을 확인하실 수 있고요.
앞선 예시와 동일한 예시를 넣어주면 아래와 같이 깔끔하게 들어가는 것을 확인하실 수 있습니다.
문제는 문제가 아니었음을
앞선 설명들을 보시면 한 테이블에 모든게 나오는 것이 문제고 깔끔하게 나오는게 정답처럼 써놓았습니다.
하지만 해당 해결 과정을 위해 참고한 블로그를 공부하다보니 각각의 장단점과 함께 전략을 활용하는 방법이었음을 알게 되었습니다.
상속 전략을 정하는 방식과 각 장단점 그리고 DTYPE 이름도 정할 수 있음을 공부하고 싶으신 분들은 아래의 Reference 블로그 주소를 참조해주시면 좋을 것 같습니다.
reference
목차
코드는 아래의 깃허브 주소에서 WebSocketAPIToy 폴더 속에 있습니다.
https://github.com/Dev-Taehee/WebSocketToy
설정
저는 Spring Initializr를 활용하여 위와 같이 설정한 후 시작했습니다.
개요
WebSocket API의 가장 중요한 두 가지는 WebSocketHandler와 WebSocketConfigurer 입니다.
public class MyHandler extends TextWebSocketHandler {
@Override
public void handleTextMessage(WebSocketSession session, TextMessage message) {
// ...
}
}
WebSocketHandler는 말그대로 WebSocket을 다루는 친구입니다. WebSocket을 통해 오는 message들을 처리할 수도 있고 WebSocket이 연결된 후의 행동이나 WebSocket이 종료된 후의 행동을 관리할 수 있습니다.
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "/myHandler");
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
WebSocketConfigurer는 WebSocket과 관련된 설정들을 관리할 수 있게 해줍니다. WebSocketHandler를 등록해주는 것과 등록된 WebSocketHandler에 접속할 수 있는 URL을 구체적으로 정해주는 등의 작업을 할 수 있습니다.
만약 기본 설정인 Tomcat이 아닌 Jetty를 사용해주는 경우에는 아래와 같이 WebSocketServerFactory를 이용하여 사전 설정을 진행해주어야 한다고 합니다.
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(echoWebSocketHandler(),
"/echo").setHandshakeHandler(handshakeHandler());
}
@Bean
public DefaultHandshakeHandler handshakeHandler() {
WebSocketPolicy policy = new WebSocketPolicy(WebSocketBehavior.SERVER);
policy.setInputBufferSize(8192);
policy.setIdleTimeout(600000);
return new DefaultHandshakeHandler(
new JettyRequestUpgradeStrategy(new WebSocketServerFactory(policy)));
}
}
단순히 Handler와 Configurer 두 가지를 상속받은 후 구현하면 WebSocket 연결이 완료된다고 하니 굉장히 편리한 것 같습니다. 저는 두 클래스를 이용하여 채팅창을 구현해보는 실습을 통해 이해도를 높여보려고합니다.
실습
websocket 설정
websocket 설정을 위하여 앞서 말씀드린 Handler와 Configurer 2가지를 생성합니다.
우선 Handler입니다.
@Log4j2
public class MyHandler extends TextWebSocketHandler {
private static List<WebSocketSession> list = new ArrayList<>();
@Override
protected void handleTextMessage(WebSocketSession session, TextMessage message) throws Exception {
log.info("HandleTextMessage 진입");
log.info("session: " + session);
log.info("message: " + message);
for(WebSocketSession webSocketSession : list) {
webSocketSession.sendMessage(message);
}
}
@Override
public void afterConnectionEstablished(WebSocketSession session) throws Exception {
log.info(session + "클라이언트 접속");
list.add(session);
}
}
afterConnectionEstablished 메서드는 클라이언트가 접속하면 실행되는 메서드입니다.
이를 통해 클라이언트가 채팅창에 들어오면 해당 클라이언트의 WebSocketSession을 등록해주는 작업을 진행하도록 하였습니다.
handleTextMessage는 클라이언트가 텍스트 메시지를 보내면 해당 메시지를 채팅창에 참여한 사람들에게 보내주는 역할을 합니다.
다음은 Configurer입니다.
@Configuration
@EnableWebSocket
public class WebSocketConfig implements WebSocketConfigurer {
@Override
public void registerWebSocketHandlers(WebSocketHandlerRegistry registry) {
registry.addHandler(myHandler(), "ws/myHandler")
.setAllowedOrigins("*");
}
@Bean
public WebSocketHandler myHandler() {
return new MyHandler();
}
}
Configurer는 앞선 예제 코드와 크게 변한 것이 없습니다.
우선 Handler에 접속할 수 있는 url을 "ws/myHandler"로 사용했습니다.
웹소켓을 이용하면 통신이 http 통신에서 Upgrade 헤더를 사용해 ws 통신으로 바뀌는 것을 앞선 시간에 확인할 수 있었습니다. 그런 이유로 "ws/myHandler"로 url을 정해봤습니다.
setAllowedOrigins를 모두 허락해두었습니다.
채팅창 접속 가능한 ChatController 생성
채팅창에 접속 가능하도록 ChatController를 생성했습니다.
@Controller
public class ChatController {
@GetMapping("/myHandler")
public String getChat() {
return "chat";
}
}
endpoint를 "/myHandler"로 설정하고 접속시 chat.html을 열도록 해두었습니다.
프론트엔드 구성
프론트엔드 구성은 chatGPT로 작성한 후 제가 수정하는 방식으로 작업했습니다.
우선 chat.html 입니다.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>채팅 창</title>
<link rel="stylesheet" th:href="@{/css/styles.css}">
</head>
<body>
<div class="input-container">
<label for="username">유저 이름: </label>
<input type="text" id="username" class="username-input" placeholder="유저 이름을 입력하세요...">
<button onclick="saveUsername()" class="save-button">저장</button>
</div>
<div class="chat-box">
<div class="chat-container" id="chat-container">
<!-- 메시지가 여기에 나타남 -->
</div>
<div class="input-container">
<input type="text" id="message-input" class="message-input" placeholder="메시지를 입력하세요...">
<button onclick="sendMessage()" class="send-button">전송</button>
</div>
</div>
<script th:src="@{/js/script.js}"></script>
</body>
</html>
다음은 styles.css 입니다.
body {
font-family: Arial, sans-serif;
margin: 0;
padding: 0;
background-color: #f0f0f0;
}
.chat-box {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
height: 100vh;
}
.chat-container {
width: 80%;
max-width: 400px;
margin: 0 auto;
background-color: #fff;
border-radius: 10px;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
overflow-y: auto;
height: 400px;
padding: 20px;
}
.message {
margin-bottom: 10px;
padding: 10px;
border-radius: 10px;
max-width: 70%;
}
.sender-message {
background-color: #f3d6c6;
align-self: flex-end;
}
.user-message {
background-color: #ccc;
}
.input-container {
display: flex;
margin-top: 20px;
}
.message-input {
flex: 1;
max-width: 400px;
padding: 10px;
border-radius: 5px;
border: 1px solid #ccc;
}
.send-button {
margin-left: 10px;
padding: 10px 20px;
border: none;
border-radius: 5px;
background-color: #4caf50;
color: white;
cursor: pointer;
}
위의 HTML과 CSS를 합쳐 아래의 화면이 나오도록 구성하였습니다.
다음은 script.js 입니다.
// WebSocket 서버 주소
const websocketUrl = 'ws://localhost:8080/ws/myHandler';
// WebSocket 연결
const socket = new WebSocket(websocketUrl);
// 연결이 열렸을 때 실행되는 이벤트 핸들러
socket.addEventListener('open', (event) => {
console.log('WebSocket 연결이 열렸습니다.');
});
// 메시지를 받았을 때 실행되는 이벤트 핸들러
socket.addEventListener('message', (event) => {
// 서버로부터 받은 메시지를 화면에 표시
const receivedMessage = JSON.parse(event.data);
displayMessage(receivedMessage.sender, receivedMessage.content, false);
});
// 연결이 닫혔을 때 실행되는 이벤트 핸들러
socket.addEventListener('close', (event) => {
console.log('WebSocket 연결이 닫혔습니다.');
});
function sendMessage() {
var messageInput = document.getElementById('message-input');
var message = messageInput.value.trim();
if (message === '') return;
var username = localStorage.getItem('username');
const chatMessage = {
sender: username,
content: message
}
// 메시지를 WebSocket을 통해 서버로 전송
socket.send(JSON.stringify(chatMessage));
// // 입력한 메시지를 화면에 표시 (옵션)
// displayMessage(username, message, true);
// 입력 칸 비우기
messageInput.value = '';
}
// 화면에 메시지를 표시하는 함수 (옵션)
function displayMessage(sender, message, isSender) {
var chatContainer = document.getElementById('chat-container');
var messageElement = document.createElement('div');
messageElement.textContent = sender + ': ' + message;
messageElement.classList.add('message');
var username = localStorage.getItem('username');
if (username === sender) {
messageElement.classList.add('sender-message');
} else {
messageElement.classList.add('user-message');
}
chatContainer.appendChild(messageElement);
// 최하단으로 스크롤
chatContainer.scrollTop = chatContainer.scrollHeight;
}
// 로컬 스토리지에서 유저 이름을 불러오고 입력 필드에 채우는 함수
function loadUsername() {
var savedUsername = localStorage.getItem('username');
if (savedUsername) {
document.getElementById('username').value = savedUsername;
}
}
// 유저 이름을 로컬 스토리지에 저장하는 함수
function saveUsername() {
var usernameInput = document.getElementById('username');
var username = usernameInput.value.trim();
if (username === '') return;
localStorage.setItem('username', username);
alert('유저 이름이 저장되었습니다: ' + username);
}
// 페이지 로드 시 유저 이름을 불러옴
loadUsername();
// 아래에 메시지 전송 함수 및 기타 함수들을 추가하면 됩니다.
js 파일에서 볼 수 있듯이 앞서 WebSocket Configurer에서 설정한 주소인 ws/Handler로 연결을 요청하도록 되어 있습니다. 또한 웹소켓 연결이므로 http://가 아닌 ws:// 로 시작된다는 점을 확인하실 수 있습니다.
앞선 작업들을 통해 다음과 같이 채팅창 기능이 구현되었습니다.
'Spring > 웹소켓' 카테고리의 다른 글
SockJS 간단한 공부(SockJS는 이제 필요없는가?) (1) | 2023.10.18 |
---|---|
AJAX, HTTP Streaming, Long Polling 이란? (0) | 2023.09.22 |
웹소켓 공부를 시작하며 (0) | 2023.09.21 |
목차
개요
웹소켓 공부를 시작하며 본 Spring Documentation에는 다음과 같은 구문이 있었습니다.
'그러나 AJAX, HTTP Streaming 또는 Long Polling을 결합하면 간단하고 효과적인 제공할 수 있습니다. 예를 들어 뉴스, 메일, 소셜 피드는 동적으로 업데이트되어야 하지만 몇 분마다 업데이트해도 괜찮습니다. 또한 메시지의 양이 상대적으로 적은 경우에도 포함됩니다.'
웹소켓 공부를 시작하기 전에 대체재로 사용 가능한 AJAX, HTTP Streaming, Long Polling에 대해 간단히 파악하고 싶어 살짝 공부해보고 넘어가보려고합니다.
AJAX란?
AJAX란 Asynchronous JavaScript and XML의 약자입니다.
AJAX는 빠르게 동작하는 동적인 웹 페이지를 만들기 위한 개발 기법의 하나입니다.
AJAX는 웹 페이지 전체를 다시 로딩하지 않고도, 웹페이지의 일부분만을 갱신할 수 있습니다.
즉, AJAX를 이용하면 백그로운드 영역에서 서버와 통신하여, 그 결과를 웹 페이지의 일부분에만 표시할 수 있습니다.
이때 서버와는 다음과 같은 다양한 형태의 데이터를 주고받을 수 있습니다.
- JSON
- XML
- HTML
- 텍스트 파일 등
AJAX의 장점
AJAX를 이용하면 다음과 같은 장점이 있습니다.
- 웹 페이지 전체를 다시 로딩하지 않고도, 웹페이지의 일부분만을 갱신할 수 있습니다.
- 웹 페이지가 로드된 후에 서버로 데이터 요청을 보낼 수 있습니다.
- 웹 페이지가 로드된 후에 서버로부터 데이터를 받을 수 있습니다.
- 백그라운드 영역에서 서버로 데이터를 보낼 수 있습니다.
AJAX의 한계
AJAX를 이용하면 여러 장점을 가지지만, AJAX로도 다음과 같은 일들을 처리할 수 없습니다.
- AJAX는 클라이언트가 서버에 데이터를 요청하는 클라이언트 풀링 방식을 사용하므로, 서버 푸시 방식의 실시간 서비스는 만들 수 없습니다.
- AJAX로는 바이너리 데이터를 보내거나 받을 수 없습니다.
- AJAX 스크립트가 포함된 서버가 아닌 다른 서버로 AJAX 요청을 보낼 수는 없습니다.
- 클라이언트의 PC로 AJAX 요청을 보낼 수는 없습니다.
클라이언트 풀링(client pooling) 방식이란?
사용자가 직접 원하는 정보를 서버에게 요청하여 얻는 방식을 의미합니다.
이에 반해 서버 푸시(server push) 방식이란 사용자가 요청하지 않아도 서버가 알아서 자동으로 특정 정보를 제공하는 것을 의미합니다.
스마트폰에서 각종 앱이 보내는 푸시 알림이 서버 후시 방식의 대표적인 예입니다.
AJAX의 작동 원리 등 다양한 활용 방법에 대해 자세히 공부하고 싶으시면 아래의 글을 참고해주세요.
코딩교육 티씨피스쿨
4차산업혁명, 코딩교육, 소프트웨어교육, 코딩기초, SW코딩, 기초코딩부터 자바 파이썬 등
tcpschool.com
HTTP Streaming이란?
HTTP Streaming은 HTTP 프로토콜을 사용하여 데이터를 조각조각으로 전송하는 방식으로 실시간 미디어 스트리밍, 실시간 웹 애플리케이션 및 다른 실시간 데이터 전송 시나리오에서 사용됩니다. 이것은 브라우저나 앱 클라이언트가 데이터를 조금씩 수신하고 화면에 표시하거나 처리할 수 있도록하는 기술입니다.
HTTP Streaming의 장점
HTTP Streaming을 사용하면 다음과 같은 장점이 있습니다.
- 미디어 스트리밍을 통해 사용자가 콘텐츠를 실시간으로 시청하거나 듣는 동안 데이터의 일부만 다운로드할 수 있기에 대역폭을 절약하고 사용자 경험을 향상시킬 수 있습니다.
- HTTP를 기반으로 하는 스트리밍은 다양한 플랫폼과 장치에서 지원되므로, 웹 브라우저, 모바일 앱, 스마트 TV 등 다양한 환경에서 사용할 수 있습니다.
- HTTP를 사용하면 콘텐츠를 캐싱하고 CDN(Content Delivery Network)을 통해 전송하여 콘텐츠 전송 성능을 최적화할 수 있습니다.
HTTP Streaming의 한계
HTTP Streaming에 많은 장점이 있지만 다음과 같은 한계점이 있습니다.
- HTTP Streaming에서 데이터는 조각으로 전송되므로 처음 몇 조각을 수신한 후에 재생이 시작될 수 있습니다. 이로 인해 초기 버퍼링 지연이 발생할 수 있습니다.
- HTTP Streaming은 본질적으로 데이터 전송에 대한 암호화 또는 보안 조치를 제공하지 않습니다. 추가 보안 계층이 없으면 채팅 애플리케이션을 통해 교환되는 정보는 무단 액세스에 취약할 수 있습니다.
- 다양한 스트리밍 프로토콜이 존재하기에 브라우저 및 장치에 따라 호환성에 문제가 발생할 수 있습니다.
- HTTP Streaming은 서버 및 클라이언트 측에서 추가 리소스를 사용하므로 서버에 대한 리소스가 집약적일 수 있습니다. 그렇기에 대규모 미디어 스트리밍 서비스의 경우 서버 인프라 및 대역폭 관리가 중요한 문제가 될 수 있습니다.
HTTP Streaming에 대해 자세히 알아보고 싶으신 분은 아래 글을 참고해주세요.
What is HTTP Streaming?
Read the Real-time communication API Blog now.
www.pubnub.com
Long Polling 이란?
Long Polling 방식은 클라이언트가 서버에 HTTP Request를 요청하면 서버는 대기하고 있다가 이벤트가 발생 시 응답을하고 클라이언트는 응답을 받자마자 다시 서버에 Request를 요청보내 실시간성을 살리는 방식입니다.
Long Polling 장점
데이터가 업데이트되면 그 즉시 클라이언트에게 응답을 보내고 전달받은 데이터를 처리하므로 실시간성이 아주 높습니다.
Long Polling 단점
다수의 클라이언트가 존재하여 동시에 이벤트가 발생할 경우, 서버는 각 클라이언트마다 응답을 해주어야하기 때문에 순간 서버의 부담이 증가하게 됩니다.
또한 데이터가 주어지는 즉시 바로 반응하고 보내므로 요청 간격이 줄어든다면 polling보다 훨씬 데이터를 많이 보내게됩니다.
reference
'Spring > 웹소켓' 카테고리의 다른 글
SockJS 간단한 공부(SockJS는 이제 필요없는가?) (1) | 2023.10.18 |
---|---|
WebSocket API를 사용해 채팅창 구현하기 (0) | 2023.10.10 |
웹소켓 공부를 시작하며 (0) | 2023.09.21 |
목차
개요
게임동아리 멤버들과 주말에 내전을 즐기며 가끔 칼바람나락 내전을 즐길 때가 있었습니다.
롤 게임에서 사용자 설정 방을 만들면 아쉬운 점이 칼바람나락의 주사위 기능이 없다는 점이었습니다.
그래서 랜덤 팀 나누기 디스코드 봇을 만들던 때와 비슷하게 프로젝트로 개발하려고 생각했었는데요.
한 웹 페이지에 10명의 인원이 상호작용이 가능하려면 웹소켓을 이용해야한다는 점을 알게되었습니다.
이에 저는 웹소켓에 대해 공부해보고 사용해보려 합니다.
아래의 Spring Documentation을 참고하여 진행하려고 합니다.
WebSockets :: Spring Framework
The WebSocket protocol, RFC 6455, provides a standardized way to establish a full-duplex, two-way communication channel between client and server over a single TCP connection. It is a different TCP protocol from HTTP but is designed to work over HTTP, usin
docs.spring.io
Getting Started | Using WebSocket to build an interactive web application
In Spring’s approach to working with STOMP messaging, STOMP messages can be routed to @Controller classes. For example, the GreetingController (from src/main/java/com/example/messagingstompwebsocket/GreetingController.java) is mapped to handle messages t
spring.io
웹소켓이란
WebSocket 프로토콜인 RFC6455는 단일 TCP 연결을 통해 클라이언트와 서버 간에 전이중 양방향 통신 채널을 설정하는 표준화된 방법을 제공합니다. 이는 HTTP와 다른 TCP 프로토콜이지만 포트 80 및 443을 사용하고 기존 방화벽 규칙을 재사용할 수 있도록 HTTP를 통해 작동하도록 설계되있습니다.
WebSocket 상호 작용은 HTTP Upgrade 헤더를 사용하여 업그레이드하거나 WebSocket 프로토콜로 전환하는 HTTP 요청으로 시작합니다. 다음 예에서는 이러한 상호 작용을 보여줍니다.
1. Upgrade 헤더입니다.
2. Upgrade 연결입니다.
일반적인 200 상태 코드 대신 WebSocket을 지원하는 서버는 다음과 유사한 출력을 반환합니다.
1. 프로토콜 스위치
성공적인 핸드셰이크 후에는 HTTP 업그레이 요청의 기반이 되는 TCP 소켓이 클라이언트와 서버 모두에 대해 계속 열려 메시지를 계속 보내고 받을 수 있습니다.
WebSocket 서버가 웹 서버(예: nginx) 뒤에서 실행 중인 경우 WebSocket 업그레이드 요청을 WebSocket 서버로 전달하도록 구성해야 할 수 있습니다. 클라우드 환경에서 실행되는 경우에도 마찬가지로 WebSocket 지원과 관련된 클라우드 제공업체의 지침을 확인해야합니다.
Upgrade 헤더란
HTTP 업그레이드는 클라이언트가 서버와 통신하기 위해 웹소켓과 같은 다른 프로토콜로 업그레이드를 요청할 수 있는 프로세스입니다.
reference: https://runebook.dev/ko/docs/http/headers/upgrade
HTTP와 WebSocket의 차이
웹소켓 연결 | HTTP 연결 |
WebSocket은 설정된 연결 채널을 재사용하여 클라이언트에서 서버로 또는 서버에서 클라이언트로 데이털르 보낼 수 있는 양방향 통신 프로토콜입니다. 연결은 클라이언트나 서버에 의해 종료될 때까지 유지됩니다. (ws:// 또는 wss:// 시작합니다.) |
HTTP 프로토콜은 연결 지향 전송 계층 프로토콜인 TCP 프로토콜 위에서 작동하는 단방향 프로토콜입니다. HTTP 연결이 닫히면 응답을 받은 후 HTTP 요청 방법을 사용하여 연결을 생성할 수 있습니다. (http:// 또는 https:// 로 시작합니다.) |
상태코드 101을 통해 WebSocket의 전환 프로토콜을 나타냅니다. | 상태코드 200을 통해 연결이 완료되었음을 나타냅니다. |
WebSocket을 사용해야 하는 경우
거래, 모니터링, 알림 서비스와 같은 실시간 애플리케이션의 경우 WebSocket을 사용하여 단일 통신 채널에서 데이커를 수신하면 좋다고 합니다.
이렇듯 WebSocket은 웹페이지를 동적이고 대화형으로 만들 수 있습니다. 그러나 AJAX, HTTP Streaming 또는 Long Polling을 결합하면 간단하고 효과적인 솔루션을 제공할 수 있습니다.
예를 들어 뉴스, 메일, 소셜 피드는 동적으로 업데이트되어야 하지만 몇 분마다 업데이트해도 괜찮습니다. 또한 메시지의 양이 상대적으로 적은 경우에도 포함됩니다.
WebSocket은 낮은 대기 시간, 높은 빈도 및 높은 볼륨의 조합의 경우에 큰 힘을 발휘합니다.
Reference
https://www.geeksforgeeks.org/what-is-web-socket-and-how-it-is-different-from-the-http/
'Spring > 웹소켓' 카테고리의 다른 글
SockJS 간단한 공부(SockJS는 이제 필요없는가?) (1) | 2023.10.18 |
---|---|
WebSocket API를 사용해 채팅창 구현하기 (0) | 2023.10.10 |
AJAX, HTTP Streaming, Long Polling 이란? (0) | 2023.09.22 |
목차
문제 이름 및 링크
구명보트
https://school.programmers.co.kr/learn/courses/30/lessons/42885
출제 사이트
프로그래머스
문제유형 및 난이도
문제유형: Greedy
난이도: 2레벨
코드 설명
이번에는 여러 방면으로 고민하다가 다른 사람의 풀이를 본 케이스입니다.
제가 참고한 블로그 링크를 아래에 남깁니다.
프로그래머스 Java Lv2 구명 보트
https://school.programmers.co.kr/learn/courses/30/lessons/42885 프로그래머스 코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는
aram-su.tistory.com
Priority Queue를 사용했다는 점이 신기했습니다.
제가 고민한 부분이 무게를 정렬하더라도 80kg와 20kg를 합치면 되는 경우는 어떻게 구분할까였습니다.
해당 부분이 잘 해결될 수 있던 점이 Priority Queue 라고 생각이 되었습니다.
Priority Queue에 보트를 하나씩 넣는다는 아이디어가 좋았습니다. 보트를 넣는 방식을 사람이 타고 남은 무게를 표시하는 방법을 통하여 다음 사람이 남은 보트에 탈 수 있는지 없는지를 판단하는 방법이 굉장히 좋았습니다.
'알고리즘 > 코딩테스트 준비' 카테고리의 다른 글
마지막 두 원소 - 쉬운 문제라도 읽기 편한 코딩을 하자 (0) | 2023.08.21 |
---|---|
공백으로 구분하기2 - 문자열 문제는 쓰는 기능만 쓰게 되는 것 같다. trim과 정규화의 활용성 (0) | 2023.08.17 |
카펫 - 수학적으로 풀었다고 생각한 풀이와 진짜 수학적 풀이 (0) | 2023.08.16 |
짝지어 제거하기 (0) | 2023.08.14 |
다음 큰 숫자 - Integer.bitCount()의 발견 (0) | 2023.08.14 |
목차
2023.08.22 - [CICD/학부수업내용복습] - 카테고리 생성 이유
앞서 작성했던 글의 내용해서 설명드렸듯 Jira와 Github를 연동한 이유는 이슈 관련 내용을 한 눈에 파악할 수 있도록 하기 위함이었습니다. 관련 작업을 진행해보도록 하겠습니다.
1. Jira와 Github 연동
우선 Jira와 Github를 연동하도록 하겠습니다.
Jira 상단의 [앱]을 클릭한 후 [더 많은 앱 살펴보기]를 클릭합니다.
검색칸에 "Github for Jira"를 입력합니다.
"Github for Jira"를 클릭합니다.
[Get app]을 클릭합니다.
[Get it now]를 클릭합니다.
잠시 기다리시면 완료되었다는 팝업이 뜹니다.
[Get started]를 눌러줍니다.
이후 나오는 화면에서 [Connect Github organization]을 클릭합니다.
다음 화면에서 어떤 GitHub를 연결할지 선택하라고 나옵니다.
저는 Github에서 github.com 도메인을 가진 저장소를 사용하고 있으니 좌측의 [GitHub Cloud]를 선택하도록 하겠습니다.
이후 넘어간 화면에서 Permission을 위해 [Authorize Jira]를 클릭합니다.
[Install GitHub for Jira on a new organization]을 클릭합니다.
어느 곳에 Jira를 설치할건지 선택하라고 주어집니다.
저는 개인 연습용으로 사용할 것이기 때문에 제 개인 Organization을 선택하겠습니다.
연습용으로 사용하기 위한 repository를 선택한 후, [Install]을 눌러줍니다.
이후 연결하고자 하는 Jira site와 연결을 진행합니다.
연결이 완료되었습니다.
연결이 완료된 후, 이슈를 생성해보면 그 동안 못보던 개발 탭에 [브랜치 만들기], [커밋 만들기]가 생긴 것을 확인하실 수 있습니다.
2. branch 생성하기
작업을 하는 팀 내부의 컨벤션에 따라서 다르겠지만 '소프트웨어개발실무' 수업에서 팀 작업을 할 때도 뮤지컬 커뮤니티 사이트를 만드는 프로젝트를 진행할 때도 저는 이슈별로 티켓을 발행하고 티켓마다 branch를 생성해서 해당 branch에서는 해당 이슈만 작업하도록 설정하는 것을 선호하였습니다.
Github와 연동한 Jira를 이용해서 branch를 생성해보겠습니다.
Jira를 이용해서 branch를 만드는 방법은 2가지가 있습니다.
2-1. Github repository에 branch 바로 생성하기
[브랜치 만들기]에 마우스를 올리면 버튼이 2개로 구분되어 있다는 것을 확인하실 수 있습니다.
이 중 첫 번째 버튼을 클릭하면 됩니다.
클릭 후, 이동한 페이지에서 어느 Repository에 branch를 생성할 것인지 어느 branch로부터 분기할 것인지 branch 이름은 무엇으로 할 것인지 정하도록 안내됩니다. 원하시는대로 설정한 후에 [Create branch]를 누르면 됩니다.
생성 후 Github repository에 가서 확인해보시면 branch가 잘 생성되었음을 확인하실 수 있습니다.
2-2. 로컬 Git에서 branch 생성하기
위와 같은 경우 말고 바로 로컬에서 브랜치를 생성해서 작업하는 경우가 더 많다고 생각이 되는데요.
그런 경우에는 [브랜치 만들기]의 2번째 버튼을 클릭하여 제공해주는 명령어를 복사하여 사용하시면 됩니다.
3. 커밋 만들기
커밋을 만들고 Jira 이슈와 연결하고 싶다면 커밋 메시지에 이슈 키를 포함시켜서 커밋을 하면됩니다.
이슈키란 이슈 좌측 상단에 있는 키 값을 뜻합니다.
[커밋 만들기] 버튼을 클릭하면 커밋 메시지 예시를 보여줍니다.
예시를 이용해서 다음과 같은 커밋 메시지를 만들어 보았습니다.
이제 Githug repository에 push를 진행하고 다시 이슈 창을 보면 커밋이 연동되어 있음을 확인할 수 있습니다.
해당 부분을 클릭하시면 이슈 관련 브랜치에 대한 모든 정보를 보기 편리한 형태로 제공되는 창이 나옵니다.
이 곳에서 해당 브랜치와 관련된 모든 커밋을 편리하게 보고 관리할 수 있습니다.
결론
이를 통해 Jira와 Github를 연동하고 칸반 보드 기반으로 이슈 티켓을 발행해 팀원에게 작업을 할당하면 팀원은 해당 이슈를 기반으로 브랜치를 생성하고 작업하면 작업 과정이 보기 편하게 자동으로 정리된다는 점을 알 수 있었습니다.
제가 '소프트웨어개발실무' 수업을 수강했을 때는 저희 팀의 팀장님이 관리자라고 가정하고 팀장님이 칸반보드에 업무를 각 팀원에게 배분하면 각 팀원이 이슈에 맞게 브랜치 생성해서 작업을 진행하고 PR을 날리면 팀장님이 버그가 없는지 확인하고 Merge하는 작업 과정을 설정하고 지켰던 기억이 있습니다.
당시 Confluence도 사용해서 문서를 관리했던 기억이 있는데요. 팀장님과 팀원 분들 모두 열심히 해주셔서 많은 것을 배워갔던 팀플로 기억합니다.
여담
뮤지컬 커뮤니티 사이트 프로젝트 당시에는 오늘 다루었던 내용을 InteliiJ와 Github의 연동을 통해 수행했었습니다. 만약 Slack과의 연동을 포함하여 통합적인 자동화 툴을 다루고자 하는 것이 아니라 오늘의 작업만 다룬다면 IntelliJ와 Github 연동을 통해 작업하는 것도 편하고 괜찮으니 고려해보시는 걸 추천드립니다.
'CICD > 학부수업내용복습' 카테고리의 다른 글
Jira와 Slack 연동 (0) | 2023.08.23 |
---|---|
카테고리 생성 이유 (0) | 2023.08.22 |
목차
2023.08.22 - [CICD/학부수업내용복습] - 카테고리 생성 이유
앞서 작성했던 글의 내용에서 설명드렸듯 Jira와 Slack 연동한 이유는 팀원들의 작업 상태 변화를 빠르게 파악하기 위함이었습니다. 관련 작업을 진행해보도록 하겠습니다.
1. 자동화 규칙 만들기 시작
프로젝트 보드 우측 상단에 보면 번개 모양의 아이콘이 있습니다.
아이콘을 클릭 후 [+ 자동화 만들기]를 눌러주세요.
[+ 자동화 만들기]를 눌러 들어간 곳에서 [규칙 만들기]를 클릭합니다.
2. 트리거 설정하기
먼저 트리거를 설정해야합니다.
트리거는 방아쇠라는 뜻으로 특정 액션을 실행시키는 역할을 맡습니다.
아래와 같이 굉장히 다양한 트리거 장치가 있습니다.
이 중 과거 과제에서의 목적이 팀원의 작업 상태 변화를 파악하는 것이 목적이었기에 "이슈 전환됨" 항목을 선택하겠습니다.
현재 칸반 보드에서 나올 수 있는 상태 변환은 2가지입니다. "해야 할 일 -> 진행 중"과 "진행 중 -> 완료"
이 중 우선 "해야 할 일 -> 작업 중"을 작업하도록 하겠습니다.
아래와 같이 상태 변화가 된 것을 확인할 수 있습니다.
3. 작업 추가하기 (Slack 메시지 보내기 작업)
다음 단계로 넘어가기 위해 [컴포넌트 추가]를 누르시면 아래와 같은 화면을 볼 수 있습니다.
브랜치 만들기, 작업 추가, 조건 추가와 같이 다양한 작업을 추가 할 수 있다는 것을 확인하실 수 있습니다.
저는 슬랙 메시지를 보내는 것이 목표이기 때문에 [THEN: 작업 추가]를 선택하도록 하겠습니다.
앞선 [트리거 만들기]에서 보셨던 것과 같이 다양한 선택을 할 수 있습니다.
이 중 저희는 "Slack 메시지 전송"을 선택하겠습니다.
선택하면 나오는 화면에서 웹후크와 메시지를 작성해주면 됩니다.
3-1. 웹후크 URL 가져오기
웹후크 URL은 Slack 채팅방의 주소라고 생각하시면 됩니다.
앞서 보신 화면에서 "웹후크 URL" 하단의 [수신 웹후크를 구성]을 클릭합니다.
해당 버튼을 클릭하신 후 이동한 페이지에서 (만약 Slack 로그인이 안되어 있다면 로그인 진행 후) 메시지를 보낼 채널을 선택하시면 됩니다.
클릭하여 나온 화면에서 웹후크 URL을 복사합니다.
해당 URL을 앞선 화면의 "웹후크 URL" 입력창에 붙여줍니다.
3-2. 메시지 작성하기
메시지를 통해 우리가 얻고자 했던 부분은 "어떤 팀원"이 "어떤 작업"을 "어떤 작업 상태"를 가지고 있는지 파악하는 것이었습니다.
아래 링크를 참고하시면 원하는 값을 얻을 수 있는 키 값들이 안내되어 있습니다.
Jira smart values - issues | Cloud automation Cloud | Atlassian Support
Use smart values in automation rules to access and manipulate issue data in Jira Work Management Cloud. View the smart values in Jira Work Management Cloud.
support.atlassian.com
"어떤 팀원" = {{issue.assignee.displayName}}
"어떤 작업" = {{issue.summary}}
"어떤 작업 상태" = 일반 메시지로 표현
위 키 값을 이용하여 아래와 같이 메시지를 작성해보았습니다.
앞서 설명드리지 않은 {{issue.toUrl}}을 통해 <링크|텍스트> 양식을 만들어 업무 내용에 링크도 포함시키도록 하였습니다.
4. 마무리
마지막으로 자동화 이름을 지정하고 종료하면 끝입니다.
결과
아래와 같이 이슈를 작업 중으로 옮겼을 때 Slack 메시지를 받을 수 있었습니다.
여담
3년전의 Jira에는 아래와 같은 자동화 템플릿이 없었던거인지 아니면 여러 팀프로젝트를 하며 CI/CD에 관심을 가지다 보니 기능이 눈에 띄는 건지는 모르겠지만 Slack과 관련하여 이미 만들어진 자동화 템플릿이 많더군요.
그 이외에 자동화 템플릿이 많은 것을 보아 Jira를 적극적으로 활용하면 정말 편하고 좋은 자동화 툴이 될 것이라 생각됩니다.
'CICD > 학부수업내용복습' 카테고리의 다른 글
Jira와 Github 연동 (0) | 2023.08.23 |
---|---|
카테고리 생성 이유 (0) | 2023.08.22 |
목차
카테고리 생성 이유
약 3년 전 '소프트웨어개발실무' 라는 수업을 수강한 적이 있습니다. 해당 수업에서는 CI/CD의 중요성을 배우고 해당 내용을 실습하는 것이 목적이었습니다. 실습의 내용은 단순하게 깃허브를 연습하는 내용도 있었습니다. 마지막 과제 내용이 재밌었는데요. '스페이스 인베이더' 게임 코드가 주어졌을 때, 우리는 해당 게임을 유지 보수하는 직원이라는 시나리오가 주어집니다. 이 때, 고객의 요구사항이 주어집니다. "2인 플레이가 가능하게 해주세요.", "일시정지가 가능하게 해주세요", "난이도 선택 기능이 있으면 좋겠어요" 등등 다양한 요구사항이 주어질 때 해당 요구사항들을 잘 처리할 수 있는 CI/CD 환경을 구성하기 위한 방법을 팀원들과 강구해보고 실제로 구성해보며 구성된 CI/CD 환경을 토대로 고객이 주는 요구사항을 해결해보는 것이 과제였습니다.
팀원들과 많은 회의를 하며 CI/CD 환경을 구성하였고 성공적으로 유지 보수를 완료하여 좋은 성적을 받았던 것으로 기억합니다. 하지만 과거 작업했던 깃허브 레포가 삭제되었는지 안보이는 점 등 관련 자료를 정리해 저장해두지 않은 저의 잘못으로 관련 내용에 대해 증명할 근거가 없습니다.
해당 과제를 수행하며 얻은 지식이 굉장히 재밌었고 유익했던 것으로 기억하기에 복습한다는 생각으로 당시의 기억을 떠올리며 CI/CD 환경 구성 작업을 따라가보려합니다. 이 과정에서 기록을 토대로 공부한 증거도 남기고 싶습니다.
작업 종류
제가 기억하고 있는 작업은 아래와 같습니다.
- Jira와 Slack을 연동하여 칸반 보드에서 작업 형태가 변경되었을 때, Slack 메시지로 전달되어 팀원들의 작업 현황을 빠르게 파악할 수 있도록 함
- Jira와 Github를 연동하여 이슈 관련 내용을 한 눈에 파악할 수 있도록 함
- Github와 Jenkins를 연동하여 빌드 배포를 자동화 함
(위 작업 종류 3가지를 한 눈에 담긴 이미지 만들어서 여기에 놓기)
1. Jira와 Slack 연동
Jira와 Slack 연동
2023.08.22 - [CICD/학부수업내용복습] - 카테고리 생성 이유 앞서 작성했던 글의 내용에서 설명드렸듯 Jira와 Slack 연동한 이유는 팀원들의 작업 상태 변화를 빠르게 파악하기 위함이었습니다. 관련
dev-taehee.tistory.com
2. Jira와 Github 연동
Jira와 Github 연동
2023.08.22 - [CICD/학부수업내용복습] - 카테고리 생성 이유 앞서 작성했던 글의 내용해서 설명드렸듯 Jira와 Github를 연동한 이유는 이슈 관련 내용을 한 눈에 파악할 수 있도록 하기 위함이었습니다.
dev-taehee.tistory.com
3. Github와 Jenkins 연동
Github와 Jenkins의 연동은 부트캠프에서 공부할 때 다루었던 내용을 정리한 글이 있습니다.
해당 내용을 시도하려고 했던 이유가 애초에 '소프트웨어개발실무' 팀플에서 다뤄봤던 내용이어서 시도해봤던 것인데요. 이 글을 정리하면서 작업하면서 겪은 이슈나 해결 방법을 잘 정리해두는 것이 얼마나 중요한건지 깨달았습니다.
AWS EC2 환경 사용 시 Jenkins를 이용하여 CI/CD 시도한 내용
개요 Codestates PreProject 진행 시, Github Actions와 AWS Connector를 막아놔서 CI/CD를 쉽게 연결하기가 어려운 상황이 있었습니다. 수업 때 배웠던 내용이 적용이 안되어 당황스러웠지만 언제나 그렇듯이
dev-taehee.tistory.com
'소프트웨어개발실무' 팀플로 CI/CD 환경을 구축하는 경험을 했고 해당 경험 덕분에 CI/CD 환경을 새롭게 구축해야하는 상황에서도 겁먹지 않고 도전할 수 있게 되었습니다.
'CICD > 학부수업내용복습' 카테고리의 다른 글
Jira와 Github 연동 (0) | 2023.08.23 |
---|---|
Jira와 Slack 연동 (0) | 2023.08.23 |
목차
문제 이름 및 링크
마지막 두 원소
https://school.programmers.co.kr/learn/courses/30/lessons/181927
출제 사이트
프로그래머스
문제유형 및 난이도
문제유형: 기초
난이도: 0레벨
코드 설명
문제 자체는 굉장히 쉬웠습니다.
아래는 제 풀이입니다.
읽기가 조금 불편하다는 점을 확인하실 수 있습니다.
class Solution {
public int[] solution(int[] num_list) {
int[] answer = new int[num_list.length+1];
System.arraycopy(num_list, 0, answer, 0, num_list.length);
if(num_list[num_list.length-1] > num_list[num_list.length-2]) {
answer[num_list.length] = num_list[num_list.length-1] - num_list[num_list.length-2];
} else {
answer[num_list.length] = num_list[num_list.length-1]*2;
}
return answer;
}
}
아래는 다른 사람의 풀이입니다.
같은 풀이지만 더 읽기 편하다고 느꼈습니다.
class Solution {
public int[] solution(int[] num_list) {
int[] answer = new int[num_list.length+1];
for(int i = 0; i < num_list.length; i++) {
answer[i] = num_list[i];
}
int last = num_list[num_list.length-1];
int before = num_list[num_list.length-2];
answer[answer.length-1] = last > before ? last - before : last*2;
return answer;
}
}
쉬운 문제는 빨리 풀고 넘어가려고 하는데 읽기 편한 코드를 작성하는 습관을 만들 필요성을 느끼는 문제였습니다.
'알고리즘 > 코딩테스트 준비' 카테고리의 다른 글
구명보트 - Greedy에서 Priority Queue 활용법 (0) | 2023.09.20 |
---|---|
공백으로 구분하기2 - 문자열 문제는 쓰는 기능만 쓰게 되는 것 같다. trim과 정규화의 활용성 (0) | 2023.08.17 |
카펫 - 수학적으로 풀었다고 생각한 풀이와 진짜 수학적 풀이 (0) | 2023.08.16 |
짝지어 제거하기 (0) | 2023.08.14 |
다음 큰 숫자 - Integer.bitCount()의 발견 (0) | 2023.08.14 |