본문 바로가기
스터디/개발 몰입 과정

3주차 개념 스터디

by sepang 2021. 8. 24.

TypeScript

자바스크립트의 슈퍼셋인 오픈소스 프로그래밍 언어

JavaScript 기반 언어라고 들었지만 처음에는 또 무언가 새로운 언어를 배워야 하는건가라고 생각했다. 그런데 직접 접했을 때 든 생각은 '처음 배웠던 언어인 c언어랑 되게 비슷하네?' 였다. 물론 본인은 TS의 매우 기본적인 내용만 확인했으므로 더 파보면 TS만의 구별되는 속성도 굉장히 많을 것이라 생각한다.

TS가 JS와 구별되는 가장 큰 차이점 중 하나는 정적 타입을 지원한다는 것이었다. 그리고 이 특징이 내가 TS가 C랑 비슷하다고 느꼈던 이유다. JS에서는 변수를 선언할 때 변수명 옆에 var나 let를 붙여준다. 이렇게 a라는 변수를 선언했다고 할 때 a의 타입은 문자열일수도, 숫자일수도, boolean값일수도 있고 그 외의 타입을 가질 수도 있다. 파이썬이랑 비슷해보이기도 한다. 반면 TS에서는 C언어에서 변수를 선언할 때 변수명 앞에 int, char, bool 등의 타입을 붙여주듯이 변수의 타입을 정적으로 설정할 수 있다. 만약 string으로 설정된 변수에는 number나 boolean값이 들어올 수 없다. 만약 들어온다면 오류가 발생한다.

처음에는 정적으로 변수의 타입을 정하는게 상대적으로 자유롭지 못해 불편하다고 생각했다. 하지만 이번에 TS를 사용해보면서 이런 생각이 바뀌게 되었다. 변수의 타입을 미리 정해놓았기에 변수에 잘못된 값의 대입되면서 발생하는 오류를 알아낼 수 있었기 때문이다. 만약 수천줄의 코드가 있고 내가 number타입의 파라미터를 받는 함수를 몇 십개 실행했다고 하자. 그 중 어딘가에 string 타입의 숫자(ex. '100')가 들어갔다면 결과가 이상하게 나올 것이고 문제를 파악하는게 굉장히 번거로울 것이다.

또다른 TS의 특징은 컴파일 언어이다. JS 같은 경우는 인터프리터 언어 이기 때문에 런타임에서 오류를 발견할 수 있다. 즉 직접 js파일을 실행해야 어떤 오류가 있는지 알 수 있는 것이다. TS는 컴파일을 하여 .ts파일을 .js로 변환한다. 이 과정에서 오류를 잡아낼 수 있다.

JavaScript의 비동기 기술

JS로 이뤄진 애플리케이션(프로세스)는 싱글 스레드 로 동작한다. 이는 애플리케이션 실행 시 다수의 실행단위로 나누어 실행하는 멀티 스레드와는 대조적이다. 하지만 JS에서는 비동기기술로 멀티 스레딩의 번거로운 문제들을 겪지 않고 멀티 스레딩같은 일을 수행할 수 있다.

동기 / 비동기

동기는 코드가 적혀있는 순서대로 코드의 내용을 수행한다. 한번에 한가지의 일만을 다룰 수 있다. 만약 어떤 함수가 다른 함수의 영향을 받는다면 그 함수는 다른 함수가 끝나서 값을 리턴할 때까지 대기해야 한다. 사용자는 이 대기시간 동안은 프로그램이 정지 한 것처럼 느껴질 것이다. 비동기는 당연히 동기와는 반대되는 개념이다. 어떤 일을 처리하기 전까지 대기해야하는 동기와는 다르게 비동기는 특정 코드가 종료되지 않아도 바로 다음 코드를 실행한다. 이게 어떻게 가능한건지는 JS가 동작하는 구조에 대한 이해가 필요하다. 대표적인 비동기 호출 함수인 setTImeout을 이용하여 알아보자

JS의 동작과정

JS가 어떻게 동작하는지 이해하기 위해서는 호출 스택이벤트 루프에 대해 알아야 한다. 위 사진은 이해를 돕기 위한 개념도이다.
백그라운드는 타이머를 처리하거나 이벤트 리스너를 저장하는 공간이다. setTimeout 함수가 실행되면 여기에서 시간을 재고 시간이 되면 콜백함수를 태스크 큐로 보낸다. 여기서 콜백 함수가 실행되지 않는다.

태스크 큐는 실행 예정인 콜백 함수들이 대기하는 공간이다. 는 FIFO(선입선출, 줄서기)임을 기억하자. 즉 먼저 들어온 콜백함수가 먼저 실행된다. 하지만 콜백함수는 여기서도 실행되지 않는다. 함수는 호출 스택에서만 실행된다. 그러면 백그라운드 -> 태스크 큐 -> 호출 스택으로 함수를 옮기는 건 누가 수행하는 걸까?

이벤트 루프가 바로 그러한 역할을 하는 존재이다. 함수가 실행될 때 이벤트 루프는 태스크 큐에 있는 함수를 하나씩 호출 스택으로 옮기고 그때서야 함수가 실행되는 것이다. 알아두어야 하는 건 변수나 함수의 선언은 이벤트 루프에 영향을 주지 않는다. 이벤트 루프는 함수의 호출과 관련이 있다. 그리고 JS 소스 코드가 실행될 때 anonymous라는 하나의 함수가 호출됨을 기억하자.

console.log("시작");

setTimeout(function(){
    console.log("3초후 실행");
}, 3000);

console.log("끝");

위의 그림을 바탕으로 해당 코드의 동작 순서를 살펴보자. 우선 처음에는 (anonoymous)라는 함수가 호출 스택에 쌓이고 'console.log("시작");'를 읽으면 호출 스택에 쌓이고 '시작'이 리턴되어 호출 스택에서 제거된다. 그 다음 setTimeout함수가 호출되면서 호출 스택에 setTimeout() 이 들어가게 된다. 해당 함수는 백그라운드에서 처리되므로 백그라운드로 이동되어 시간을 재고 호출 스택에서는 사라진다. 그리고 'console.log("끝");'이 호출되어 호출 스택에 올라가고 '끝'을 리턴하면서 호출 스택에서 제거된다. 그리고 (anonymous)함수도 종료된다.

이후부터 콜백함수들이 실행되기 위한 과정을 밟는다. 타이머가 만료되면 콜백 함수를 태스크 큐로 보낸다. 이벤트 루프는 호출 스택이 비어있다면 태스크 큐에 가장 앞의 콜백함수를 호출 스택으로 이동시킨다. 호출 스택에 위치하게된 콜백함수는 이제 'console.log("3초후 실행");'을 호출하고 "3초후 실행"을 리턴한다. 해당 과정이 완료되면 최종 작업이 완료된다.

이러한 비동기 특성때문에 JS는 싱글 스레드로 동작함에도 논 블로킹처리가 가능하다. 처리되어야 하는 하나의 작업이 작업 전체의 흐름을 막는 것을 블로킹, 막지 않으면 논 블로킹이라고 한다. 위에서 setTimeout이 처리되는 동안 호출 스택에서는 'console.log("끝");'을 처리하고 있었다. 즉 논블로킹이라고 볼 수 있는 것이다.

비동기 처리기술의 변화

콜백함수

function f(callback, ...){
    ...
    callback();
    ...
}
f(a, ...);

콜백 함수는 다른 함수에 인자로 넘겨지는 함수이다. 비동기적으로 콜백 함수를 호출하는 함수에 인자로 넣어주면 어떤 이벤트가 발생하거나 특정 시점이 지난 후에 해당 콜백함수를 호출하게끔 할 수 있다.

Promise

콜백함수가 중첩되는 경우가 빈번하게 발생하여 코드가 복잡해지는 것을 막기 위해 ES6부터 Promise라는 것이 도입되었다. 이를 사용하기 위해서는 Promise 객체를 생성해야 한다.

const promise = new Promise((resolve, reject) => {
    ...
});

resolve는 함수 안의 처리가 끝났을 때 호출해야하는 콜백함수이고 다음 처리를 실행하는 함수에 전달된다. reject는 함수 안의 처리가 실패했을 때 호출해야하는 콜백함수이다. 주로 오류메시지 문자열을 인수로 사용한다.

promise.then(ouFulfilled);
promise.catch(onRejected);

프로미스 내부에서 resolve가 호출되면 then이 실행되고, reject가 호출되면 catch가 실행된다. 이때 resolve, reject에 실어준 인자는 then, catch의 파라미터에서 받을 수 있다. 그리고 then의 반환값을 promise로 하면 promise가 수행된 이후 다음 then이나 catch가 호출된다. 이것으로 콜백의 중첩을 없앨 수 있게 된다.

async / await

Promise로 인해 콜백 지옥은 해결했지만 코드는 여전히 장황하다. 이를 완화하기 위해 async / await가 도입되었다.

async function 함수명(){
  await 비동기처리_메서드명();
}

CORS(Cross-Origin Resource Sharing)

웹 페이지 상의 제한된 리소스를 최초 자원이 서비스된 도메인 밖의 다른 도메인으로부터 요청할 수 있게 허용하는 구조

해석하면 교차 출처 리소스 공유이다. 웹 어플리케이션에서 자신이 속하지 않은 다른 도메인, 프로토콜, 포트에 있는 리소스를 요청할 때 웹 어플리케이션은 cross-origin HTTP요청을 실행한다. 그런데 보안상의 이유로 브라우저는 스크립트 안에서 시작되는 cross-origin HTTP 요청을 제한한다. 즉 웹 에플리케션은 다른 origin으로부터의 응답이 CORS header(http header)를 포함하지 않는 이상 자기가 로드된 origin과 동일한 origin으로 리소스를 요청하는 것만 허용이 되는 것이다.

다른 도메인의 img파일이나 css파일을 가져오는 것은 가능하지만 'script'태크로 둘러쌓인 스크립트에서 생성된 cross-origin HTTP 요청은 same-origin policy를 적용받아 cross-site HTTP 요청이 제한되는 것이다. 이를 개선하기 위해 W3C에서 CORS라는 새로운 표준을 내놓게 되었다.

JWT (JSON Web Token)

JWT는 주로 클라이언트-서버나 서비스-서비스 사이 통신 시 인증을 위해 사용하는 토큰이다.

구조

HEADER.PAYLOAD.SIGNATURE

위와 같이 . 을 기준으로 헤더, 페이로드, 서명으로 구분된다.

Header

JWT를 어떻게 검증하는가에 대한 내용이 담긴다. 이런 방법이 담긴 JSON 객체를 Base64 URL-Safe로 인코딩한다.

{
    "alg": "ES256",
    "kid": "Key ID"
}

위와 같은 JSON 객체를

Base64URLSafe(UTF-8('{"alg": "ES256","kid": "Key ID"}')) -> eyJhbGciOiJFUzI1NiIsImtpZCI6IktleSBJRCJ9

위 처럼 인코딩하여 나온 문자열이 헤더가 된다.

Payload

JWT의 내용이 들어간다. 페이로드의 속성들을 클레임 셋이라고 부른다. 클레임 셋에는 JWT에 대한 내용(토큰 생성자 정보, 생성 일시)이나 클라이언트-서버 간 주고 받기로 한 값들로 구성된다. 이 역시 해당 JSON 객체를 Base64 URL-Safe로 인코딩하면 하면 payload 문자열을 얻을 수 있다.

Signature

헤더와 페이로드를 합친 문자열을 서명한 값. 헤더의 alg와 kid에 정의된 기법을 사용하여 서명된다.

테스트 프레임워크

클래스 라이브러리나 프로그램 생성기 활용을 통해 테스트의 설계 및 작성을 용이하게 하는 소프트웨어 인프라 구조

자바스크립트 테스트 프레임워크 종류

Jest

  • 페이스북에서 제작
  • 적절한 전략과 올바른 도구 조합으로 모든 범위 테스트 가능
  • Sinon.js와 동일한 Assertion, Mocing, Spying 기능 제공Mocha
  • 서드파티 Assertionk Mocking, Spying 도구 사용
  • 때문에 유연하고 확장성 뛰어남Jasmine
  • 오랜 기간동안 사용자와 커뮤니티로 인해 생성된 방대한 자료
  • 대부분의 버전에서 Angular 지원

Mocha

  • Jamine과는 다르게 서드파티 Assertion, Mocking, Spying 도구를 사용
  • 때문에 유연하고 확장성이 뛰어남

    Jasmine

  • 오랜 시간 사용자와 커뮤니티에 의해 생성된 방대한 자료
  • 거의 모든 버전에서 Angular를 지원

Dependency Injection(의존성 주입)

의존성의 예시는 클래스 A, 클래스 B가 있을 때 B 클래스에서 A 클래스를 내부 멤버 변수로 사용하게 된다면 B는 A에 의존관계가 형성된다. 의존성이 형성되면 코드의 재활용성이 감소하고 A가 수정되었을 때 , B도 수정 해야하는 문제가 생긴다. 주입은 외부에서 객체를 생성하여 변수에 넣어주는 것이다. 그러므로 의존성 주입은 이 둘을 합친 의미로 내부에서 만든 변수를 외부에서 넣어주게 하는 것이다.

클래스 내부에서 의존하는 클래스를 직접 생성하지 않고 파라미터를 통해 주입해줌으로써 의존도를 낮추고 유연한 코드를 작성할 수 있게 되는 것이다.

데코레이터

기존에 정의된 클래스나 함수에 새로운 기능을 추가하여 사용하고 싶으면 어떻게 해야할까? 자식 클래스를 만들면 부모의 기능을 그대로 가져오면서 새로운 기능을 추가하여 사용할 수 있다. 그리고 다른 방법으로는 데코레이터이 있는데 좀더 유연한 기능 확장을 가능케 한다. 데코레이션은 메인 구문에서 부가적인 구문을 추가한다. 그리고 이 부가적인 구문을 데코레이터를 사용하여 자유롭게 사용이 가능하다.

데코레이터의 구현은 함수(이를 이용하여 추가적 기능 구현)와 동일하며 파라미터에 따라 클래스, 메소드, 프로퍼티 등에 붙일 수 있어서 각 구성요소에 기능을 추가할 수 있다. 클래스 데코레이터는 클래스의 생성자를 파라미터로 받아서 동작한다. 메소드 데코레이터는 클래스 메소드에 붙는다. 해당 메소드를 가지는 클래스, 메소드 이름, 속성 설명자(프로퍼티의 행동을 정의)를 파라미터로 받는다. 프로퍼티 데코레이터는 속성 설명자를 제외하고 메소드 데코레이터와 동일한 파라미터를 받는다.

Nest.js

Nestjs는 효율적이고, 안정적이며, 확장에 용이한 서버 어플리케이션을 구축하기 위한 진보된 nodejs 프레임워크입니다.

장점

nest.js는 TS를 적극적으로 도입하고, Dependency injection, Inversion of Control, Module을 통한 구조화 등의 기술을 통해 생산적 개발에 용이하다 (효율성). TS의 적극적 도입으로 서버 어플리케이션 개발 시 발생가능한 오류들을 사전에 방지한다. 또한 모듈로 감싸는 형태로 개발하기 때문에 테스트 수행시 모듈별로 테스트 코드 작성이 가능하여 쉽게 구현가능하다.(안정성) 또한 모듈을 통해 확장에 유리하다. (확장성)

express와의 차이점

express는 구조에 대한 자유도가 높다. 때문에 express를 사용하면서 툴, 기술, 미들웨어를 자유롭게 선택할 수 있다. 하지만 자유롭다는건 통일성이 부족하다는 의미이기도 하다. 때문에 express라는 같은 프레임워크를 사용하여도 사람이나 팀에 따라 express 구조가 다를 수 있다.

반면 nest.js는 controller, service, module이 각자의 정해진 역할을 가지고 있고 이외의 클래스들도 그들의 역할을 가지고 있다. 그러므로 정해진 틀에 맞춰야만 편히함을 취할 수 있게된다. 때문에 구조의 통일성을 갖추게 되는 것이다.

또한 nest.js는 TS를 지원하고 권장한다. 때문에 컴파일 단계에서 오류 발견이 가능하여 안정성이 높아진다.

Database ORM(Object-Relational Mapping)

이전에는 데이터 베이스의 데이터를 코드에서 가져와 사용하려면 SQL같은 쿼리문을 사용하였다. 때문에 객체 모델과 데이터 베이스 모델간 불일치가 존재하였다. 그러나 ORM을 이용한다면 객체 간 관계를 바탕으로 SQL을 자동으로 생성하여 이러한 불편을 해소해준다.

참고자료
INGG. / [JS] Javascript 동작 원리와 비동기처리 / https://ingg.dev/js-work/#async
NHN Cloud / JWT를 소개합니다 / https://meetup.toast.com/posts/239
조은우 개발 블로그 / 자바스크립트 테스트 프레임워크 간단 비교 / https://jonnung.dev/javascript/2018/11/15/tdd-javascript-testing-framework/
Log Rocket / A practical guide to TypeScript decorators / https://blog.logrocket.com/a-practical-guide-to-typescript-decorators/

댓글