'React.js, 스프링 부트, AWS로 배우는 웹 개발 101'을 읽고 정리한 글입니다.
1.1 Todo 웹 애플리케이션
1.1.1 Todo 웹 애플리케이션 기능
- Todo 생성: + 버튼을 클릭해 Todo 아이템 생성
- Todo 리스트: 생성된 아이템 목록을 화면에서 확인
- Todo 수정: Todo 아이템을 체크하거나 내용 수정
- Todo 삭제: Todo 아이템 삭제
- 회원가입: 사용자는 회원 가입하고 계정을 통해 Todo 애플리케이션에 접근 가능
- 로그인: 계정으로 로그인
- 로그아웃: 로그인한 사용자는 로그아웃 가능
1.1.2 Todo 웹 애플리케이션 아키텍처
- 프론트엔드와 백엔드 서버가 분리된 이 아키텍처에서 브라우저는 백엔드의 REST API를 이용해서 HTTP 요청을 보낸다.
- 로컬 환경에서 실행하거나 EC2로 배포를 하는 데서 그치지 않는다.
- 실제 프로덕션에서 운영하는 데 필요한 기술과 스케일링에 몇 가지 기술을 구현한다.
- 예를 들어 로드 밸런서, 오토 스케일링 그룹, 도메인 등록 및 HTTPS 설정 등이 이에 해당한다.
1.1.3 기술과 구현 사이
우리가 이용할 기술이 어디에 속하는지 짚고 넘어가자.
HTML/CSS/React.js
우리의 프론트엔드 애플리케이션은 프론트엔드 클라이언트를 반환하는 서버가 있다.
이 서버의 역할은 React.js을 반환하는 것이다.
이런 방식으로 프론트엔드와 백엔드를 분리할 수 있다. 왜 분리할까?
스프링 부트
스프링 부트로 구현한 REST API를 프론트엔드 애플리케이션이 사용한다.
프론트엔드 애플리케이션은 웹일 필요는 없다.
예를 들어 모바일 앱 역시 별도의 백엔드 개발 없이 백엔드 애플리케이션의 REST API를 사용할 수 있다.
REST API를 구현하고 프론트엔드를 분리하는 것은 이후 마이크로서비스 아키텍처로 서비스를 확장하는 데 용이하다.
AWS
프론트엔드와 백엔드 애플리케이션이 실행될 프로덕션 환경을 구축하는 데 사용한다.
1.1.4 정리
우리가 만들 애플리케이션의 아키텍처와 요구사항을 설명했고 최종적으로 아키텍처와 각 주요 기술의 용도를 살펴봤다. 본격적으로 프로젝트를 시작하기에 앞서 알고 있어야 하는 배경 지식에 대해 살펴보도록 하자.
1.2 배경 지식
HTTP (하이퍼텍스트 트랜스퍼 프로토콜)
HTTP는 애플리케이션 레벨의 네트워크 프로토콜이다.
많은 웹 기반 애플리케이션이 HTTP를 이용하여 서버와 클라이언트 간에 통신한다.
그런데 이 HTTP라는 것이 정확히 무엇일까?
HTTP는 HyperText Transfer Protocol의 약자다. Transfer Protocol이란 통신 규약이라는 뜻이다.
그렇다면 HyperText란 무엇인가?
초기에 HyperText는 다른 문서로 향하는 링크가 있는 텍스트로 시작했다.
문서 내에서 HyperText를 지정하려면 특별히 HTML (HyperText Markup Language)를 사용해야 한다.
현재, HTTP는 HTML 문서를 주고받는 간단한 프로토콜에서 벗어나 그림 파일, 동영상, 3D 등 다양한 미디어 리소스를 주고받는 형태로 발전했다. 마찬가지로 HTML도 단순한 HyperLink를 위한 마크업 언어에서 다양한 시각적 기능을 제공하는 마크업 언어로 발전했다
사용자는 브라우저라는 클라이언트를 통해 서버에 HTTP 요청을 전송할 수 있다.
- 브라우저의 주소창에 URL을 입력하고 엔터를 누르면 브라우저는 HTTP GET 요청을 해당 URL 서버로 전송한다.
- 요청을 처리한 결과인 HTTP 응답을 브라우저에 렌더링(화면에 디스플레이)하는 것이다.
HTTP 요청
HTTP 요청에는 송신자의 다양한 정보가 담겨 있다.
- 예를 들어 위 예의 송신자는 localhost:8080으로 GET 요청을 전송하려 한다.
- 프로토콜은 HTTP 1.1 버전이다.
- 운영체제는 Mac OS X이다.
- 요청 전송 시 사파리 브라우저를 사용했다.
여기서 주목해야 할 부분은 요청 메서드다. HTTP 요청에는 GET, POST, PUT, DELETE와 같은 메서드를 지정할 수 있다.
비록 HTTP 메서드가 이런 기능을 한다고 하지만 이는 전적으로 API를 개발하는 개발자에게 달려 있다.
예를 들어 POST 메서드이지만 개발자는 리소스에 어떤 작업도 하지 않고 그냥 반환하도록 API를 작성할 수 있다.
DELETE 메서드에 아무것도 삭제하지 않는 API를 구현해 사용할 수도 있다.
요점은 HTTP 메서드 '기능'의 의미는 '이런 기능을 위한 API에 사용하는 것이 좋다.'는 뜻이지 GET 메서드로 지정했으니 리소스가 반환되는 것이 아니라는 뜻이다. 각 메서드에 연결되는 API는 개발자가 작성해야 하는 것이다.
HTTP 응답
HTTP 요청과 비슷하게 HTTP 응답도 여러 가지 정보를 담고 있다.
HTTP/1.1 옆에 200이라는 숫자는 응답 코드다.
- 200: 성공적으로 요청을 처리 완료
- 404: 해당 리소스가 존재하지 않음
- 403: 송신자에게 해당 리소스에 접근할 권한이 없음
- 500: 서버의 에러로 요청을 처리할 수 없음
Content-Type은 응답의 미디어 타입을 의미한다. 미디어 타입에는
- text/html
- text/css
- application/json
- video/mpeg
등이 있다.
Keep-Alive, Cache-Control, Connection 등 통신에 관련된 정보를 확인할 수 있다.
끝으로 서버 애플리케이션은 보통 HTTP Response Body에 요청 처리 결과를 보낸다.
예를 들어 www.google.com 에 GET 요청을 보내면 Google은 Response Body에 Google의 랜딩 페이지 HTML을 넣어 반환한다.
JSON (JavaScript Object Notation)
JSON은 'Object'를 표현하는 문자열이다. 왜 Object를 문자열로 표현해야 하는가?
여기서 Object가 무엇인지 잠깐 짚고 넘어간다.
public class TodoItem {
String title;
boolean done;
public TodoItem(String title, boolean done) {
this.title = title;
this.done = done;
}
}
자바 클래스의 예로 이 클래스를 이용해 Object를 생성하면 아래와 같다.
new TodoItem("myTitle", false);
이렇게 생성한 Object는 메모리상에 아래와 비슷한 형태로 존재할 것이다.
실제로 Object가 메모리상에 어떻게 존재하는지 아키텍처와 언어에 따라 다르기 때문에 사람이 읽기 힘들다.
위 그림처럼 프론트엔드는 백엔드에 인터넷을 통해 TodoItem을 전송하려 한다.
프론트엔드와 백엔드는 서로 언어도 다르고 아키텍처도 다르다고 가정하자.
이 Object를 전송하려면 프론트엔드와 백엔드 둘 다 이해할 수 있는 형태로 Object를 변환해야 한다.
이렇게 저장 또는 전송을 위해 메모리상의 Object를 다른 형태로 변환하는 작업을 직렬화라고 하고, 그 반대 작업을 역직렬화라고 한다.
그럼 이제 어떤 형태로 Object를 직렬화할 것인가에 대한 질문만 남는다. 이에 대한 해답이 JSON이다.
JSON은 키-값 (key-value)의 형태로 Object를 표현한 문자열이다.
{
"title":"myTitle",
"done":false
}
TodoItem Ojbect를 JSON으로 변환한 예다.
자바의 인스턴스 변수의 이름은 키(key)가 되고, 변수에 들어간 값은 값(value)이 되는 것을 확인할 수 있다.
JSON의 자료형들은 아래와 같다.
JSON (JavaScript Object Notation)이라고 부르는 이유는 자바스크립트에서 Object를 생성하는 형식과 같기 때문이다.
var object = {
"title": "myTitle",
"done": false
};
이제 다시 프론트엔드와 백엔드의 상황으로 돌아가서 JSON을 이용해 통신하기로 약속했다고 가정하자.
- 프론트엔드에서 Object를 JSON 형태의 문자열로 변환한 후
HTTP 요청의 Body 부분에 변환한 JSON을 넣어 요청을 전송한다. - 요청을 받은 백엔드는 HTTP 요청의 Body 부분에서 JSON을 꺼내 TodoItem을 변환해 사용할 수 있다.
이런 변환 과정은 라이브러리와 프레임워크가 대신 해주므로 크게 신경 쓸 일이 없다.
하지만 JSON을 이용해 자료를 교환한다는 사실을 알고 넘어가는 것이 좋다.
서버
정적 웹 서버, 동적 웹 서버
정적 웹 서버
정적 웹 서버란 HTTP 서버 중에서도 리소스 파일을 리턴하는 서버를 의미한다.
예를 들어 서버 호스트는 8080에서 실행하고 있는 로컬 호스트라고 가정하자.
- localhost8080/file.html HTTP 요청을 받아 서버로 보내면
- 정적 웹 서버인 이 서버는 지정된 경로에서 file.html을 찾아서
- HTTP Response body에 넣어 전송한다.
- 이때 서버는 해당 html 파일에 아무 작업도 하지 않고 파일을 있는 그대로 리턴한다.
- 이런 정적 웹 서버의 예로 아파치나 Nginx 등이 있다.
- 아파치나 Nginx를 설치하고 지정된 경로에 리소스 파일을 저장하면 자동으로 해당 리소스는 웹 서버를 통해 접근할 수 있게 된다.
- 그래서 서버를 설치 및 설정하고 원하는 리소스를 경로에 지정하는 것 말고는 개발자가 따로 할 일이 없다.
동적 웹 서버 Dynamic Web Server
동적 웹 서버는 요청을 처리한 후 처리한 결과에 따라 Response Body를 재구성하거나 HTML 템플릿 파일에 결과를 대체해 보낸다.
클라이언트는 요청에 요청 매개변수를 보낼 수 있다.
- name=Engineer라는 매개변수와 값을 보낸다.
- 이를 확인한 서버는 요청과 매개변수에 맞는 작업을 수행한 후
- 그 자리에서 html 파일을 구성하거나 템플릿 html 파일에서 적절한 값을 대체하여 html을 구성해 리턴한다.
동적 웹 서버는 클라이언트가 누군지, 어떤 매개변수를 보내는지에 따라 같은 요청이라도 다른 응답을 받을 수 있다.
각 요청과 매개변수에 따라 로직을 작성하는 것이 대부분의 백엔드 개발자가 해야 할 일이다.
그러나 비즈니스 요구사항에 따라 이 로직은 변한다. 개발자들은 아파치나 Nginx 같은 서버 프로그램을 사용하지 못한다.
그렇다면 백엔드 개발자들은 처음부터 끝까지 소켓 프로그래밍, HTTP 파싱, 스레드 풀 관리 등 모든 것을 새로 다 작성해야 할까?
다행히 자바 프로그램 중 동적 웹 서버 구현을 도와주는 서블릿 엔진이 라는 프로그램이 있다.
아파치 톰캣이 서블릿 엔진에 해당한다.
서블릿 컨테이너/엔진
개발자들은 서블릿 엔진을 설치한 후 서블릿 엔진에게 자기가 개발한 비즈니스 로직, 즉 클래스 파일과 해당 클래스 파일을 어느 요청에서 실행해야 하는지 알려줘야 한다.
이때 우리는 서블릿 엔진이 이해할 수 있는 형태로 클래스 파일을 작성해야 한다.
- 서블릿 엔진이 이해할수 있는 클래스란 javax.servlet.http.HttpServlet의 상속받는 서브 클래스를 의미한다.
- 우리는 HttpServlet을 상속받는 클래스를 작성해 특정 형식에 맞춰 압축해 전달해 준다.
- 이렇게 서블릿 엔진을 이용해 개발자는 서버를 처음부터 구현하지 않고도 각기 다른비즈니스 로직을 구현하고 배포할 수 있다.