0103 - 0109
0103
이벤트 루프
최근에 비동기를 다시 한 번 공부하면서 부족했던 개념들을 채우게 되었다.
이에 대한 내용을 정리해보고자 한다.
이벤트 루프란?
- 브라우저 메인 스레드 동작 타이밍을 관리하는 기능이다.
- 메인 스레드란 JS 코드 실행이나 브라우저 렌더링을 맡는 등 브라우저의 주된 동작이 수행되는 곳이다.
- 브라우저 동작 대부분은 메인 스레드에서 싱글 스레드로 실행된다.
- 비동기 함수들을 제외한 대부분의 코드는 메인 스레드라는 곳에서 실행된다.
- 또한 브라우저 화면을 그리는 렌더링 작업도 이곳에서 실행된다.
- 메인 스레드가 싱글 스레드로 동작하는 것이 중요한 이유는 하나의 작업을 하고 있다면 다른 작업을 지연시키기 때문이다.
- 즉, 메인 스레드에서 블로킹이 발생하면 브라우저는 먹통이 되는 현상이 발생한다. 사용자 이벤트도 메인 스레드에서 동작하기 때문에 키보드 입력이나 마우스 클릭도 동작하지 않는다.
- 메인 스레드는 이벤트 루프에 의해 관리된다.
- 메인 스레드와 같이 싱글 스레드에서 하나의 작업이 오랫동안 실행되어서도 안 되고, 여러 작업 중 어떤 작업을 우선으로 동작시킬 것인가를 결정하는 것도 매우 중요하다.
- 또한 작업 간 전환 속도를 빠르게 하여 한 번에 하나의 작업씩 수행하지만 마치 동시에 수행하는 것처럼 동작해야 한다.
- 이러한 컨트롤을 이벤트 루프가 해주기에 이를 정확히 이해하는 것이 중요하다.
이벤트 루프의 우선순위
- 초기 콜스택에 쌓여있는 작업을 모두 처리한다.
- Promise.then 콜백이 마이크로태스크 큐에 등록되어 있다면 실행
- 모든 콜백이 처리될 때까지 계속 수행한다.
- 화면 갱신이 필요하다면 렌더링 파이프라인으로 이동
- 사용자가 스크롤 이동을 했거나, 어떤 요소를 클릭했거나 등등 화면을 갱신할 필요가 있을 때
- requestAnimationFrame을 사용하면 이벤트 루프가 모니터 주사율에 맞춰 렌더링 파이프라인으로 들어가려고 노력한다.
- 태스크 큐에 있는 콜백을 하나씩 실행
requestAnimationFrame vs setTimeout
- rAF와 setTimeout의 가장 큰 차이점은 1프레임 당 호출이 보장되느냐 않느냐에 차이가 있다.
- rAF는 렌더링 파이프라인과 붙어 동작하기 때문에 1프레임 당 1번의 호출이 보장되는 반면, setTimeout은 지연되어 버벅거리는 애니메이션을 제공할 수 있다.
reference
이벤트 루프, 넌 누구냐
0104
pjax & SPA
SPA로 프로젝트를 구성했을 때의 장점
여러 화면으로 구성된 애플리케이션을 구현하려다 보니 가장 먼저 하게 된 고민이 라우팅 처리였다.
기존의 링크 태그를 사용하는 전통적인 화면 전환 방식은 새로운 페이지 요청 시마다 새로 고침이 발생되어 사용성이 좋지않다고 판단했다.
그래서 사용자 경험을 향상시키기 위해 SPA로 프로젝트를 진행하고자 했다.
SPA 구성 과정
index.html에는 div 태그 하나만 선언하고, 이후 모든 렌더링은 JS를 사용해서 진행했다.
우선 페이지 별로 클래스 컴포넌트화하여 내부에 html을 반환할 메서드와 이벤트 핸들러를 등록할 메서드를 정의했다. 그리고 엔트리 파일인 app.js에서 render 함수를 정의했다. 이 함수는 현재 url의 location.pathname에 해당하는 클래스 컴포넌트를 불러와서 app id를 가진 div 태그의 내부에 html을 삽입하고, 이벤트 핸들러를 등록하는 메서드를 실행시킨다.
이를 사용하기 위해서는 a 태그의 기본 동작인 링크 이동을 막고, history를 관리할 필요가 있었다.
라우팅을 실행할 a 태그에는 data-link라는 사용자 정의 속성을 주고, 이 속성을 가진 a 태그를 클릭할 때는 preventDefault()로 기본 동작을 막았다. 그리고 a 태그의 href 속성을 가져와서 pushState 메서드를 사용하여 history에 push하고 url을 변경했다. 그리고 작성해뒀던 render 함수를 실행시켜 url에 해당하는 뷰를 렌더링했다.
또한 popState 이벤트가 발생할 때도 render 함수를 실행시켜 이전 버튼이나 다음 버튼을 클릭했을 때의 url에 해당하는 뷰를 화면에 렌더링했다.
이러한 pjax 방식은 실제 url을 변경시키기 때문에 새로고침시 서버의 도움이 필요하다는 특징을 가지고 있다.
그래서 server에서는 어떤 url로든 url 입력을 통해 get 요청을 하게 된다면 index.html을 반환하도록 했고, DOMContentLoaded 이벤트가 발생했을 때 render 함수를 실행시켜 현재 url에 해당하는 뷰가 렌더링되도록 구현했다.
그 외의 이벤트를 통해 ajax 방식으로 서버와 통신이 이루어질 때는 json 형식의 데이터를 보내주어 렌더링하도록 했다.
위와 같이 구현했을 때 문제점
제가 처음 구현했을 때는 페이지 단위로 클래스 컴포넌트를 작성했는데, 이를 컴포넌트 단위로 좀 더 분할해서 재사용성을 키우면 좋겠다는 생각을 했다.
그리고 페이지를 라우팅할 때, 중복되는 요소를 포함하여 모든 div 태그를 비우고 다시 렌더링 하는 것이 성능적으로 좋지 않다고 생각했다.
변화가 발생하지 않은 DOM을 포함하여 모든 요소를 리렌더링 해야 했기에 이에 대한 비용이 크다고 생각했고, 이를 해결하기 위해서는 변화가 발생했는 지를 확인하여 변화가 발생한 컴포넌트만 리렌더링을 해야겠다고 생각했다.
queryString이나 param을 구현한다면?
정규표현식을 통해 param이나 query string 정보를 받아오고, 클래스 컴포넌트를 생성할 때 이를 인자로 넘겨서 동적인 html을 반환할 수 있다고 같다고 생각한다.
프로젝트에 적용하지 못한 이유
페이지 내부에서 컴포넌트 분리, 효율적인 렌더링을 위한 비교 알고리즘, query string이나 param 처리 등을 고려했을 때 시간적인 문제가 발생할 것이라고 생각했다.
프로젝트가 개인 프로젝트이거나 시간이 좀 더 여유롭게 주어졌더라면 더 많은 것을 시도하고 배울 수 있을 것이라고 생각해서 끝까지 진행했을 것 같다.
하지만 팀프로젝트이고 각자 맡은 기능 파트가 있었기에 나의 탐구적인 욕심으로 팀원에게 영향이 가지 않도록 하기 위해 배우게 된 내용에 만족하며 멀티 페이지 애플리케이션으로 프로젝트를 진행했다.
느낀점
바닐라 자바스크립트로 spa를 구현하는 기본 원리에 대해 이해하게 되었고, spa 프레임워크가 이를 얼마나 더 효율적이고 편리하도록 제공해주는 지에 대해 느끼게 되었다.
더 나아가 여기에 웹 컴포넌트를 더해서 프로젝트를 진행한다면 되게 재밌을 것 같다는 느낌을 받았다. 그래서 이와 관련된 프로젝트를 진행해봐야겠다고 다짐했다.
Pjax란?
pjax란 HTML 5의 History API인 pushState와 popstate 이벤트를 사용하여 ajax의 history 관리 문제를 해결하는 라우팅 방식이다.
ajax 요청은 주소창의 URL을 변경시키지 않기 때문에 브라우저의 history 관리가 동작하지 않는다. 그렇기 때문에 새로고침을 해도 언제나 첫 페이지가 다시 로딩된다. 이때 사용하는 것이 pushState 메서드이다. pushState 메서드는 주소창의 URL을 변경하고 URL을 history entry로 추가하지만 서버로 HTTP 요청을 하지는 않는다.
단, 브라우저 새로고침 버튼을 클릭하면 브라우저 주소창의 url이 변경되기 때문에 요청이 서버로 전달된다. 즉, pjax 방식은 서버 렌더링 방식과 ajax 방식이 혼재되어 있는 방식으로 서버의 지원이 필요하다.
0105
Git action
DevOps
- Development + Operations
- 개발과 운영의 합성어
- Dev : Plan - Code - Build - Test
- Ops : Release - Deploy - Operate - Monitor
- CI/CD 툴 이용하여 Build, Test, Deploy 자동화
- Pros
- 커뮤니케이션 리소스 개선
- 개발, 배포 속도가 빨라짐
CI (Continuous Integration)
- 자동화된 프로세스
- 코드 변경사항의 정기적 빌드, 테스트 병합 자동화
- Pros
- 빠른 디버깅
- 코드 품질 개선
CD (Continuous Delivery(or Deployment))
- Continuous Delivery: 공유 저장소로 자동 Release(Test -> Staging)
- Continuous Deployment: Production Level까지 자동 Deploy(Test -> Staging -> Production)
- MSA(MicroService Architecture) + Agile 일 경우, 사용자에게 최대한 빠른 시간안에 Production 제공 필요
Issue & PR Templates
- 일관성 있는 로깅이 중요하다.
- configuration file을 만들어 issue와 PR 내용의 template 지정 가능
.github/ISSUE_TEMPLATE/*.md에 template 생성 가능Settings- Feaures_Set up templates에서 손쉽게 생성 가능
GitHub actions
- github에서 공식 제공하는 CI/CD Tools
- 개발 워크플로우 자동화
Workflow
- Job들로 구성. Event에 의해 트리거되는 자동화 된 프로세스
- 최상위 개념
- YAML으로 작성되며, .github/workflows에 저장
Event
- Workflow를 실행하는 규칙
- Push, pull request, Cron으로 연결된 외부 이벤트에 의해 실행
Job
- Step들로 구성
- 가상환경의 인스턴스에서 실행
- 다른 Job에 의존관계를 가질 수 있고, 독립 병렬 실행도 가능
Step
- Task들의 집합으로 커맨드를 실행하거나 action을 실행
Action
- 가장 작은 단위
- Step을 연결해 Job을 구성
- 재사용 가능
Runner
- workflow가 실행될 인스턴스
- GitHub-hosted runner와 self-hosted runner으로 나뉨
예시
name: Hello
on:
push:
branches:
- main
jobs:
hello:
runs-on: ubuntu-latest
steps:
- name: Say Hello
run: echo "hello, world!"
0106
다양한 로그인 방식
JWT
JWT란?
JSON 객체를 사용해서 토큰 자체에 정보들을 저장하고 있는 웹 토큰
토큰의 유효성 검증을 위한 문자열인 Signature, 그리고 정보를 해싱하기 위한 알고리즘 정보인 Header, 서버와 클라이언트가 주고받는 실제로 사용될 정보인 Payload로 구성
JWT 인증방식
- 먼저 브라우저에서 Login요청을 한다.
- 서버에서는 계정정보를 읽어 사용자를 확인한 후, 사용자의 고유한 ID값을 부여한 후, 기타 정보와 함께 Payload에 넣는다.
- JWT 토큰의 유효기간을 설정한다.
- 암호화할 secret key를 이용해 ACCESS TOKEN을 발급한다.
- 사용자는 토큰을 받아 저장한 후, 인증이 필요한 요청마다 토큰을 헤더에 실어 보낸다.
- 서버에서는 해당 토큰의 verify signature를 secret key로 복호화한 후, 조작 여부, 유효기간을 확인한다.
- 검증이 완료된다면, Payload를 디코딩하여 사용자의 ID에 맞는 데이터를 가져온다.
장점
세션/쿠키 방식은 별도의 저장소의 관리가 필요하다. 그러나 JWT는 발급한 후 검증만 하면 되기 때문에 추가 저장소가 필요 없어서 stateless 한 서버를 만들수 있다.
토큰 기반으로 하는 다른 인증시스템에 접근이 가능하다. 예를들어 Google 로그인 등은 모두 토큰을 기반으로 인증한다.
단점
Payload의 정보가 많아지면 네트워크 사용량 증가.
토큰이 클라이언트에 저장되어 토큰을 탈취 당하면 서버에서 대처하기가 어렵다.
단점 해결 방법
- 짧은 만료 기간 설정 토큰의 만료기간을 짧게 설정하면 토큰이 탈취되더라도 빠르게 만료되기 때문에 피해를 최소화 할 수 있다. 그러나 사용자가 자주 로그인해야 하는 불편함이 수반된다.
- Refresh Token
짧은 만료기간을 가지는 Access Token 및 그보다 긴 만료 기간을 가진 Refresh Token을 발급하는 전략이다.
클라이언트는 Access Token이 만료되었을 때 Refresh Token을 사용하여 Access Token의 재발급을 요청한다.
서버는 DB에 저장된 Refresh Token과 비교하여 유효한 경우 토큰을 재발급하고, 만료된 경우 사용자에게 로그인을 요청한다.
이 전략을 사용하면 Access Token의 만료 기한을 짧게 설정할 수 있고, 서버가 Refresh Token을 강제로 만료시킬 수 있다.
하지만 별도의 저장소에 저장해야 하기 때문에 JWT의 장점을 완벽하게 누릴 수 없다.
쿠키 / 세션 방식
http는 비연결성과 stateless하다는 특징을 가지고 있기에 요청에 대한 응답을 처리하게 되면 연결을 끊어 버린다.
그래서 로그인을 하더라도 다음 요청에서는 해당 클라이언트를 기억하지 못한다는 문제가 존재한다.
쿠키와 세션은 이를 보완하기 위한 기술로 가장 전통적인 로그인 방식이다.
세션은 클라이언트의 인증정보를 서버 측에 저장하고 관리한다.
로그인이 정상적으로 성공한다면 인증 정보는 서버에 관리하고 클라이언트 식별자인 JSESSIONID를 쿠키에 담는다.
이후 클라이언트는 JSESSIONID 쿠키를 요청을 보낼 때마다 함께 보내 클라이언트를 식별한다.
이 방식의 단점으로는 해커가 JSESSIONID를 탈취하여 클라이언트인 척 위장할 수 있다는 한계가 존재하고,
서버에서는 세션 저장소를 사용하므로 요청이 많아지면 서버에 부하가 심해진다.
OAuth
이는 카카오 로그인, 구글 로그인과 같이 외부 서비스에서 인증을 가능하게 하고, 그 서비스의 API를 이용하게 해주는 것을 OAuth라고 한다.
예를 들어 사용자가 서비스 접근 및 로그인 요청을 하게 되면, 서비스에서 또 다른 외부 서비스로 로그인 요청을 하게 된다.
그러면 외부 서비스에서 로그인 페이지를 제공하고, 인증 성공 시 서비스는 Access Token을 발급받게 된다.
이를 통해 로그인이 성공하게 되면 이후 서비스를 사용할 수 있고, 별도의 회원가입을 하지 않아도 Oauth를 제공하는 외부 서비스에만 가입되어 있다면 편하게 인증할 수 있다는 장점을 가지고 있다.
0107
제너레이터
제너레이터는 코드 블록의 실행을 일시 중지했다가 필요한 시점에 재개할 수 있는 특수한 함수입니다.
제너레이터 함수는 함수 호출자에게 함수 실행의 제어권을 양도할 수 있고, 함수의 호출자와 함수의 상태를 주고받을 수 있습니다.
제너레이터 함수를 호출하면 제너레이터 객체를 반환하는데, 이는 이터러블이면서 동시에 이터레이터입니다.
제너레이터랑 이터레이터 객체의 차이
제너레이터 객체는 next 메서드를 갖는 이터레이터이지만 이터레이터에 없는 return, throw 메서드를 갖는다. 제너레이터 객체의 세 개의 메서드를 호출하면 다음과 같이 동작한다.
- next 메서드를 호출하면 제너레이터 함수의 yield 표현식까지 코드 블록을 실행하고, yield된 값을 value 프로퍼티 값으로, false를 done 프로퍼티 값으로 갖는 이터레이터 리절트 객체를 갖는다.
- return 메서드를 호출하면 인수로 전달받은 값을 value 프로퍼티 값으로, true를 done 프로퍼티 값으로 갖는 이터레이터 리절트 객체를 반환한다.
- throw 메서드를 호출하면 인수로 전달받은 에러를 발생시키고 undefined를 value 프로퍼티 값으로, true를 done 프로퍼티 값으로 갖는 이터레이터 리절트 객체를 반환한다.
제너레이터 일시 중지와 재개
- 제너레이터는 yield 키워드와 next 메서드를 통해 실행을 일시 중지했다가 필요한 시점에 다시 재개할 수 있다.
- 일반 함수는 호출 이후 제어권을 함수가 독점하지만 제너레이터는 함수 호출자를 제어권에게 양도하여 필요한 시점에 함수 실행을 재개할 수 있다.
- 제너레이터 객체의 next 메서드를 호출하면 yield 표현식까지 실행되고 일시 중지된다. 이때 함수의 제어권이 호출자로 양도된다.
- 이때 제너레이터 객체의 next 메서드는 value, done 프로퍼티를 갖는 이터레이터 리절트 객체를 반환한다.
async 함수를 어떻게 구현할 수 있는가
자바스크립트의 제너레이터를 활용하여 구현할 수 있다고 생각합니다.
제너레이터 함수를 인수로 받는 async 함수를 만든 뒤,
async 함수 내부에서 인수로 받은 제너레이터 함수를 실행하여 변수에 제너레이터 객체를 저장합니다.
그리고 제너레이터를 참조하여 next메서드를 실행하고 done 프로퍼티가 true라면 value를 false라면 제네레이터 함수가 끝까지 실행되지 않았으므로 value의 후속처리 메서드를 실행하여 다시 resolve한 결과를 인수로 재귀 호출하는 클로저를 반환합니다.
사용할 때는 yield 키워드를 마치 await처럼 사용하면 async, await처럼 동작하게 구현할 수 있습니다.