Express vs Fastify 서버 프레임워크 비교

Express vs Fastify

저는 백엔드 프레임워크로 Nestjs를 사용하고 있습니다. Nestjs 는 Express 혹은 Fastify 기반으로 작성을 할 수 있기 때문에 자연스럽게 Express와 Fastify를 비교하여 어떤 경우에 어떤 것을 취해서 개발을 해야할지 고민이 되었습니다. 이같은 문제를 해결하기 위해선 이 두 프레임워크의 비교가 불가피 하였습니다.

두 프레임워크 모두 기본적으로 TypeScript 대응, 라우터 기능, Validator 기능 등 기본적인 서버 프레임워크가 갖춰야할 사항은 모두 갖추었는데 어느 부분에서 차이가 나는 것일까요?

Express

Untitled

Express는 이전부터 현재까지 쭉 Node.js의 표준처럼 사용 되어진 서버 프레임 워크입니다. 대부분 Node.js 기반으로 서버를 개발한다고 하면 Express를 떠올릴만큼 대중적이며 이 같은 이유로 참고할 자료도 훨씬 방대하고 개발자들도 익숙한 프레임워크 입니다. Fastify와 다르게 비동기 처리를 지원하지 않습니다.

Fastify

Untitled

Fastify는 이름처럼 Express보다 빠른 성능을 강조하는 프레임워크 입니다. Fastify측에서 제공하는 벤치마크에 의하면 간단한 hello world! 오버헤드를 측정하는데 Express보다 월등하게 뛰어난 성능을 보여주고 있습니다.

Benchmark

그러면 실제로 fastify와 express의 성능 벤치마크를 확인하여 성능차이를 확인해 봅시다.

https://github.com/foxifyjs/fastify-benchmarks

Untitled

express와 fastify만 비교해서 살펴본다면 약 2배 정도의 요청을 처리하는 것으로 결과가 나왔습니다.


Lerna를 사용하여 Monorepo 사용해보기

Monorepo & Lerna

Created: September 12, 2021 9:30 AM Tags: Tools Updated: September 12, 2021 1:05 PM

Untitled

About

여러 레포지토리의 dependency를 관리하고 업데이트 하는 것은 시간이 오래 걸리고 또한 연동하는데 에러가 많이 발생할 수 있습니다. 그래서 관리 포인트를 여러 레포지토리에 두는 것이 아니라 하나의 레포지토리(monorepo) 에 둠으로써 관리하는 접근 방식을 사용하기 시작 하였습니다.

이렇게 monorepo구조를 사용함으로써 얻는 장점은 아래와 같습니다.

  • 하나의 레포지토리 내에서 관리 되기 때문에 구조적으로 단순함
  • 여러 프로젝트를 하나의 레포지토리에서 관리하기 때문에 종속성에 대한 관리도 통일하여 진행할 수 있어 종속성 관리에 편함
  • 종속성간 관계를 학습하는 것이 multi-repo구조보다 적어지기 때문에 새로운 개발자가 on-boarding 하는것이 편함

본 문서에서는 Lerna를 사용하여 monorepo 를 구성하는 방법을 소개합니다.

Lerna

Lerna 설치

yarn global add lerna

Lerna를 설치해 줍니다. 아래 명령어로 설치를 확인합니다.

lerna --version

프로젝트 생성

monorepo 로 사용 될 폴더를 만들고 그 안에서 lerna init 을 실행하여 초기 구성 파일을 생성합니다.

mkdir lerna-tutorial
cd lerna-tutorial
lerna init

https://i.imgur.com/7JrupTg.png

정상적으로 구성이 완료되었다면 다음과 같은 폴더 구조를 갖게 됩니다.

https://i.imgur.com/HSL4Pyi.png

여기서 lerna.json 파일을 약간 수정을 해줍니다.

  • npm client 로 yarn을 사용함
  • workspaces를 사용함
  • 버전관리는 각 패키지마다 독립적으로 진행함
{
  "packages": [
    "packages/*"
  ],
  "npmClient": "yarn",
  "useWorkspaces": true,
  "version": "independent"
}

https://i.imgur.com/bydKq9L.png

package.json 에도 workspace 폴더를 알 수 있도록 아래 내용을 추가합니다.

"workspaces": [
   "packages/*"
],

https://i.imgur.com/sWyUIUb.png

Package 추가

기본적으로 lerna create <package명> 을 사용하여 자동으로 패키지를 추가할 수 있습니다.

lerna create playground

https://i.imgur.com/ID03lGC.png

정상적으로 패키지가 추가되었다면 아래와 같이 package 폴더 내에 추가 됩니다.

Untitled

Typescript 설정

여기서 이제 monorepo 상의 모든 패키지에서 typescript를 사용하기 위해서 typescript 종속성을 추가해 줍니다. 이를 위해 루트 폴더의 package.json의 다음과 같은 devDependencies 에 종속성을 추가해줍니다.

yarn add typescript -D
yarn add @types/node -D
yarn add ts-node -D

그리고 루트 폴더의 package.json의 scripts에 다음과 같은 스크립트를 추가합니다.

"start": "lerna run start"

lerna run startmonorepo 상의 모든 패키지에서 yarn start 가 실행되게 합니다.

또한 각 패키지의 package.json 의 scripts에는 다음과 같은 스크립트를 추가하였습니다.

"start": "ts-node lib/playground.ts"

typescript를 사용하기 위해 루트 폴더와 각 패키지 폴더에 tsconfig.json 파일을 추가합니다.

{
    "compilerOptions": {
      "module": "commonjs",
      "declaration": true,
      "noImplicitAny": false,
      "removeComments": true,
      "noLib": false,
      "emitDecoratorMetadata": true,
      "experimentalDecorators": true,
      "target": "es6",
      "sourceMap": true,
      "lib": [
        "es6"
      ]
    },
    "exclude": [
      "node_modules",
      "**/*.spec.ts"
    ]
   }

또한 각 패키지 폴더에 js로 생성된 파일을 모두 ts로 변환해 줍니다. 모든 과정을 완료했다면 다음과 같은 폴더 구조를 갖습니다.

Untitled

루트 폴더에서 lerna run start 를 실행하면 다음과 같은 결과를 얻게 됩니다.

Untitled

마무리

이 글에서 lerna 를 사용한 monorepo 프로젝트를 생성하는 법을 가볍게 소개하였습니다. 본 문서에서 소개한 명령어는 극히 일부일 뿐이고 더욱 자세한 내용는 lerna git repository 에서 더 자세한 정보를 얻을 수 있습니다.


[C++] AtCoder Beginner Contest 216

AtCoder Beginner Contest 216

결과

submission rating

아직 공식 반영이 되지 않아서 ac-predictor에서 가져왔습니다.

Rating : 740 -> 717 (-23)

풀이

A

a

A - Signed Difficulty

X.Y 를 입력받고 주어진 조건에 따라 출력만 하면 된다.

  • Y가 2 이하일 때 X-

  • Y가 3 이상 6 이하일 때 X

  • Y가 7 이상 9 이하일 때 X+

어짜피 Y는 한자리 수로 고정이므로 10을 곱해서 처리했다.

#pragma warning(disable : 4996)
#include <bits/stdc++.h>
#define all(x) (x).begin(), (x).end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef vector<ll> vll;
typedef vector<ld> vld;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pld;
typedef tuple<ll, ll, ll> tl3;
#define FOR(a, b, c) for (int(a) = (b); (a) < (c); ++(a))
#define FORN(a, b, c) for (int(a) = (b); (a) <= (c); ++(a))
#define rep(i, n) FOR(i, 0, n)
#define repn(i, n) FORN(i, 1, n)
#define tc(t) while (t--)
// https://atcoder.jp/contests/abc216/tasks/abc216_a
int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    ld t;
    cin >> t;
    ll tt = (ll)(t * 10);
    ll x = tt / 10;
    ll y = tt % 10;
    if (y <= 2)
        cout << x << "-";
    else if (y <= 6)
        cout << x;
    else
        cout << x << "+";

    return 0;
}
B

b

B - Same Name

성과 이름이 n개 주어진다. 여기서 같은 이름이 존재하면 Yes 아니면 No를 출력하는 문제

이름을 붙여서 정렬하여 같은것이 있나 확인하여 풀었다.

정렬한 뒤 동일한 이름이면 붙어있을 것이기 때문에 그것만 체크해 주었다.

#pragma warning(disable : 4996)
#include <bits/stdc++.h>
#define all(x) (x).begin(), (x).end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef vector<ll> vll;
typedef vector<ld> vld;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pld;
typedef tuple<ll, ll, ll> tl3;
#define FOR(a, b, c) for (int(a) = (b); (a) < (c); ++(a))
#define FORN(a, b, c) for (int(a) = (b); (a) <= (c); ++(a))
#define rep(i, n) FOR(i, 0, n)
#define repn(i, n) FORN(i, 1, n)
#define tc(t) while (t--)
// https://atcoder.jp/contests/abc216/tasks/abc216_b
int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    ll n;
    cin >> n;
    vector<string> s(n);
    vector<string> t(n);
    vector<string> name(n);
    rep(i, n) {
        cin >> s[i] >> t[i];
        name[i] = s[i] + " " + t[i];
    }
    sort(all(name));
    for (int i = 1; i < n; i++) {
        if (name[i] == name[i - 1]) {
            cout << "Yes";
            return 0;
        }
    }
    cout << "No";

    return 0;
}
C

c

C - Many Balls

Spell A, B가 있는데 A는 가진 공 수에 하나를 더하는 것이고 B는 가진 공 수를 두배로 만든다.

이러한 스펠을 사용해서 공을 n개 만들려고 할 떄 스펠을 사용한 순서대로 작성하는 문제

저는 역으로 n에서 시작해서 2로 나뉘어지면 B, 아니면 A로 해서 0까지 만든 뒤 뒤집어 출력하는 방법을 사용하였습니다.

최대 Spell 사용 갯수가 120으로 제한되어 있지만 10^18을 해도 정답은 길지 않기 때문에 무난하게 풀었습니다.

#pragma warning(disable : 4996)
#include <bits/stdc++.h>
#define all(x) (x).begin(), (x).end()
using namespace std;
typedef long long ll;
typedef long double ld;
typedef vector<ll> vll;
typedef vector<ld> vld;
typedef pair<ll, ll> pll;
typedef pair<ld, ld> pld;
typedef tuple<ll, ll, ll> tl3;
#define FOR(a, b, c) for (int(a) = (b); (a) < (c); ++(a))
#define FORN(a, b, c) for (int(a) = (b); (a) <= (c); ++(a))
#define rep(i, n) FOR(i, 0, n)
#define repn(i, n) FORN(i, 1, n)
#define tc(t) while (t--)
// https://atcoder.jp/contests/abc216/tasks/abc216_c
int main() {
    ios_base::sync_with_stdio(0);
    cin.tie(0);

    ll n;
    cin >> n;
    vector<char> ans;
    while (n) {
        if (n % 2 == 0) {
            n /= 2;
            ans.push_back('B');
        }
        else{
            n -= 1;
            ans.push_back('A');
        }
    }
    reverse(all(ans));
    for(int i = 0;i<ans.size();i++){
        cout << ans[i];
    }

    return 0;
}

Yarn 2 (Yarn berry)로 node-module에서 해방되기

Untitled

Description

node 기반으로 개발을 하게된다면 node-module 을 빼 놓을 수 없다. 하지만 node-module은 Ryan Lienhart Dahl도 “It’s my fault and I’m very sorry.” 라고 표현할 만큼 후회하고 있다고 하였다. (링크) 이 말대로 node-module은 비효율적인 의존성 관리때문에 많은 개발자들에게 놀림거리가 되고는 한다. 대표적인 예가 아래의 사진이다.

Untitled

그렇다면 이러한 구조가 Yarn 2(Yarn berry) 에서는 어떻게 변경되었나 알아보자

기존 Yarn에서는 Hoisting 방식을 사용하여 가능한 모든 의존성 모듈을 중앙의 위치로 추출하고 병합합니다.

Untitled

Hoisting을 사용하여 B@2.0을 유지하고 동일한 경로를 유지하면서 A@1.0, B@1.0 의 중복은 제거할 수 있었습니다.

하지만 이러한 방법을 사용하게 되면 유령 의존성(Phantom dependencies) 이 발생하게 되는데. 어떠한 패키지가 Hoisting을 통해 최상단에 위치하게 되면 package에 추가하지 않고도 사용이 가능해지는 문제가 발생합니다.

Untitled

이를 Yarn 2 에서는 PnP(Plug and Play) 를 통해서 해결하고자 하였습니다.

Yarn 2 에서는 yarn install을 수행하게 되면 node-module이 생성되는 대신 .pnp.cjs 파일이 생성됩니다. 또한 실제 의존성 모듈은 .yarn/cache에 저장됩니다. .pnp.cjs에 명시된 정보에 따라 각 패키지들이 참조 되기 때문에 기존 처럼 node-module을 뒤져볼 필요가 사라져 속도가 증가하였습니다.

이렇게 의존성을 .yarn/cache에 저장해놓기 때문에 .yarn 폴더 전체를 git에 푸시한다면 yarn install 없이 바로 프로젝트를 구동할 수 있습니다.

Migration

  1. Yarn을 설치 합니다

     npm install -g yarn
    
  2. 프로젝트 경로로 이동하여 버전을 변경합니다

     yarn set version berry
    
  3. Yarn install 을 수행하여 lock 파일에 대한 마이그레이션 을 수행합니다.
  4. .gitignore에 아래와 같은 문구를 추가합니다.

     #If you're using Zero-Installs:
     .yarn/*
     !.yarn/cache
     !.yarn/patches
     !.yarn/plugins
     !.yarn/releases
     !.yarn/sdks
     !.yarn/versions
    
     #If you're not using Zero-Installs:
     .yarn/*
     !.yarn/patches
     !.yarn/releases
     !.yarn/plugins
     !.yarn/sdks
     !.yarn/versions
     .pnp.*
    

Known Problem

  1. Yarn 2 에는 아직 yarn publish 명령어 대신 yarn npm publish 를 사용해야합니다.
  2. node를 사용한다면 node를 실행할 때 yarn node 를 사용해야 실행이 됩니다.

Dependabot + Github Action으로 dependencies 최신으로 관리하기

ss

Dependabot은 dependencies를 쉽게 최신버전으로 관리할 수 있게 해주는 툴입니다. Dependabot이 의존성의 최신여부를 체크하여 Pull Request로 각각의 의존성에 대하여 업데이트 할 수 있도록 합니다.

https://dependabot.com/

이 문서에선 Github Action을 이용하여 Dependabot을 사용하는 법을 간단히 소개합니다.

설치

  • 아래의 workflow를 추가합니다.
version: 2
updates:
- package-ecosystem: npm
  directory: "/"
  schedule:
    interval: daily
    time: "20:00"

위의 설정은 npm 패키지 업데이트를 루트 디렉토리에서 실행하는데 매일 20:00시에 수행하며 최대 PR의 갯수는 10개로 제한하는 설정입니다.

package-ecosystem

package-ecosystem 의 경우엔 npm 이외에도 여러가지 package manager를 지원합니다.

package_manager

directory

directory 의 경우엔 패키지 설정 파일이 들어있는 디렉토리를 지정해 주는 설정입니다.

npm의 경우엔 package.json 이 위치한 경로를 입력해주면 됩니다.

schedule

schedule은 업데이트를 체크할 주기를 결정합니다.

  schedule:
    interval: daily
  schedule:
    interval: weekly
  schedule:
    interval: monthly

위와 같이 daily, weekly, monthly의 3가지 옵션이 존재합니다.

또한 특정 시간에 업데이트를 실행하는 경우도 있습니다. 이는 time, timezone으로 설정이 가능합니다.

  schedule:
    interval: daily
    time: "09:00"
    timezone: "Asia/Tokyo"

ignore

ignore는 업데이트 체크 여부를 하지 않을 dependency를 지정합니다.

# Use `ignore` to specify dependencies that should not be updated 

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
    ignore:
      - dependency-name: "express"
        # For Express, ignore all updates for version 4 and 5
        versions: ["4.x", "5.x"]
        # For Lodash, ignore all updates
      - dependency-name: "lodash"

위와 같이 패키지명을 지정하여 통째로 무시할 수 있으며, 패키지명과 버전을 명시해서 특정 버전대만을 무시할 수도 있습니다.

open-pull-requests-limit

open-pull-requests-limit는 Dependabot이 Open할 최대 Pull Request의 갯수를 지정합니다. 만약 Dependabot이 해당 제한만큼 Pull Request를 생성했다면 해당 PR들을 Close하거나 Merge하기 전 까지 새로운 PR을 생성하지 않습니다.

# Specify the number of open pull requests allowed

version: 2
updates:
  - package-ecosystem: "npm"
    directory: "/"
    schedule:
      interval: "daily"
    # Disable version updates for npm dependencies
    open-pull-requests-limit: 0

  - package-ecosystem: "pip"
    directory: "/"
    schedule:
      interval: "daily"
    # Allow up to 10 open pull requests for pip dependencies
    open-pull-requests-limit: 10

이후로도 여러가지 옵션이 있지만 나머지는 링크에서 확인해 보세요

Auto-Dependabot

위에서 Dependabot이 Pull Request를 열어주었을 때 해당 PR이 Github Action들을 전부 통과했다면 해당 PR을 자동으로 Merge하게 됩니다.

설치

아래 workflow를 통해서 설정할 수 있습니다. 여기엔 SECRET값으로 repo를 컨트롤할 수 있는 권한을 가진 Github Token이 필요합니다.

name: auto-merge

on:
  pull_request_target:

jobs:
  auto-merge:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v2
      - uses: ahmadnassri/action-dependabot-auto-merge@v2
        with:
          target: minor
          github-token: $

위 workflow를 적용했다면 아래 사진처럼 모든 Checks에 대해서 Pass를 한 Pull Request에 대해서 자동으로 Review를 설정하고 자동으로 Merge를 하게 됩니다.

ss