Intro


뭉쳐야 산다.

요즘 팀 프로젝트를 진행하면서 나는 시간 관리를 정말 못하는 사람임을 느끼고 있다. 프로젝트 기획 및 설계 단계가 절반을 지나오면서 추릴 내용은 추리고, 확장할 내용은 확장시키면서 팀원 각자에게 할당된 업무들이 많아졌다.

여기서 왜 시간 관리가 안된다고 느꼈냐면, 매일 계속되는 할당량 미달 문제 때문이다. 스스로 하루 단위의 계획을 세우고 그럴듯하게 실천에 옮기지만, 대부분 과했거나 핑계를 대면서 여유를 부리니 항상 자정이 가까운 시간, 아니 자정이 넘어서까지도 그 날 못했던 할당량을 채우기 위해 공부하게 되었다.

하루 계획을 세울 때 욕심을 부린 것도 아니고, 조금만 신경쓴다면 충분히 소화할 수 있는 수준의 계획인데도 주 중에 2~3번 채울까 말까이다. 물론 개인적인 일정이나 프로젝트 진행간 생긴 변수에 대해 대응을 할 때면 당연하게도 계획했던 것들을 미룰 수 밖에 없지만, 온전히 혼자 세운 계획을 온전히 이루지 못했다는 것에 죄책감을 느끼곤 한다.

이러한 계획 실패로 인한 죄책감에서 조금이라도 벗어나기 위해서, 하루 단위 게획을 조금 다르게 세워보기로 결정했다. 바로 혼자가 아닌 팀원들과 같이 계획을 짜고 공유하여 피드백을 주는 것이다. 물론 팀원들이 모두 동의했기에 가능한 일이었다. 팀 단위로 일일 계획을 수행한지 얼마 되지 않았지만 팀원들과 함께 하기 때문인지 보다 동기부여가 되고 있음을 느낀다.

이렇게 해도 똑같이 계획 달성에 실패할 수 있겠지만, 실패 이유에 대해서 동료들과 리뷰를 진행하고 피드백을 주고받으니 계획 실패에 대한 죄책감이 더욱 줄어들게 되었다. 정답이 아닐수도 있지만 팀 단위로 행동하는 것은 생각보다 좋은 경험을 안겨주는 것 같다고 느낀다.




Week 19

카카오 클라우드 스쿨 19주차 86~90일까지의 공부하고 고민했던 흔적들을 기록하였습니다.

WebRTC를 도입할 때 Web Socket이 필요하다고?!

프로젝트에 실시간 화상 채팅 서비스를 도입하기 위해서 필요한 기술들이 찾다보니 먼저는 WebRTC를 알게되었고, 둘 째로 Web Socket의 필요성에 대해 느끼게 되었다. 그런데, WebRTC를 동립하기 위한 실시간 통신 기술이 선행되어야 할 것 같아서 먼저 웹 소켓이 무엇인지에 대해서 공부해보려 한다.

Web Socket이란?

먼저 Web Socket(웹 소켓)이 무엇인지 공식문서를 찾아보았다.

여기서 말하는 웹 소켓은 사용자의 브라우저와 서버 사이의 인터액티브 통신 세션을 설정할 수 있게 하는 고급 기술이라고 한다. 필자는 이를 서버와 클라언트 간의 메시지 교환을 위한 프로토콜(통신 규약)이라고 이해하였다.

또한 개발자가 웹 소켓 API를 이용한다면, 서버로 메시지를 보내고 서버의 응답을 위해 주기적으로 서버에 요청을 보내서 새로운 데이터나 상태 변경 여부를 확인하지 않고도 이벤트 중심의 응답을 받는 것이 가능하다고 한다.

이전까지는 클라이언트에서 먼저 요청을 보내면, 서버에서 응답을 보내주는 방식이었지만, 웹 소켓을 통한다면 클라이언트와 서버간의 연결을 유지한 채로 서버에서 먼저 데이터를 보낼 수 있게 된다.

Web Socket의 동작 원리

위 그림을 살펴보면 먼저 요청을 보내고 요청에 대한 응답(HTTP upgrade)을 받는다. 그러면 클라이언트와 서버의 커넥션이 생기고, 이를 유지한 채로 데이터를 교환할 수 있게 되는 것이다.

여기서 upgrade란 클라이언트가 HTTP 연결을 시작하고자 하는 것을 알리고, 서버가 이를 받아들이고 웹소켓 프로토콜로 변경하는 과정을 뜻하는 규칙이라고 볼 수 있다. upgrade 이후에는 클라이언트와 서버 간에 웹소켓 연결이 확립되고, 양쪽 모두가 웹소켓 프로토콜을 사용하여 메시지를 주고받을 수 있게 된다.

이 동작 과정을 구체적으로 살펴보면 다음과 같이 크게 세가지로 분류할 수 있다. 빨간 색 박스, 노란색 박스, 보라색 박스 순으로 Opening Handshake와, Data transfer, Closing Handshake가 이루어진다.

Opening HandshakeClosing Handshake 은 HTTP TCP 통신의 과정 중 하나이다. 이 때, HTTP로 접속 요청을 하여 Hand Shake가 완료되면 해당 프로토콜은 웹 소켓 프로토콜(WS)로 변경된다.

HandShake는 직역하면 '악수'라는 뜻으로, 마치 우리가 다른 사람과의 첫 만남에서 만나서 악수를 요청하고 악수를 받듯이, 정상적인 통신을 하기 전에, 일련의 과정을 거쳐 연결을 성립시키는 것을 말한다. 이 때, 연결을 수립할 때는 3way-HandShake, 연결을 종료할 때는 4way-HandShake를 사용한다.


Web Socket의 특징

웹소켓의 특징으로는 양방향 통신, 실시간 네트워킹이 가능하다는 점이다.

양방향 통신
데이터 송수신을 동시에 처리할 수 있는 통신 방법으로 일반적인 HTTP 통신과는 다르게 클라이언트와 서버가 서로에게 원할 때 데이터를 주고 받을 수 있다.

실시간 네트워킹
웹 환경에서 연속된 데이터를 빠르게 가져와서 보여줄 수 있으며, 여러 단말기나 기기의 데이터를 신속하게 교환할 수 있다. (채팅, 주식, 비디어 데이터 등 ..)

WebRTC를 사용하기 위해서 Web Socket이 필요한 이유는 뭘까?

WebRTC는 브라우저끼리 실시간으로 음성이나 비디오, 데이터 등을 교환하기 위한 실시간 통신 기술이다. 이를 위해 통신 과정에서 P2P(Peer-to-Peer) 연결을 사용하는데, 웹 브라우저 간에 직접적인 P2P 통신을 통해 데이터를 전송하게 된다.

그런데 Web Socket의 경우 클라이언트와 서버가 유기적으로 연결을 유지하는 기술이기에 WebRTC와는 전적으로 사용 방식이 다르다고 볼 수 있다.

하지만, WebRTC를 통해 P2P 연결을 설정하고 유지하기 위해서는 별도의 시그널링 프로세스가 필요하다. 여기서 시그널링은 P2P 연결을 설정하는 과정에서 필요한 정보를 교환하는 과정이라고 보면 된다.

이 시그널링 과정에서는 통신해야할 미디어 속성이나 NAT 및 방화벽 정보, ICE 후보자 정보 등의 데이터를 교환하게 된다.

WebRTC는 이러한 이러한 시그널링 기능을 제공하지 않기 때문에 WebSocket을 사용하여 별도의 시그널링 서버를 구축하는 것이 일반적이라고 한다.

결국 WebRTC 기술을 프로젝트에 도입하여 P2P 연결을 설정하고 유지하기 위해서는, Web Socket을 통한 시그널링 서버를 구축해야 하는 것이다.



Spring Boot 애플리케이션에서 Web Socket 구현하기

앞에서 알아본 Web Socket을 활용한 채팅 기능을 스프링 부트에서 구현하는 실습을 진행하였다.

기본적으로 서버와 클라이언트가 구축되어야 하는데, 아를 위해 스프링 부트의 템픓릿 엔진인 Thymeleaf을 함께 사용하였다.

먼저 스프링 부트 프로젝트를 만들어서 Web Socket 설정과 관련된 코드를 작성해보자.

Spring Boot 프로젝트 생성하기

프로제트를 생성할 때 추가할 의존성은 다음과 같다.

  • DevTools, Lombok, Spring Web, Thymeleaf, WebSocket

Service 클래스 작성하기

클라이언트가 접속할 때마다 생성되어 클라이언트와 직접 통신하는 클래스를 작성하였다. 새로운 클라이언트가 접속할 때마다 클라이언트의 세션 관련 정보를 정적형으로 저장하여 통신할 수 있다.

service.WebSocketChat.java

import jakarta.websocket.OnClose;
import jakarta.websocket.OnMessage;
import jakarta.websocket.OnOpen;
import jakarta.websocket.Session;
import jakarta.websocket.server.ServerEndpoint;
import org.springframework.stereotype.Service;

import java.util.Collections;
import java.util.HashSet;
import java.util.Set;

@Service
@ServerEndpoint(value="/chat")
public class WebSocketChat {

    private static Set<Session> clients = Collections.synchronizedSet(new HashSet<Session>());

    @OnOpen
    public void onOpen(Session s) {
        System.out.println("open session: " + s.toString());
        if (!clients.contains(s)) {
            clients.add(s);
            System.out.println("session open: " + s);
        } else {
            System.out.println("이미 연결된 session 입니다.");
        }
    }
    @OnMessage
    public void onMessage(String msg, Session session) throws Exception{
        System.out.println("receive message: " + msg);
        for(Session s : clients) {
            System.out.println("send data: " + msg);
            s.getBasicRemote().sendText(msg);
        }
    }
    @OnClose
    public void onClose(Session s) {
        System.out.println("session close: " + s);
        clients.remove(s);
    }
}

WebSocket의 연결점을 알려주는 @ServerEndoint 어노테이션을 지정한 클래스를 생성하게 된다. 여기서 @ServerEndpoint 어노테이션은 WebSocket을 활성화시키는 매핑 정보를 지정한다.

@ServerEndpoint 어노테이션은 WebSocket 서버 엔드포인트를 정의하는 어노테이션이다. @ServerEndpoint 어노테이션을 사용하여 WebSocket 서버를 정의하면, 클라이언트와 서버 간의 WebSocket 연결이 맺어지고, 클라이언트와 서버 간의 메시지 교환을 할 수 있다. 그래서 서버 측에서 클라이언트와 실시간으로 데이터를 주고받을 수 있게 된다.

client라는 Set에 접속한 클라이언트 세션 정보를 저장할 때, Collections.synchronizedSet 메소드를 통해 동기화된 Set으로 만든다. 이유는 새로운 인스턴스를 만들지 않고 싱글톤 객체로 동작하게끔 만들기 위해서이다. 추가로 static 키워드를 붙여서 더더욱 싱글톤으로 동작하게끔 하였다.

private static Set<Session> clients = Collections.synchronizedSet(new HashSet<Session>());

하지만, 실제로 사용자 세션 접속 정보를 Redis와 같은 인 메모리 DB에 담을 때는 Map을 사용하는 것이 일반적이라는 것을 유의하자.

그리고 클라이언트의 접속, 메시지 수신, 접속 종료에 따른 이벤트 핸들러 메서드를 작성하는데 사용한 어노테이션에 대해서 살펴보자.

  • @OnOpen: 클라이어트가 접속할 때 발생하는 이벤트이다.
  • @OnClose: 클라이언트가 브라우저를 끄거나 다른 경로로 이동하여 접속이 종료될 때 발생하는 이벤트이다.
  • @OnMessage: 메시지가 수신되었을 때 발생하는 이벤트이다.

Config 클래스 작성하기

이 떄, 클라이언트가 접속할 떄마다 인스턴스가 생성되는데 인스턴스가 생성되지 않고 싱글톤으로 동작하도록 별도의 설정 클래스를 추가해야 한다.

config.WebSocketConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;
import org.springframework.web.socket.server.standard.ServerEndpointExporter;

@Component
public class WebSocketConfig {
    @Bean
    public ServerEndpointExporter serverEndpointExporter() {
        return new ServerEndpointExporter();
    }
}

Controller 클래스 작성하기

View의 경우 Template Engine인 Thymeleaf를 사용하기 때문에 ModelAndView를 통해 데이터를 보내도록 작성하였다.

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class ChatController {
    @RequestMapping("/chat")
    public ModelAndView chat() {
        ModelAndView mv = new ModelAndView();
        mv.setViewName("chatting");
        return mv;
    }
}

여기까지 작성했다면 서버단의 코드 작성이 끝이다.


화면 출력을 위한 HTML 작성하기

chatting.html 파일을 생성하고 채팅을 하기 위한 내용을 추가하였다.

templates/chatting.html

<!DOCTYPE html>
<html>
<head>
    <meta charset="UTF-8">
    <title>Insert title here</title>
    <link rel='stylesheet' type='text/css' href='/css/chat.css'>
</head>
<body>
<div id='chat'>
    <h1>WebSocket Chatting</h1>
    <input type='text' id='mid' value='noname'>
    <input type='button' value='로그인' id='btnLogin'>
    <br/>
    <div id='talk'></div>
    <div id='sendZone'>
        <textarea id='msg' value='hi...' ></textarea>
        <input type='button' value='전송' id='btnSend'>
    </div>
</div>
<script src='/js/chat.js'></script>
</body>
</html>

JavaScript 작성하기

마지막으로 웹 소켓 통신을 위한 자바스크립트 파일을 생성하고 작성하였다.

function getId(id){
    return document.getElementById(id);
}
// 전송 데이터(JSON)
let data = {};
let ws;
let mid = getId('mid');
let btnLogin = getId('btnLogin');
let btnSend = getId('btnSend');
let talk = getId('talk');
let msg = getId('msg');

btnLogin.onclick = function(){
    ws = new WebSocket("ws://" + "[나의 IP 주소]:9999" + "/chat");
    console.log(ws)
    ws.onmessage = function(msg){
        let data = JSON.parse(msg.data);
        let css;
        if(data.mid == mid.value){
            css = 'class=me';
        }else{
            css = 'class=other';
        }
        let item = `<div ${css} >
                        <span><b>${data.mid}</b></span> [ ${data.date} ]<br/>
                        <span>${data.msg}</span>
                    </div>`;
        talk.innerHTML += item;
        talk.scrollTop=talk.scrollHeight;
    }
}

msg.onkeyup = function(ev){
    if(ev.keyCode == 13){
        send();
    }
}

btnSend.onclick = function(){
    send();
}

function send(){
    if(msg.value.trim() != ''){
        data.mid = getId('mid').value;
        data.msg = msg.value;
        data.date = new Date().toLocaleString();
        let temp = JSON.stringify(data);
        ws.send(temp);
    }
    msg.value ='';
}

위 코드에서 onmessage 이벤트를 보면 메시지를 전송한 사용자가 자신인지 아닌지에 따라 다르게 클래스를 적용하는 조건을 적용하였다. 이를 통해 채팅방에서 자신과 다른 사용자를 구분한다.

또한,onkeyup 이벤트를 보면 이벤트가 발생한 키의 키 코드가 13 일 때 이벤트가 발생하게 되는데 13Enter 키의 키 코드를 나타낸다. 결국 채팅을 입력하고 엔터를 치는 순간 메시지를 전송하겠다는 의미send() 함수를 호출하게 된다.

동작 여부 확인하기

JS까지 작성한 후 크롬 브라우저 2개를 띄운뒤 각각 test1과 test2라는 사용자로 채팅을 해보니 의도한 대로 잘 동작하는 것을 확인할 수 있었다.

해당 실습에 이용한 CSS 파일을 이번 글에서는 생략했는데, 실습에 이용된 모든 소스 코드는 Github Repository에 있으니 참고하자.



좋은 API를 설계하는 것과 Restful한 API를 설계하는 것은 다를 걸까?

애플리케이션을 개발하기 위해 요구사항을 정의하고 필요한 기능들을 산출하면서 API 명세서를 작성하게 되었다. 어떻게 하면 더 좋은 API를 만들 수 있을까? 고민하게 되었다.

HTTP 통신을 이용하는 웹 서비스를 개발하게 될 텐데, 그렇다면 Restful한 API를 설계하는 것이 좋은 API를 설계하게 되는 것일까 의문이 생겼다. 그래서 좋은 API를 설계하는 것과 Restful한 API를 설계하는 것이 다른 것인지에 대해서 공부하고 기록하려한다.

좋은 API란 무엇일까?

필자는 좋은 API를 설계하는 것은 사용자가 쉽게 이해하고 사용할 수 있는 API를 만드는 것이라고 생각한다. 이를 위해서는 API의 목적과 기능을 명확하게 정의하고, 사용자가 API를 사용할 때 발생할 수 있는 문제를 최소화하는 등 고려할 사항은 굉장히 많다.

이와 관련해서 AWS의 Channy Yun님이 작성하신 좋은 API 디자인을 위한 6가지 배운 점 이라는 내용을 보고 대략적으로 어떻게 설계해야 좋은 API를 작성할 수 있는지 감을 잡을 수 있었다.

또한 AWS가 15년간 API 설계에 대해 배운 점 6가지는 다음과 같다.

  1. API는 영원하다!
  2. 하위 호환성을 지켜라.
  3. 고객의 입장에서 생각하고 만들어라.
  4. 오류가 명시적인 API를 만들어라.
  5. 목적과 사용법을 바로 이해할 수 있는 API를 만들어라.
  6. 세부적인 구현 정보는 누출되지 않게 신경 써라.

각 항목별로 자세한 내용은 해당 링크를 참고하도록 하고, 이 6가지 항목을 통해서 해당 API가 사용자 친화적이어야 하고, 개발자 입장에서 유지보수하기 쉽도록 작성해야 하며, 굳이 사용자에게 노출될 만한 정보는 포함시키지 말아야 한다고 느꼈다.


Restful한 API를 설계한다?

앞서 좋은 API를 설계하는 것에 대해서 간단하게 살펴보았다. 이와 더불어 필자는 HTTP 통신을 이용한 웹 서비스를 개발하기 때문에 Rest한 API를 작성하는 것을 무시할 수 없다. 그렇다면 Rest API 가이드라인을 따르는 것이 결국 좋은 API를 설계하는 것과 같은 것인지에 대해서 알아보자.

RESTful한 API를 설계하는 것은 HTTP 프로토콜을 따르는 웹 서비스를 구현하는 것이다.

RESTful한 API는 Representational State Transfer(표현 상태 전이)의 약어, 자원(resource)의 표현(representation)에 의존하며, HTTP 메서드(GET, POST, PUT, DELETE 등)를 사용하여 자원을 조작할 수 있다. 이를 위해서는 URI를 통해 자원을 표현하고, 요청 메서드와 상태 코드를 이용하여 자원을 조작하는 방식으로 API를 설계해야 한다.

Restful API의 특징

RESTful한 API를 작성하고 사용한다면, 클라이언트와 서버 간의 인터페이스를 명확하게 정의할 수 있으며, 자원과 행위를 분리하여 서버의 부담을 줄일 수 있다. 또한, HTTP 프로토콜을 따르므로, 캐싱, 로드 밸런싱, 보안 등의 기능을 쉽게 구현할 수 있다.

이러한 RESTful한 API의 특징은 다음과 같다.

  1. 클라이언트/서버 구조: 서버와 클라이언트가 서로 독립적으로 개발될 수 있도록 하며 서로간의 의존성을 줄일 수 있다.
  2. 무상태성(Statelessness): 요청 사이에 어떠한 데이터도 저장하지 않고, 각각의 요청이 독립적으로 처리되어야 한다.
  3. 캐시 가능(Cacheability): HTTP 프로토콜을 기반으로 하기 때문에 캐시를 사용할 수 있다.
  4. 계층 구조(Layered system): 서버와 클라이언트 사이에 중간 계층을 두어 구조를 단순화할 수 있다.
  5. 인터페이스 일관성(Uniform interface): 자원을 식별하고, 표현을 위한 메시지 전달, 자원 조작을 위한 메서드를 일관된 형태로 사용한다.
  6. 자원의 식별: 각 자원은 고유한 URI를 사용하여 식별한다.
  7. 자기 서술적 메시지(Self-descriptive message): 메시지 스스로 메시지에 대한 설명이 가능해야 한다.
  8. HATEOAS(Hypermedia as the engine of application state): 서버로부터 받은 응답에 포함되어 있는 링크 정보를 바탕으로 클라이언트가 가능한 상태를 파악할 수 있어야 한다.

이러한 RESTful API의 특성들은 서비스의 확장성, 유연성, 이식성 등을 높여주며, API를 사용하는 클라이언트와 서버 간의 인터페이스를 명확하게 정의하고, 자원과 행위를 분리하여 서버의 부담을 줄일 수 있도록 해준다.

결국 좋은 API를 설계하는 과정에는 Restful한 API를 설계하는 내용이 포함된다.

따라서, 좋은 API를 설계하는 것과 RESTful한 API를 설계하는 것은 본질적으로 다르지만, RESTful한 API를 작성하고 사용하는 것이 좋은 API를 설계하는 과정이 나절차에 도움이 되는 요소 중의 하나가 될 수 있다고 느꼈다.



API 통신을 위한 RestTemplate을 파헤치기

파이널 프로젝트 구조를 MSA 구조로 확장하기 용이한 멀티 모듈, 분산 아키텍처의 구조로 전개하기로 기획하게 되었다. 각각의 서비스가 독립적으로 별도의 애플리케이션으로 실행되기 때문에 연관 관계가 필요한 정보의 경우 API 통신으로 가져와야한다.

이를 위해서 Spring 6.0 부터는 Rest Template, Web Client, HTTP Interface 이라는 3가지의 Rest Client 방식을 제공한다. 이번 글에서는 Rest Client 방식 중 하나인 RestTemplate에 대해서 알아보고, 실습해보자.

RestTemplate이란?

RestTemplate은 Spring에서 제공하는 내장 클래스로, HTTP 요청을 보내고 응답을 받는 데 사용되는 도구이다. RestTemplate을 이용하면 손쉽고 간편하게 Rest 방식의 API를 호출할 수 있다.

RestTemplate은 Spring 3.0부터 지원하는 HTTP 클라이언트이다. Restful API의 가이드라인을 지킬 수 있으며, GET, POST, PUT, DELETE 등의 HTTP Method를 제공하며, JSON, XML, String 등의 응답을 처리할 수 있다.

또한, 스프링 공식 문서에서 살펴본 바로는 RestTemplate은 동기식 클라이언트로써 Blocking I/O 기반으로 동작한다고 한다. 즉, REST API 호출이후 응답을 받을 때까지 기다려야 한다는 특징이 있다.

Spring Boot에서 RestTemplate을 이용해 API 통신하기

RestTemplate에 대해서 알아봤으니 다음으로 실습을 진행해보자.

이 때 실습 개발환경의 경우는 다음과 같다.

  • 사용자 관련 정보와 게시판 정보를 가지는 서로 다른 Spring Boot 프로젝트를 만든다.
  • 게시글을 작성하기 위한 작성자(사용자) 정보를 가져오기 위해 RestTemplate으로 API 통신을 한다.
  • 사용자 서비스는 8888 포트로, 게시판 서비스는 9999 포트를 사용한다.

그렇다면 게시글(Article) 프로젝트에서 사용자(Member) 프로젝트의 API 통신을 하기 위해 RestTemplate을 사용해보자.


사용자 서비스에서 사용자 이름 정보를 제공하는 API 구현하기

먼저 사용자 관리 서비스에서 제공할 API는 /member/{id} 과 같은 형태로 제공한다.

MemberController.java

@GetMapping("/member/{id}")
public ResponseEntity<GetMemberResponseDTO> memberInfo(@PathVariable Long id) {
	String nickname = memberService.findByName(id);
    GetMemberResponseDTO res = GetMemberResponseDTO.builder()
    	    .nickname(nickname)
        	.build();
	return ResponseEntity.ok(res);
}

궁긍적으로 1L의 고유번호를 가지는 사용자 이름인 새싹개발자 라는 데이터를 제공해야 한다.


게시판 서비스에서 RestTemplate을 적용하기 위한 의존성 설정하기

RestTemplate을 사용하려면 위와 같은 의존성을 추가해야 한다. 프로젝트 생성시 Spring Web을 선택하면 자동으로 추가된다.

build.gradle

implementation 'org.springframework.boot:spring-boot-starter-web'

RestTemplate 설정 클래스 작성하기

다음으로 사용자 정보가 필요할 때가 많을 것 같은데 그때마다 일일이 RestTemplate 객체를 생성하는 것은 비효율적이기 때문에 Config 설정 클래스를 통해 빈으로 등록하여 가져다 쓰기 위한 설정파일을 생성한다.

config.RestTemplateConfig.java

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestTemplate;

@Configuration
public class RestTemplateConfig {
    @Bean
    public RestTemplate restTemplate() {
        return new RestTemplate();
    }
}

RestTemplate 객체를 만들어 빈으로 등록해둔다.


외부 API로 통신하여 사용자 정보 가져오기

실제로 RestTemplate을 이용한 외부 API 통신은 컨트롤러 계층이 아니라 서비스 로직에서 수행해야 할 것으로 판단하여 ArticleService Interface의 구현체인 ArticleServiceImpl에서 해당 로직을 작성하였다.

ArticleServiceImpl.java

@Override
public Long write(SaveRequestDTO dto) {
    String url = "http://localhost:8888/member/" + dto.getWriter();
	ResponseEntity<Map> res = restTemplate.exchange(url, HttpMethod.GET, null, Map.class);
    Article article = Article.builder()
            .articleId(dto.getArticleId())
            .writer((String) res.getBody().get("nickname")) // 외부 API로 가져온 사용자 닉네임 정보 삽입
            .title(dto.getTitle())
            .content(dto.getContent())
            .build();
	// 이하 생략
}

먼저, url 변수를 통해 호출할 REST API의 URI를 지정한다. 여기서는 /member/{id} 라는 사용자 서비스의 API를 사용했다.

로컬에서 테스트 하기 때문에 http://localhost:8888 uri에 게시글 등록 요청시 가져온 사용자 번호를 URI에 추가한다.

다음으로 RestTemplate 객체를 사용하여 exchange() 메소드를 호출한다. 이 때, url 변수와 HttpMethod.GET을 전달하면서, 요청 body는 null로 전달한다.

여기서 사용한 API의 GET 요청은 요청 본문(Body)을 요구하지 않기에 exchange() 메소드에서 요청 본문(Body)을 전달하지 않아도 무방하다. GET 요청은 URI와 쿼리 파라미터만을 사용하여 요청을 처리하게 된다. 따라서, GET 요청에서 요청 본문을 전달하지 않아도 되며, exchange() 메소드에서 요청 바디를 null로 전달하면 된다.

마지막으로, 반환된 Map 객체에서 nickname 필드를 가져와서, Article 객체를 생성합니다. 파라미터로 받은 SaveRequestDTO 객체에서는 게시글 등록 요청시 받은 작성자의 ID만 포함되기 때문에, 작성자의 닉네임을 가져와서 Article 객체를 생성하기 위해 API를 호출하였다.

그렇게 1L이라는 고유번호 Long 타입을 가지는 사용자 정보로 API를 호출하여 Article을 저장할 때 작성자 정보를 가져온 데이터로 활용할 수 있었다.



RestTemplate가 Deprecated 된다고?!

RestTemplate과 관련된 내용들을 찾아보면서 RestTemplate은 Deprecated 되었으니 앞으로 사용을 지양해야 한다는 글을 자주 접하게 되었다. 당장 구글링해봐도 쉽게 찾아볼 수 있을 정도이다.

그래서 RestTemplate이 정말로 Deprecated 되었는지 스프링에서 찾아보았다.

그런데, Deprecated 되었다는 내용을 찾아볼 수 없었다.

정말로 스프링에서 Deprecated 되었다면 위와 같은 doExecute 메소드처럼 Deprecated 되었다는 주석 내용이라던가 @Deprecated 어노테이션이 추가되는 등 개발자 입장에서 명확하게 알 수 있어야 할 텐데, RestTemplate에 대한 내용에서는 Deprecated 되었다는 내용을 찾아볼 수 없었다.


Spring 공식 팀에서의 Deprecated 철회

그렇게 여러 자료를 찾아보던 중, 토비님의 RestTemplate은 스프링에서 제거되나요? 유튜브 영상에서도 같은 이슈가 소개된 것을 알게 되었다.

토비님이 직접 해당 이슈를 추적해본 결과 Spring 공식 팀에서 RestTemplate을 Deprecated 로 반영하였다가, 알 수 없는 이유로 철회한 것을 Version History를 통해 보여주셨다.

Spring 공식 팀에서 RestTemplate을 Deprecated 된 시점에, 여러 개발자가 당연히 Deprecated 될 것이라 판단하고 관련된 여러 포스트가 작성되고 공유되며 RestTemplate이 Deprecated 된다는 오해를 불러 일으키게 된 것이다.

Spring 공식 팀에서는 RestTemplate보다 WebClient를 밀어주었는데, 생각보다 개발자들의 반응은 미적지근했고, 이로 인해 RestTemplate의 Deprecated 여부가 철회되는데 영향이 있었을 수도 있다고 한다.


RestTemplate은 계속 사용해도 무방하다.

결론을 정리하자면, 모종의 이유로 RestTemplate의 Deprecated 여부는 철회되었고, 앞으로도 Rest Client 방식은 RestTemplate, WebClient, HTTP Interface 3가지가 제공될 것이다.

해당 이슈를 통해 느꼈던 점은 여러 블로그의 포스팅만을 보고 믿는 것은 굉장히 무섭다는 것이었다. 소스 코드의 근원지인 공식문서를 항상 살피고 이전 버전이나 현재 버전에서는 어떻게 알려주고 있는지를 먼저 찾아보는 것이 이런 오해를 덜 일으킬 수 있지 않을까?






Final..

파이널 프로젝트를 진행하는 기간에는 조금 여유를 가지고 공부할 수 있을 줄 알았는데 큰 착각이었다.

프로젝트를 기획하고 설계하는 것만도 많은 에너지를 소모하게 되는데, 더불어 프로젝트에 필요한 여러 CS 지식들을 공부하고, 코딩테스트 준비를 위한 문제도 풀다보니 너무나도 시간이 부족했다.

프로젝트에서 요구되는 기술 중에서는 배우지 못한 기술들이 있어서 기술마다의 러닝커브를 고려하다보니 온전히 완성할 수 있을까 걱정도 되었다. 그런데 걱정할 시간도 없다고 생각하고 이것저것 부지런하게 하다보니 오히려 할 수 있겠다 라는 생각이 더 커졌다.

이제 이번 주까지는 프로젝트 기획 및 설계 단계를 빠르게 마무리하고 애플리케이션 개발과 클라우단 환경 구축에 힘써보려 한다. 설계 과정에서 예상보다 일정이 조금 미루어져 정해진 일정에 맞추기 위해서 팀원들과 충분히 소통하여 효율적으로 시간을 사용해야 한다고 다짐했다.



혹여 잘못된 내용이 있다면 지적해주시면 정정하도록 하겠습니다.

참고자료 출처

profile
찍어 먹기보단 부어 먹기를 좋아하는 개발자

0개의 댓글