REST API


REST : 웹 (HTTP) 의 장점을 활용한 아키텍쳐

1. REST (REpresentational State Transfer) 기본

  • REST의 요소
    • MethodMethod의미Idempotent
      POST Create No
      GET Select Yes
      PUT Update Yes
      DELETE Delete Yes
      Idempotent : 한 번 수행하냐, 여러 번 수행했을 때 결과가 같나?
    • Resource
      • http://myweb/users와 같은 URI
      • 모든 것을 Resource (명사)로 표현하고, 세부 Resource에는 id를 붙임
    • Message
      • 메시지 포맷이 존재
        HTTP POST, http://myweb/users/
        {
        	"users" : {
        		"name" : "terry"
        	}
        }
        
      • : JSON, XML 과 같은 형태가 있음 (최근에는 JSON 을 씀)
  • REST 특징
    • Uniform Interface
      • HTTP 표준만 맞는다면, 어떤 기술도 가능한 Interface 스타일
      • 예) REST API 정의를 HTTP + JSON로 하였다면, C, Java, Python, IOS 플랫폼 등 특정 언어나 기술에 종속 받지 않고, 모든 플랫폼에 사용이 가능한 Loosely Coupling 구조
      • 포함
        • Self-Descriptive Messages
          • API 메시지만 보고, API를 이해할 수 있는 구조 (Resource, Method를 이용해 무슨 행위를 하는지 직관적으로 이해할 수 있음)
        • HATEOAS(Hypermedia As The Engine Of Application State)
          • Application의 상태(State)는 Hyperlink를 통해 전이되어야 함.
          • 서버는 현재 이용 가능한 다른 작업에 대한 하이퍼링크를 포함하여 응답해야 함.
        • Resource Identification In Requests
        • Resource Manipulation Through Representations
    • Statelessness
      • 즉, HTTP Session과 같은 컨텍스트 저장소에 상태 정보 저장 안함
      • **Request만 Message로 처리**하면 되고, 컨텍스트 정보를 신경쓰지 않아도 되므로, 구현이 단순해짐.
      • 따라서, REST API 실행중 실패가 발생한 경우, Transaction 복구를 위해 기존의 상태를 저장할 필요가 있다. (POST Method 제외)
    • Resource 지향 아키텍쳐 (ROA : Resource Oriented Architecture)
      • Resource 기반의 복수형 명사 형태의 정의를 권장.
    • Client-Server Architecture
    • Cache Ability
    • Layered System
    • Code On Demand(Optional)

'Web' 카테고리의 다른 글

OAuth  (0) 2022.05.06
Web Server와 WAS의 차이  (0) 2022.05.06
HTTP status code  (0) 2022.05.06
Cookie & Session  (0) 2022.05.06
브라우저 동작 방법  (0) 2022.05.06

HTTP status code

클라우드 환경에서 HTTP API를 통해 통신하는 것이 대부분임

이때, 응답 상태 코드를 통해 성공/실패 여부를 확인할 수 있으므로 API 문서를 작성할 때 꼭 알아야 할 것이 HTTP status code다

 

  • 10x : 정보 확인
  • 20x : 통신 성공
  • 30x : 리다이렉트
  • 40x : 클라이언트 오류
  • 50x : 서버 오류


200번대 : 통신 성공

상태코드 이름 의미
200 OK 요청 성공(GET)
201 Create 생성 성공(POST)
202 Accepted 요청 접수O, 리소스 처리X
204 No Contents 요청 성공O, 내용 없음


300번대 : 리다이렉트

상태코드 이름 의미
300 Multiple Choice 요청 URI에 여러 리소스가 존재
301 Move Permanently 요청 URI가 새 위치로 옮겨감
304 Not Modified 요청 URI의 내용이 변경X


400번대 : 클라이언트 오류

상태코드 이름 의미
400 Bad Request API에서 정의되지 않은 요청 들어옴
401 Unauthorized 인증 오류
403 Forbidden 권한 밖의 접근 시도
404 Not Found 요청 URI에 대한 리소스 존재X
405 Method Not Allowed API에서 정의되지 않은 메소드 호출
406 Not Acceptable 처리 불가
408 Request Timeout 요청 대기 시간 초과
409 Conflict 모순
429 Too Many Request 요청 횟수 상한 초과


500번대 : 서버 오류

상태코드 이름 의미
500 Internal Server Error 서버 내부 오류
502 Bad Gateway 게이트웨이 오류
503 Service Unavailable 서비스 이용 불가
504 Gateway Timeout 게이트웨이 시간 초과

'Web' 카테고리의 다른 글

OAuth  (0) 2022.05.06
Web Server와 WAS의 차이  (0) 2022.05.06
REST API  (0) 2022.05.06
Cookie & Session  (0) 2022.05.06
브라우저 동작 방법  (0) 2022.05.06

Cookie & Session

CookieSession

저장위치 Client Server
저장형식 Text Object
만료시점 쿠키 저장시 설정
(설정 없으면 브라우저 종료 시)
정확한 시점 모름
리소스 클라이언트의 리소스 서버의 리소스
용량제한 한 도메인 당 20개, 한 쿠키당 4KB 제한없음

저장 위치

  • 쿠키 : 클라이언트의 웹 브라우저가 지정하는 메모리 or 하드디스크
  • 세션 : 서버의 메모리에 저장

만료 시점

  • 쿠키 : 저장할 때 expires 속성을 정의해 무효화시키면 삭제될 날짜 정할 수 있음
  • 세션 : 클라이언트가 로그아웃하거나, 설정 시간동안 반응이 없으면 무효화 되기 때문에 정확한 시점 알 수 없음

리소스

  • 쿠키 : 클라이언트에 저장되고 클라이언트의 메모리를 사용하기 때문에 서버 자원 사용하지 않음
  • 세션 : 세션은 서버에 저장되고, 서버 메모리로 로딩 되기 때문에 세션이 생길 때마다 리소스를 차지함

용량 제한

  • 쿠키 : 클라이언트도 모르게 접속되는 사이트에 의하여 설정될 수 있기 때문에 쿠키로 인해 문제가 발생하는 걸 막고자 한 도메인당 20개, 하나의 쿠키 당 4KB로 제한해 둠
  • 세션 : 클라이언트가 접속하면 서버에 의해 생성되므로 개수나 용량 제한 없음

'Web' 카테고리의 다른 글

OAuth  (0) 2022.05.06
Web Server와 WAS의 차이  (0) 2022.05.06
REST API  (0) 2022.05.06
HTTP status code  (0) 2022.05.06
브라우저 동작 방법  (0) 2022.05.06

"브라우저가 어떻게 동작하는지 아세요?"

웹 서핑하다보면 우리는 여러 url을 통해 사이트를 돌아다닌다. 이 url이 입력되었을 때 어떤 과정을 거쳐서 출력되는걸까?

web의 기본적인 개념이지만 설명하기 무지 어렵다.. 렌더링..? 파싱..?

 

브라우저 주소 창에 http://naver.com을 입력했을 때 어떤 과정을 거쳐서 네이버 페이지가 화면에 보이는 지 알아보자

오픈 소스 브라우저(크롬, 파이어폭스, 사파리 등)로 접속했을 때로 정리



브라우저 주요 기능


사용자가 선택한 자원을 서버에 요청, 브라우저에 표시

자원은 html 문서, pdf, image 등 다양한 형태

자원의 주소는 URI에 의해 정해짐

 

브라우저는 html과 css 명세에 따라 html 파일을 해석해서 표시함

이 '명세'는 웹 표준화 기구인 W3C(World wide web Consortium)에서 정해짐

예전 브라우저들은 일부만 명세에 따라 구현하고 독자적 방법으로 확장했음

(결국 심각한 호환성 문제 발생... 그래서 요즘은 대부분 모두 표준 명세를 따름)

 

브라우저가 가진 인터페이스는 보통 비슷비슷한 요소들이 존재

시간이 지나면서, 사용자에게 필요한 서비스들로 서로 모방하며 갖춰지게 된 것

  • URI 입력하는 주소 표시 줄
  • 이전 버튼, 다음 버튼
  • 북마크(즐겨찾기)
  • 새로 고침 버튼
  • 홈 버튼



브라우저 기본 구조



사용자 인터페이스

주소 표시줄, 이전/다음 버튼, 북마크 등 사용자가 활용하는 서비스들 (요청한 페이지를 보여주는 창을 제외한 나머지 부분)

브라우저 엔진

사용자 인터페이스와 렌더링 엔진 사이의 동작 제어

렌더링 엔진

요청한 콘텐츠 표시 (html 요청이 들어오면? → html, css 파싱해서 화면에 표시)

통신

http 요청과 같은 네트워크 호출에 사용 (플랫폼의 독립적인 인터페이스로 구성되어있음)

UI 백엔드

플랫폼에서 명시하지 않은 일반적 인터페이스. 콤보 박스 창같은 기본적 장치를 그림

자바스크립트 해석기

자바스크립트 코드를 해석하고 실행

자료 저장소

쿠키 등 모든 종류의 자원을 하드 디스크에 저장하는 계층



렌더링이란?

웹 분야를 공부하다보면 렌더링이라는 말을 많이 본다. 동작 과정에 대해 좀 더 자세히 알아보자

 

렌더링 엔진은 요청 받은 내용을 브라우저 화면에 표시해준다.

기본적으로 html, xml 문서와 이미지를 표시할 수 있음

추가로 플러그인이나 브라우저 확장 기능으로 pdf 등 다른 유형도 표시가 가능함

(추가로 확장이 필요한 유형은 바로 뜨지 않고 팝업으로 확장 여부를 묻는 것을 볼 수 있을 것임)


렌더링 엔진 종류

크롬, 사파리 : 웹킷(Webkit) 엔진 사용

파이어폭스 : 게코(Gecko) 엔진 사용

 

웹킷(Webkit) : 최초 리눅스 플랫폼에 동작하기 위한 오픈소스 엔진 (애플이 맥과 윈도우에서 사파리 브라우저를 지원하기 위해 수정을 더했음)


렌더링 동작 과정

 

먼저 html 문서를 파싱한다.

그리고 콘텐츠 트리 내부에서 태그를 모두 DOM 노드로 변환한다.

그 다음 외부 css 파일과 함께 포함된 스타일 요소를 파싱한다.

이 스타일 정보와 html 표시 규칙은 렌더 트리라고 부르는 또 다른 트리를 생성한다.

이렇게 생성된 렌더 트리는 정해진 순서대로 화면에 표시되는데, 생성 과정이 끝났을 때 배치가 진행되면서 노드가 화면의 정확한 위치에 표시되는 것을 의미한다.

이후에 UI 백엔드에서 렌더 트리의 각 노드를 가로지으며 형상을 만드는 그리기 과정이 진행된다.

이러한 과정이 점진적으로 진행되며, 렌더링 엔진은 좀더 빠르게 사용자에게 제공하기 위해 모든 html을 파싱할 때까지 기다리지 않고 배치와 그리기 과정을 시작한다. (마치 비동기처럼..?)

전송을 받고 기다리는 동시에 받은 내용을 먼저 화면에 보여준다
(우리가 웹페이지에 접속할 때 한꺼번에 뜨지 않고 점점 화면에 나오는 것이 이 때문!!!)

 

DOM이란?

Document Object Model(문서 객체 모델)

웹페이지 소스를 까보면 <html>, <body>와 같은 태그들이 존재한다. 이를 Javascript가 활용할 수 있는 객체로 만들면 문서 객체가 된다.

모델은 말 그대로, 모듈화로 만들었다거나 객체를 인식한다라고 해석하면 된다.

즉, DOM은 웹 브라우저가 html 페이지를 인식하는 방식을 말한다. (트리구조)


웹킷 동작 구조

어태치먼트 : 웹킷이 렌더 트리를 생성하기 위해 DOM 노드와 스타일 정보를 연결하는 과정

이제 조금 트리 구조의 진행 방식이 이해되기 시작한다..ㅎㅎ



파싱과 DOM 트리 구축


파싱이라는 말도 많이 들어봤을 것이다.

파싱은 렌더링 엔진에서 매우 중요한 과정이다.


파싱(parsing)

문서 파싱은, 브라우저가 코드를 이해하고 사용할 수 있는 구조로 변환하는 것

 

문서를 가지고, 어휘 분석과 구문 분석 과정을 거쳐 파싱 트리를 구축한다.

조금 복잡한데, 어휘 분석기를 통해 언어의 구문 규칙에 따라 문서 구조를 분석한다. 이 과정에서 구문 규칙과 일치하는 지 비교하고, 일치하는 노드만 파싱 트리에 추가시킨다. (끝까지 규칙이 맞지 않는 부분은 문서가 유효하지 않고 구문 오류가 포함되어 있다는 것)

 

파서 트리가 나왔다고 해서 끝이 아니다.

컴파일의 과정일 뿐, 다시 기계코드 문서로 변환시키는 과정까지 완료되면 최종 결과물이 나오게 된다.

 

보통 이런 파서를 생성하는 것은 문법에 대한 규칙 부여 등 복잡하고 최적화하기 힘드므로, 자동으로 생성해주는 파서 생성기를 많이 활용한다.

웹킷은 플렉스(flex)나 바이슨(bison)을 이용하여 유용하게 파싱이 가능

 

우리가 head 태그를 실수로 빠뜨려도, 파서가 돌면서 오류를 수정해줌 ( head 엘리먼트 객체를 암묵적으로 만들어준다)

결국 이 파싱 과정을 거치면서 서버로부터 받은 문서를 브라우저가 이해하고 쉽게 사용할 수 있는 DOM 트리구조로 변환시켜주는 것이다!



요약


  • 주소창에 url을 입력하고 Enter를 누르면, 서버에 요청이 전송
  • 해당 페이지에 존재하는 여러 자원들(text, image 등등)이 보내짐
  • 이제 브라우저는 해당 자원이 담긴 html과 스타일이 담긴 css를 W3C 명세에 따라 해석할 것임
  • 이 역할을 하는 것이 '렌더링 엔진'
  • 렌더링 엔진은 우선 html 파싱 과정을 시작함. html 파서가 문서에 존재하는 어휘와 구문을 분석하면서 DOM 트리를 구축
  • 다음엔 css 파싱 과정 시작. css 파서가 모든 css 정보를 스타일 구조체로 생성
  • 이 2가지를 연결시켜 렌더 트리를 만듬. 렌더 트리를 통해 문서가 시각적 요소를 포함한 형태로 구성된 상태
  • 화면에 배치를 시작하고, UI 백엔드가 노드를 돌며 형상을 그림
  • 이때 빠른 브라우저 화면 표시를 위해 '배치와 그리는 과정'은 페이지 정보를 모두 받고 한꺼번에 진행되지 않음. 자원을 전송받으면, 기다리는 동시에 일부분 먼저 진행하고 화면에 표시



[참고 자료]

네이버 D2 : 링크

'Web' 카테고리의 다른 글

OAuth  (0) 2022.05.06
Web Server와 WAS의 차이  (0) 2022.05.06
REST API  (0) 2022.05.06
HTTP status code  (0) 2022.05.06
Cookie & Session  (0) 2022.05.06

Javascript와 Node.js로 Git을 통해 협업하기

 

협업 프로젝트를 하기 위해서는 Git을 잘 써야한다.

하나의 프로젝트를 같이 작업하면서 자신에게 주어진 파트에 대한 영역을 pull과 push 할 때 다른 팀원과 꼬이지 않도록 branch를 나누어 pull request 하는 등등..

협업 과정을 연습해보자



Prerequisites

RequiredDescription

Git We follow the GitHub Flow
Node.js 10.15.0 LTS
Yarn 1.12.3 or above

 

Git과 GitHub을 활용한 협업 개발

Git : 프로젝트를 진행할 때 소스 코드의 버전 관리를 효율적으로 처리할 수 있게 설계된 도구

GitHub : Git의 원격 저장소를 생성하고 관리할 수 있는 기능 제공함. 이슈와 pull request를 중심으로 요구사항을 관리

 

Git 저장소 생성

$ mkdir awesome-javascript
$ cd awesome-javascript
$ git init

 

GitHub 계정에 같은 이름의 저장소를 생성한 후, git remote 명령어를 통해 원격 저장소 추가

$ git remote add origin 'Github 주소'

 

GitHub에 이슈 등록하기


이슈는 왜 등록하는거죠?

코드 작성하기에 앞서, 요구사항이나 해결할 문제를 명확하게 정의하는 것이 중요

GitHub의 이슈 관리 기능을 활용하면 협업하는 동료와 쉽게 공유가 가능함

 

GitHub 저장소의 Issues 탭에서 New issue를 클릭해서 이슈를 작성할 수 있음

 

이슈와 pull request 요청에 작성하는 글의 형식을 템플릿으로 관리할 수 있음

(템플릿은 마크다운 형식)


숨긴 폴더인 .github 폴더에서 이슈 템플릿과 pull request 템플릿을 관리하는 방법

devops/github-templates 브랜치에 템플릿 파일을 생성하고 github에 푸시하자

$ git checkout -b devops/github-templates
$ mkdir .github
$ touch .github/ISSUE_TEMPLATE.md # Create issue template
$ touch .github/PULL_REQUEST_TEMPLATE.md # Create pull request template
$ git add .
$ git commit -m ':memo: Add GitHub Templates'
$ git push -u origin devops/github-templates



Node.js와 Yarn으로 개발 환경 설정하기


오늘날 javascript는 애플리케이션 개발에 많이 사용되고 있다.

이때 git을 활용한 협업 환경뿐만 아니라 코드 검증, 테스트, 빌드, 배포 등의 과정에서 만나는 문제를 해결할 수 있는 개발 환경도 설정해야 한다.

이때 많이 사용하는 것이 Node.js와 npm, yarn

 

Node.js와 npm : JavaScript가 거대한 오픈소스 생태계를 확보하는 데 결정적인 역할을 함

 

Node.js는 Google이 V8 엔진으로 만든 Javascript 런타임 환경으로 오늘날 상당히 많이 쓰이는 중!

npm은 Node.js를 설치할 때 포함되는데, 패키지를 프로젝트에 추가할 수 있도록 다양한 명령을 제공하는 패키지 관리 도구라고 보면 된다.

yarn은 페이스북이 개발한 패키지 매니저로, 규모가 커지는 프로젝트에서 npm을 사용하다가 보안, 빌드 성능 문제를 겪는 문제를 해결하기 위해 탄생함

 

Node.js 설치 후, yarn을 npm 명령어를 통해 전역으로 설치하자

$ npm install yarn -g

 

프로젝트 생성


yarn init 명령어 실행

프로젝트 기본 정보를 입력하면 새로운 프로젝트가 생성됨

 

pakage.json 파일이 생성된 것을 확인할 수 있다.

{
  "name": "awesome-javascript",
  "version": "1.0.0",
  "main": "index.js",
  "repository": "https://github.com/kim6394/awesome-javascript.git",
  "author": "gyuseok <gyuseok6394@gmail.com>",
  "license": "MIT"
}

이 파일은 프로젝트의 모든 정보를 담고 있다.

이 파일에서 가장 중요한 속성은 dependencies로, 프로젝트와 패키지 간의 의존성을 관리하는 속성이다.

yarn의 cli 명령어로 패키지를 설치하면 package.json 파일의 dependencies 속성이 자동으로 변경됨

node-fetch 모듈을 설치해보자

$ yarn add node-fetch

pakage.json안에 아래와 같은 내용이 추가된다.

"dependencies": {
    "node-fetch": "^2.6.0"
}

 

추가로 생성된 yarn.lock 파일은 뭔가요?

앱을 개발하는 도중 혹은 배포할 때 프로젝트에서 사용하는 패키지가 업데이트 되는 경우가 있다. 또한 협업하는 동료들마다 다른 버전의 패키지가 설치될 수도 있다.

yarn은 모든 시스템에서 패키지 버전을 일관되게 관리하기 위해 yarn.lock 파일을 프로젝트 최상위 폴더에 자동으로 생성함.

(사용자는 이 파일을 직접 수정하면 안됨. 오로지 cli 명령어를 사용해 관리해야한다!)

 

프로젝트 공유

현재 프로젝트는 Git의 원격 저장소에 반영해요 협업하는 동료와 공유가 가능하다.

프로젝트에 생성된 pakage.json과 yarn.lock 파일도 원격 저장소에서 관리해야 협업하는 동료들과 애플리케이션을 안정적으로 운영하는 것이 가능해짐

 

원격 저장소에 공유 시, 모듈이 설치되는 node-_modules 폴더는 제외시켜야 한다. 폴더의 용량도 크고, 어차피 yarn.lock 파일을 통해 동기화 되기 때문에 따로 git 저장소에서 관리할 필요가 없음

따라서, 해당 폴더를 .gitignore 파일에 추가해 git 관리 대상에서 제외시키자

$ echo "node_modules/" > .gitignore



이슈 해결 관련 브랜치 생성 & 프로젝트 push

이번엔 이슈 해결과 관련된 브랜치를 생성하고, 프로젝트를 github에 푸시해보자

$ git add .
$ git checkout -b issue/1
$ git commit -m 'Create project with Yarn'
$ git push -u origin issue/1

 

푸시가 완려되면, GitHub 저장소에 pull request가 생성된 것을 확인할 수 있다.

pull request는 작성한 코드를 master 브랜치에 병합하기 위해 협업하는 동료들에게 코드 리뷰를 요청하는 작업

Pull requests 탭에서 New pull request 버튼을 클릭해 pull request를 생성할 수 있다


pull request시 주의할 점

리뷰를 하는 사람에게 충분한 정보를 제공해야 함

새로운 기능을 추가했으면, 기능을 사용하기 위한 재현 시나리오와 테스트 시나리오를 추가하는 것이 좋음.

개발 환경이 변경되었다면 변경 내역도 반드시 포함하자

 

Jest로 테스트 환경 설정

실제로 프로젝트를 진행하면, 활용되는 Javascript 구현 코드가 만들어질 것이고 이를 검증하는 테스트 환경이 필요하게 된다.

Javascript 테스트 도구로는 jest를 많이 사용한다.

 

GitHub의 REST API v3을 활용해 특정 GitHub 사용자 정보를 가져오는 코드를 작성해보고, 테스트 환경 설정 방법에 대해 알아보자


테스트 코드 작성

구현 코드 작성 이전, 구현하려는 기능의 의도를 테스트 코드로 표현해보자

테스트 코드 저장 폴더 : __test__

구현 코드 저장 폴더 : lib

테스트 코드 : github.test.js

 

$ mkdir __tests__ lib
$ touch __tests__/github.test.js

 

github.test.js에 테스트 코드를 작성해보자

내 GitHub kim6394 계정의 사용자 정보를 가져왔는지 확인하는 코드다.

const GitHub = require('../lib/github')

describe('Integration with GitHub API', () => {
    let github

    beforeAll ( () => {
        github = new GitHub({
            accessToken: process.env.ACCESS_TOKEN,
            baseURL: 'https://api.github.com',
        })
    })

    test('Get a user', async () => {
        const res = await github.getUser('kim6394')
        expect(res).toEqual (
            expect.objectContaining({
                login: 'kim6394',
            })
        )
    })
})


Jest 설치

yarn에서 테스트 코드를 실행할 때는 yarn test

먼저 설치를 진행하자

$ yarn add jest --dev

--dev 속성은 뭔가요?

설치할 때 이처럼 작성하면, devDependencies 속성에 패키지를 추가시킨다. 이 옵션으로 설치된 패키지는, 앱이 실행되는 런타임 환경에는 영향을 미치지 않는다.

 

테스트 명령을 위한 script 속성을 pakage.json에 설정하자

  "scripts": {
    "test": "jest"
  },
  "dependencies": {
    "axios": "^0.19.0",
    "node-fetch": "^2.6.0"
  },
  "devDependencies": {
    "jest": "^24.8.0"
  }


구현 코드 작성

아직 구현 코드를 작성하지 않았기 때문에 테스트 실행이 되지 않을 것이다.

lib 폴더에 구현 코드를 작성해보자

lib/github.js

const fetch = require('node-fetch')

class GitHub {
    constructor({ accessToken, baseURL }) {
        this.accessToken = accessToken
        this.baseURL = baseURL
    }

    async getUser(username) {
        if(!this.accessToken) {
            throw new Error('accessToken is required.')
        }

        return fetch(`${this.baseURL}/users/${username}`, {
            method: 'GET',
            headers: {
                Authorization: `token ${this.accessToken}`,
                'Content-Type' : 'application/json',
            },
        }).then(res => res.json())
    }
}

module.exports = GitHub

 

이제 GitHub 홈페이지에서 access token을 생성해서 테스트해보자

토큰은 사용자마다 다르므로 자신이 생성한 토큰 값으로 입력한다

$ ACCESS_TOKEN=29ed3249e4aebc0d5cfc39e84a2081ad6b24a57c yarn test

아래와 같이 테스트가 정상적으로 작동되어 출력되는 것을 확인할 수 있을 것이다!

yarn run v1.10.1
$ jest
 PASS  __tests__/github.test.js
  Integration with GitHub API
    √ Get a user (947ms)

Test Suites: 1 passed, 1 total
Tests:       1 passed, 1 total
Snapshots:   0 total
Time:        3.758s
Ran all test suites.
Done in 5.30s.



Travis CI를 활용한 리뷰 환경 개선


동료와 협업하여 애플리케이션을 개발하는 과정은, pull request를 생성하고 공유한 코드를 리뷰, 함께 개선하는 과정이라고 말할 수 있다.

지금까지 진행한 과정을 확인한 리뷰어가 다음과 같이 답을 보내왔다.

 

README.md를 참고해 테스트 명령을 실행했지만 실패했습니다..

 

무슨 문제일까? 내 로컬 환경에서는 분명 테스트 케이스를 통해 테스트 성공을 확인할 수 있었다. 리뷰어가 보낸 문제는, 다른 환경에서 테스트 실패로 인한 문제다.

이처럼 테스트케이스에 정의된 테스트를 실행하는 일은 개발과정에서 반복되는 작업이다. 따라서 리뷰어가 테스트를 매번 실행하게 하는 건 매우 비효율적이다.

CI 도구가 자동으로 실행하도록 프로젝트 리뷰 방법을 개선시켜보자


Travis CI로 테스트 자동화

저장소의 Settings 탭에서 Branches를 클릭한 후, Branch protection rules에서 CI 연동기능을 사용해보자

(CI 도구 빌드 프로세스에 정의한 작업이 성공해야만 master 브랜치에 소스코드가 병합되도록 제약 조건을 주는 것)

 

대표적인 CI 도구는 Jenkins이지만, CI 서버 구축 운영에 비용이 든다.

 

Travis CI는 아래와 같은 작업을 위임한다

  • ESLint를 통한 코드 컨벤션 검증
  • Jest를 통한 테스트 자동화

 

Travis CI의 연동과 설정이 완료되면, pull request를 요청한 소스코드가 Travis CI를 거치도록 GitHub 저장소의 Branch protection rules 항목을 설정한다.

이를 설정해두면, 작성해둔 구현 코드와 테스트 코드로 pull request를 요청했을 때 Travis CI 서버에서 자동으로 테스트를 실행할 수 있게 된다.


GitHub-Travis CI 연동

https://travis-ci.org/에서 GitHub Login

https://travis-ci.org/account/repositories에서 연결할 repository 허용

프로젝트에 .travis.yml 설정 파일 추가

 

.travis.yml

---
language: node_js
node_js:
  - 10.15.0
cache:
  yarn: true
  directories:
  - node_modules

env:
  global:
    - PATH=$HOME/.yarn/bin:$PATH

services:
  - mongodb

before_install:
  - curl -o- -L https://yarnpkg.com/install.sh | bash

script:
 - yarn install
 - yarn test

 

다시 돌아와서, 리뷰어가 테스트를 실패한 이유는 access token 값이 전달되지 못했기 때문이다.

환경 변수를 관리하기 위해선 Git 저장소에서 설정 정보를 관리하고, 값의 유효성을 검증하는 것이 좋다.

(보안 문제가 있을 때는 다른 방법 강구)

 

dotenv과 joi 모듈을 사용하면, .env 할 일에 원하는 값을 등록하고 유효성 검증을 할 수 있다.

프로젝트에 .env 파일을 생성하고, access token 값을 등록해두자

 

이제 yarn으로 두 모듈을 설치한다.

$ yarn add dotenv joi
$ git add .
$ git commit -m 'Integration with dotenv and joi to manage config properties'
$ git push

이제 Travis CI로 자동 테스트 결과를 확인할 수 있다.



Node.js 버전 유지시키기


개발자들간의 Node.js 버전이 달라서 문제가 발생할 수도 있다.

애플리케이션의 서비스를 안정적으로 관리하기 위해서는 개발자의 로컬 시스템, CI 서버, 빌드 서버의 Node.js 버전을 일관적으로 유지하는 것이 중요하다.

 

package.json에서 engines 속성, nvm을 활용해 버전을 일관되게 유지해보자

"engines": {
    "node": ">=10.15.3",
 },

 

.nvmrc 파일 추가 후, nvm use 명령어를 실행하면 engines 속성에 설정한 Node.js의 버전을 사용한다.

 

$ echo "10.15.3" > .nvmrc
$ git add .
$ nvm use
Found '/Users/user/github/awesome-javascript/.nvmrc' with version <10.15.3>  
Now using node v10.15.3 (npm v6.4.1)  
...
$ git commit -m 'Add .nvmrc to maintain the same Node.js LTS version'




지금까지 알아본 점

  • Git과 GitHub을 활용해 협업 공간을 구성
  • Node.js 기반 개발 환경과 테스트 환경 설정
  • 개발 환경을 GitHub에 공유하고 리뷰하면서 발생 문제를 해결시켜나감

 

지속적인 코드 리뷰를 하기 위해 자동화를 시키자. 이에 사용하기 좋은 것들

  • ESLint로 코드 컨벤션 검증
  • Jest로 테스트 자동화
  • Codecov로 코드 커버리지 점검
  • GitHub의 webhook api로 코드 리뷰 요청

 

자동화를 시켜놓으면, 개발자들은 코드 의도를 알 수 있는 commit message, commit range만 신경 쓰면 된다.

 

협업하며 개발하는 과정에는 코드 작성 후 pull request를 생성하여 병합까지 많은 검증이 필요하다.

테스트 코드는 이 과정에서 예상치 못한 문제가 발생할 확률을 줄여주며, 구현 코드 의도를 효과적으로 전달할 수 있다.

또한 리뷰 시, 코드 컨벤션 검증뿐만 아니라 비즈니스 로직의 발생 문제도 고민이 가능하다.



[참고 사항]

'ETC' 카테고리의 다른 글

Git vs GitHub vs GitLab Flow  (0) 2022.05.06
GitHub 저장소(repository) 미러링  (0) 2022.05.06
GitHub Fork로 협업하기  (0) 2022.05.06

Git vs GitHub vs GitLab Flow

 

git-flow의 종류는 크게 3가지로 분리된다.
어떤 차이점이 있는지 간단히 알아보자

 

1. Git Flow

가장 최초로 제안된 Workflow 방식이며, 대규모 프로젝트 관리에 적합한 방식으로 평가받는다.

기본 브랜치는 5가지다.

  • feature → develop → release → hotfix → master

 

 

Master

릴리즈 시 사용하는 최종 단계 메인 브랜치

Tag를 통해 버전 관리를 한다.

 

Develop

다음 릴리즈 버전 개발을 진행하는 브랜치

추가 기능 구현이 필요해지면, 해당 브랜치에서 다시 브랜치(Feature)를 내어 개발을 진행하고, 완료된 기능은 다시 Develop 브랜치로 Merge한다.

 

Feature

Develop 브랜치에서 기능 구현을 할 때 만드는 브랜치

한 기능 단위마다 Feature 브랜치를 생성하는게 원칙이다.

 

Release

Develop에서 파생된 브랜치

Master 브랜치로 현재 코드가 Merge 될 수 있는지 테스트하고, 이 과정에서 발생한 버그를 고치는 공간이다. 확인 결과 이상이 없다면, 해당 브랜치는 Master와 Merge한다.

 

Hotfix

Mater브랜치의 버그를 수정하는 브랜치

검수를 해도 릴리즈된 Master 브랜치에서 버그가 발견되는 경우가 존재한다. 이때 Hotfix 브랜치를 내어 버그 수정을 진행한다. 디버그가 완료되면 Master, Develop 브랜치에 Merge해주고 브랜치를 닫는다.

 

git-flow에서 가장 중심이 되는 브랜치는 master와 develop이다. (무조건 필요)

이름을 변경할 수는 있지만, 통상적으로 사용하는 이름이므로 그대로 사용하도록 하자

진행 과정 중에 Merge된 feature, release, hotfix 브랜치는 닫아서 삭제하도록 한다.

이처럼 계획적인 릴리즈를 가지고 스케줄이 짜여진 대규모 프로젝트에는 git-flow가 적합하다. 하지만 대부분 일반적인 프로젝트에서는 불필요한 절차들이 많아 생산성을 떨어뜨린다는 의견도 많은 방식이다.

 

2. GitHub Flow

git-flow를 개선하기 위해 나온 하나의 방식

흐름이 단순한 만큼, 역할도 단순하다. git flow의 hotfix나 feature 브랜치를 구분하지 않고, pull request를 권장한다.

 

 

Master 브랜치가 릴리즈에 있어 절대적 역할을 한다.

Master 브랜치는 항상 최신으로 유지하며, Stable한 상태로 product에 배포되는 브랜치다.

따라서 Merge 전에 충분한 테스트 과정을 거쳐야 한다. (브랜치를 push하고 Jenkins로 테스트)

 

새로운 브랜치는 항상 Master 브랜치에서 만들며, 새로운 기능 추가나 버그 해결을 위한 브랜치는 해당 역할에 대한 이름을 명확하게 지어주고, 커밋 메시지 또한 알기 쉽도록 작성해야 한다.

그리고 Merge 전에는 pull request를 통해 공유하여 코드 리뷰를 진행한다. 이를 통해 피드백을 받고, Merge 준비가 완료되면 Master 브랜치로 요청하게 된다.

이 Merge는 바로 product에 반영되므로 충분한 논의가 필요하며 CI도 필수적이다.

Merge가 완료되면, push를 진행하고 자동으로 배포가 완료된다. (GitHub-flow의 핵심적인 부분)

 

CI (Continuous Integration)

  • 형상관리 항목에 대한 선정과 형상관리 구성 방식 결정
  • 빌드/배포 자동화 방식
  • 단위테스트/통합테스트 방식

이 세가지를 모두 고려한 자동화된 프로세스를 구성하는 것



3. GitLab Flow

github flow의 간단한 배포 이슈를 보완하기 위해 관련 내용을 추가로 덧붙인 flow 방식

 

 

Production 브랜치가 존재하여 커밋 내용을 일방적으로 Deploy 하는 형태를 갖추고 있다.

Master 브랜치와 Production 브랜치 사이에 pre-production 브랜치를 두어 개발 내용을 바로 반영하지 않고, 시간을 두고 반영한다. 이를 통한 이점은, Production 브랜치에서 릴리즈된 코드가 항상 프로젝트의 최신 버전 상태를 유지할 필요가 없는 것이다.

즉, github-flow의 단점인 안정성과 배포 시기 조절에 대한 부분을 production이라는 추가 브랜치를 두어 보강하는 전력이라고 볼 수 있다.



정리

3가지 방법 중 무엇이 가장 나은 방식이라고 선택할 수 없다. 프로젝트, 개발자, 릴리즈 계획 등 상황에 따라 적합한 방법을 택해야 한다.

배달의 민족인 '우아한 형제들'이 github-flow에서 git-flow로 워크플로우를 변경한 것 처럼 (해당 기사 링크) 브랜칭과 배포에 대한 전략 상황에 따라 변경이 가능한 부분이다.

따라서 각자 팀의 상황에 맞게 적절한 워크플로우를 선택하여 생산성을 높이는 것이 중요할 것이다.



[참고 자료]

'ETC' 카테고리의 다른 글

Collaborate with Git on Javascript and Node.js  (0) 2022.05.06
GitHub 저장소(repository) 미러링  (0) 2022.05.06
GitHub Fork로 협업하기  (0) 2022.05.06
  • 미러링 : commit log를 유지하며 clone

 

  1. 저장소 미러링
    1. 복사하고자 하는 저장소의 bare clone 생성
    2. git clone --bare {복사하고자하는저장소의 git 주소}
      
    3. 새로운 저장소로 mirror-push
    4. cd {복사하고자하는저장소의git 주소}
      git push --mirror {붙여놓을저장소의git주소}
    5. 1번에서 생성된 저장소 삭제

 

  1. 100MB를 넘어가는 파일을 가진 저장소 미러링
    1. git lfs BFG Repo Cleaner 설치
    2. 복사하고자 하는 저장소의 bare clone 생성
    3. git clone --mirror {복사하고자하는저장소의 git 주소}
      
    4. commit history에서 large file을 찾아 트랙킹
    5. git filter-branch --tree-filter 'git lfs track "*.{zip,jar}"' -- --all
    6. BFG를 이용하여 해당 파일들을 git lfs로 변경
    7. java -jar ~/usr/bfg-repo-cleaner/bfg-1.13.0.jar --convert-to-git-lfs '*.zip'
      java -jar ~/usr/bfg-repo-cleaner/bfg-1.13.0.jar --convert-to-git-lfs '*.jar'
    8. 새로운 저장소로 mirror-push
    9. cd {복사하고자하는저장소의git 주소}
      git push --mirror {붙여놓을저장소의git주소}
    10. 1번에서 생성된 저장소 삭제

 

'ETC' 카테고리의 다른 글

Collaborate with Git on Javascript and Node.js  (0) 2022.05.06
Git vs GitHub vs GitLab Flow  (0) 2022.05.06
GitHub Fork로 협업하기  (0) 2022.05.06

GitHub Fork로 협업하기


  1. Fork한 자신의 원격 저장소 확인 (최초에는 존재하지 않음)
  2. git remote -v
  3. Fork한 자신의 로컬 저장소에 Fork한 원격 저장소 등록
  4. git remote add upstream {원격저장소의 Git 주소}
  5. 등록된 원격 저장소 확인
  6. git remote -v
  7. 원격 저장소의 최신 내용을 Fork한 자신의 저장소에 업데이트
    • pull : fetch + merge
  8. git fetch upstream
    git checkout master
    git merge upstream/master

 

'ETC' 카테고리의 다른 글

Collaborate with Git on Javascript and Node.js  (0) 2022.05.06
Git vs GitHub vs GitLab Flow  (0) 2022.05.06
GitHub 저장소(repository) 미러링  (0) 2022.05.06

[Java] Record

 

 

Java 14에서 프리뷰로 도입된 클래스 타입
순수히 데이터를 보유하기 위한 클래스

 

Java 14버전부터 도입되고 16부터 정식 스펙에 포함된 Record는 class처럼 타입으로 사용이 가능하다.

객체를 생성할 때 보통 아래와 같이 개발자가 만들어야한다.

 

public class Person {
   private final String name;
   private final int age;
 
   public Person(String name, int age) {
      this.name = name;
      this.age = age;
   }
 
   public String getName() {
      return name;
   }
 
   public int getAge() {
      return age;
   }
}
  • 클래스 Person 을 만든다.
  • 필드 name, age를 생성한다.
  • 생성자를 만든다.
  • getter를 구현한다.

 

보통 Entity나 DTO 구현에 있어서 많이 사용하는 형식이다.

이를 Record 타입의 클래스로 만들면 상당히 단순해진다.

 

public record Person(
	String name,
    int age
) {}

 

자동으로 필드를 private final 로 선언하여 만들어주고, 생성자와 getter까지 암묵적으로 생성된다. 또한 equals, hashCode, toString 도 자동으로 생성된다고 하니 매우 편리하다.

대신 getter 메소드의 경우 구현시 getXXX()로 명칭을 짓지만, 자동으로 만들어주는 메소드는 name(), age()와 같이 필드명으로 생성된다.



[참고 자료]

'Language > JAVA' 카테고리의 다른 글

Stream API  (0) 2022.05.05
Error & Exception  (0) 2022.05.05
직렬화(Serialization)  (0) 2022.05.05
Primitive type & Reference type  (0) 2022.05.05
Promotion & Casting  (0) 2022.05.05

JAVA Stream

Java 8버전 이상부터는 Stream API를 지원한다

 

자바에서도 8버전 이상부터 람다를 사용한 함수형 프로그래밍이 가능해졌다.

기존에 존재하던 Collection과 Stream은 무슨 차이가 있을까? 바로 **'데이터 계산 시점'**이다.

Collection

  • 모든 값을 메모리에 저장하는 자료구조다. 따라서 Collection에 추가하기 전에 미리 계산이 완료되어있어야 한다.
  • 외부 반복을 통해 사용자가 직접 반복 작업을 거쳐 요소를 가져올 수 있다(for-each)

Stream

  • 요청할 때만 요소를 계산한다. 내부 반복을 사용하므로, 추출 요소만 선언해주면 알아서 반복 처리를 진행한다.
  • 스트림에 요소를 따로 추가 혹은 제거하는 작업은 불가능하다.

Collection은 핸드폰에 음악 파일을 미리 저장하여 재생하는 플레이어라면, Stream은 필요할 때 검색해서 듣는 멜론과 같은 음악 어플이라고 생각하면 된다.

 

외부 반복 & 내부 반복

Collection은 외부 반복, Stream은 내부 반복이라고 했다. 두 차이를 알아보자.

**성능 면에서는 '내부 반복'**이 비교적 좋다. 내부 반복은 작업을 병렬 처리하면서 최적화된 순서로 처리해준다. 하지만 외부 반복은 명시적으로 컬렉션 항목을 하나씩 가져와서 처리해야하기 때문에 최적화에 불리하다.

즉, Collection에서 병렬성을 이용하려면 직접 synchronized를 통해 관리해야만 한다.

 

 

Stream 연산

스트림은 연산 과정이 '중간'과 '최종'으로 나누어진다.

filter, map, limit 등 파이프라이닝이 가능한 연산을 중간 연산, count, collect 등 스트림을 닫는 연산을 최종 연산이라고 한다.

둘로 나누는 이유는, 중간 연산들은 스트림을 반환해야 하는데, 모두 한꺼번에 병합하여 연산을 처리한 다음 최종 연산에서 한꺼번에 처리하게 된다.

ex) Item 중에 가격이 1000 이상인 이름을 5개 선택한다.

List<String> items = item.stream()
    			.filter(d->d.getPrices()>=1000)
                          .map(d->d.getName())
                          .limit(5)
                          .collect(tpList());

filter와 map은 다른 연산이지만, 한 과정으로 병합된다.

만약 Collection 이었다면, 우선 가격이 1000 이상인 아이템을 찾은 다음, 이름만 따로 저장한 뒤 5개를 선택해야 한다. 연산 최적화는 물론, 가독성 면에서도 Stream이 더 좋다.

 

Stream 중간 연산

  • filter(Predicate) : Predicate를 인자로 받아 true인 요소를 포함한 스트림 반환
  • distinct() : 중복 필터링
  • limit(n) : 주어진 사이즈 이하 크기를 갖는 스트림 반환
  • skip(n) : 처음 요소 n개 제외한 스트림 반환
  • map(Function) : 매핑 함수의 result로 구성된 스트림 반환
  • flatMap() : 스트림의 콘텐츠로 매핑함. map과 달리 평면화된 스트림 반환

중간 연산은 모두 스트림을 반환한다.

Stream 최종 연산

  • (boolean) allMatch(Predicate) : 모든 스트림 요소가 Predicate와 일치하는지 검사
  • (boolean) anyMatch(Predicate) : 하나라도 일치하는 요소가 있는지 검사
  • (boolean) noneMatch(Predicate) : 매치되는 요소가 없는지 검사
  • (Optional) findAny() : 현재 스트림에서 임의의 요소 반환
  • (Optional) findFirst() : 스트림의 첫번째 요소
  • reduce() : 모든 스트림 요소를 처리해 값을 도출. 두 개의 인자를 가짐
  • collect() : 스트림을 reduce하여 list, map, 정수 형식 컬렉션을 만듬
  • (void) forEach() : 스트림 각 요소를 소비하며 람다 적용
  • (Long) count : 스트림 요소 개수 반환

 

Optional 클래스

값의 존재나 여부를 표현하는 컨테이너 Class

  • null로 인한 버그를 막을 수 있는 장점이 있다.
  • isPresent() : Optional이 값을 포함할 때 True 반환

 

Stream 활용 예제

  1. map()
  2. List<String> names = Arrays.asList("Sehoon", "Songwoo", "Chan", "Youngsuk", "Dajung");
    
    names.stream()
        .map(name -> name.toUpperCase())
        .forEach(name -> System.out.println(name));
  3. filter()
  4. List<String> startsWithN = names.stream()
        .filter(name -> name.startsWith("S"))
        .collect(Collectors.toList());
  5. reduce()sum : 55
  6. Stream<Integer> numbers = Stream.of(1, 2, 3, 4, 5, 6, 7, 8, 9, 10);
    Optional<Integer> sum = numbers.reduce((x, y) -> x + y);
    sum.ifPresent(s -> System.out.println("sum: " + s));
  7. collect()
  8. System.out.println(names.stream()
                       .map(String::toUpperCase)
                       .collect(Collectors.joining(", ")));



[참고자료]

'Language > JAVA' 카테고리의 다른 글

Record  (0) 2022.05.05
Error & Exception  (0) 2022.05.05
직렬화(Serialization)  (0) 2022.05.05
Primitive type & Reference type  (0) 2022.05.05
Promotion & Casting  (0) 2022.05.05

+ Recent posts