본문 바로가기

내일 배움 캠프/Weekly I learned

WIL 20230806

728x90

0. 기간 : 

20230731 ~ 20230806

1-1. 이번 주 동안 있었던 일

키오스크 과제 보완

타입스크립트 강의 완강

TIL 복습 및 정리 (~ 2023년 8월 1일)

CI CD 구축 (배포에서 생명주기로 인해 고전 중)

1-2. 이번 주 체크리스트

[ O ] 예쁜 말로 협업하였는가?

[ O ] 12시간 동안 몰입하였는가?

[ X ] 코딩 시 복사 + 붙여넣기를 하지 않았는가?

다른 사람의 이메일을 git config에 등록하는 사고가 있었다. 

[ O ] 친절한 개발자였는가?

[ O ] 이번 주를 평가하였을 때 주니어 개발자가 될 수 있겠는가?

될 수 있다면 책임감을 가진 주니어 개발자가 되고 싶다.

2. 이번 주 소감

많이 배워 나간 한 주였다.

책임감 있는 사람(개발자)가 되고 싶다.

3. 이번 주에 배운 것

1. 관리자 설정 API 아이디어 

- 시간 관계상 다음 프로젝트 때 구현하는 걸로 한다. (사유 : 타입스크립트 공부)

1. request-ip 라이브러리를 이용하여 클라이언트 ip를 받는다.

참고자료 : https://riverblue.tistory.com/22

2. 접근이 허용된 클라이언트 ip에게만 관리자 계정으로 수정 가능한 API에 접근을 허용한다.

3. 접근이 허용된 클라이언트 ip들은 따로 테이블을 만들어 관리한다.

 

[ 튜터님 조언 ]

> ip가 변경될 수 있으므로 userid 기준으로 특정 아이디를 root관리자로 설정

> root관리자로 로그인 했을 때만 관리자 추가 가능하게 한다.

 

2. getter

문제

클래스 안에 함수를 만들어 return으로 env 정보들을 주었다.

하지만 env는 중요한 정보들이라 함부로 변경되면 안 되기 때문에 접근을 제한하는 것이 맞다.

시도

언더바와 getter를 사용하여 캡슐화한다.

해결

알게된 점 

중요한 정보는 _로 가리고 getter 를 이용해 가져온다.

 

3. limit 1

문제 

이전에 sql쿼리를 사용할 때 limit 1을 걸지 않고 데이터 1개를 추출한 적이 있다.

이에 대한 피드백을 받았다. 아주 나이스한 기회이다.

시도

왜냐하면 마침 오늘 SQL과제를 하며 limit 1을 사용할 일이 있었다. 이 경우에는 일치하는 정보가 많아서 limit 1이 유용하였다.

해결

알게 된 점

일치하는 정보가 1개인 경우에도 limit 1을 쓰면 SQL쿼리가 돌다가 일치하는 정보를 만나면 바로 멈추고 상위 1개 데이터를 반환하므로 성능을 높일 수 있다. 

 

4. Thunder Client에서 localhost 또는 127.0.0.1 API 전송  무한 로딩(Processing)

문제

오늘 아침부터 Thunder Client API 작동하지 않음.

시도

Postman 으로 시도 할 경우 정상작동하여 나의 이슈가 아닌 Thunder Client의 이슈일 수 있다고 판단

인터넷에 Thunder Client 관련 찾아 봄.

 

찾은 결과 : 

thunder-client-support 공식 레포지토리 이슈

https://github.com/rangav/thunder-client-support/issues/1251#issuecomment-1666113888

해결

결론 :  최신 VScode update 하게 되면 발생하게 되는 Thunder Client 이슈

  1. VScode July 2023 (version 1.81) : 127.0.0.1 대신 0.0.0.0  입력하여 API 호출
  2. VScode June 2023 (version 1.80) 으로 돌아가기

알게된 것

가끔 휴먼 에러가 아닐 수도 있다.

문제를 정확히 판별하기 위해 여러가지 방법을 사용해 보고 결과를 도출하자.

 

5. CI 기초

 

5-1. 테스트 코드 작성

//__test__/integration/items.integration.spec.js

import supertest from 'supertest';
import { ExpressApp } from '../../app.js';
import sequelize from '../../db/sequelize.js';

const expressApp = new ExpressApp();
const app = expressApp.app;

beforeAll(async () => {
  if (process.env.NODE_ENV === 'test') await sequelize.sync();
  else throw new Error('NODE_ENV가 test 환경으로 설정되어 있지 않습니다.');
});

describe('Layered Architecture Pattern, Posts Domain Integration Test', () => {
  test('GET /api/items API (getItems) Integration Test Success Case, Not Found Itmes Data', async () => {
    const response = await supertest(app)
      .get(`/api/items`) 
      .query({ category: 'all' }) 
      .send({}); 

    expect(response.body).toEqual({
      message: '전체 상품이 조회되었습니다.',
      list: [],
    });
  });

  test('POST /api/options API (createOptions) Integration Test Success Case', async () => {
    const createOptionRequestBodyParams = {
      extra_price: 200,
      shot_price: 0,
      hot: false,
    };

    const response = await supertest(app)
      .post(`/api/options`) 
      .query({}) 
      .send(createOptionRequestBodyParams);

    expect(response.status).toEqual(200);

    expect(response.body).toMatchObject({
      message: '옵션 추가에 성공하였습니다.',
    });
  });

  test('POST /api/items API (createItem) Integration Test Success Case', async () => {
    const createItemRequestBodyParams = {
      name: '치즈케이크',
      price: 2000,
      type: 'food',
      option_id: 1,
    };

    const response = await supertest(app)
      .post(`/api/items`) 
      .query({}) 
      .send(createItemRequestBodyParams); 

    expect(response.status).toEqual(200);

    expect(response.body).toMatchObject({
      message: '상품 추가에 성공하였습니다.',
    });
  });

  test('POST /api/posts API (createPost) Integration Test Error Case, Invalid Params Error', async () => {
    const createItemRequestBodyParamsByInvalidParamsError = {
      name: '아메리카노',
      price: 1000,
      type: 'coffee',
      option_id: 200,
    };

    const response = await supertest(app)
      .post(`/api/items`) 
      .query({}) 
      .send(createItemRequestBodyParamsByInvalidParamsError); 

    expect(response.body).toMatchObject({
      message: '정확한 옵션을 입력해 주세요.',
    });
  });

  test('GET /api/items API (getItems) Integration Test Success Case, is Exist Posts Data', async () => {
    const response = await supertest(app)
      .get(`/api/items`) 
      .query({ category: 'all' }) 
      .send({}); 

    expect(response.body).toMatchObject({
      message: '전체 상품이 조회되었습니다.',
      list: [
        {
          id: 1,
          name: '치즈케이크',
          option_id: 1,
          price: 2000,
          type: 'FOOD',
          amount: 0,
          createdAt: expect.anything(),
          updatedAt: expect.anything(),
          option: {
            id: 1,
            extra_price: 200,
            shot_price: 0,
            hot: 0,
            createdAt: expect.anything(),
            updatedAt: expect.anything(),
          },
        },
      ],
    });
  });
});

afterAll(async () => {
  if (process.env.NODE_ENV === 'test') await sequelize.sync({ force: true });
  else throw new Error('NODE_ENV가 test 환경으로 설정되어 있지 않습니다.');
});

5-2. yml 파일 작성

// github/workflows/test.yml

name: Node.js CI

on:
  push:
    branches: ['master']
  pull_request:
    branches: ['master']

jobs:
  build:
    runs-on: ubuntu-latest

    strategy:
      matrix:
        node-version: [18.x]

    steps:
      - uses: actions/checkout@v3
      - name: Use Node.js ${{ matrix.node-version }}
        uses: actions/setup-node@v3
        with:
          node-version: ${{ matrix.node-version }}
          cache: 'npm'

        env:
          MYSQL_USERNAME: ${{secrets.MYSQL_USERNAME}}
          MYSQL_PASSWORD: ${{secrets.MYSQL_PASSWORD}}
          MYSQL_DATABASE: ${{secrets.MYSQL_DATABASE}}
          TEST_DATABASE: ${{secrets.TEST_DATABASE}}
          MYSQL_HOST: ${{secrets.MYSQL_HOST}}
          MYSQL_PORT: ${{secrets.MYSQL_PORT}}
          HOST: ${{secrets.HOST}}
          PORT: ${{secrets.PORT}}

      - run: echo "MYSQL_USERNAME=$MYSQL_USERNAME" >> .env
          echo "MYSQL_PASSWORD=$MYSQL_PASSWORD" >> .env
          echo "MYSQL_DATABASE=$MYSQL_DATABASE" >> .env
          echo "TEST_DATABASE=$TEST_DATABASE" >> .env
          echo "MYSQL_HOST=$MYSQL_HOST" >> .env
          echo "MYSQL_PORT=$MYSQL_PORT" >> .env
          echo "HOST=$HOST" >> .env
          echo "PORT=$PORT" >> .env
          npm ci
          npm test

5-3. github actions 및 secrets 사용 설정

 

5-4. test 통과

문제

package-lock.json 파일이 없으면 실행이 안 되는 것 같다.

시도

package-lock.json 파일을 생성.

해결

해결함.

알게 된 점 

GitHub Actions에서는 캐싱(caching)을 지원하기 때문에 CI 서버에서 npm 패키지를 설치할 때 성능을 향상 시킬 수 있다.

참고 자료 : https://www.daleseo.com/github-actions-setup-node/

6.  git config

문제

CI CD 연습을 위해 ec2 인스턴스에서 SSH 를 만들고 github에 등록하였다.

이후에 다른 사람의 이름으로 commit이 올라갔다.

시도

SSH 를 몇 번 확인하였으나 아무리 생각해도 SSH는 중복이 되기 어렵다.

동생의 의견도 동일하였다.

해결

일단 commit이 잘못 되고 계신 계정 주인 분께 사과 메일을 보냈다.

친절한 회신을 통해 방법을 알려 주셨다.

git config를 확인해 보니 그 분의 이메일로 되어 있었다.

배포가 잘 안 되어서 급한 마음에 또 복붙을 한 것 같다.

알게된 것

다른 사람의 이름으로 commit이 올라간다면 그 사람의 이메일을 git config 에 사용 중일 수 있다. 

 

7. DeploymentLimitExceededException

문제 

deployment가 이미 있다고 한다. 

시도

기존에 임의로 만들어 두었던 깡통 deployment를 삭제하였다.

file_exists_behavior : OVERWRITE 로 설정해 주었다.

해결

성공하였다.

알게 된 점

deployment가 중복 될 때는 확인해서 필요 없는 것은 삭제한 뒤

file_exists_behavior : OVERWRITE 로 설정해 준다.

version: 0.0
os: linux
files:
  - source: /home/ubuntu/kiosk
    destination: /home/ubuntu/build
    overwrite: yes
    file_exists_behavior: OVERWRITE

hooks:
  AfterInstall:
    - location: scripts/after-deploy.sh
      timeout: 300
      runas: ubuntu

 

4. 배운 것들을 토대로 적용할 미래

자신이 만든 것에 대해 책임감 있게 처리할 수 있는 개발자

자신이 만든 것을 검토 및 자동화하는 CI CD를 구축할 수 있는 개발자

5. 다음주 목표

심화 프로젝트 완성

팀원들이 동의한다면 CI CD 구축

6. 나에게 응원 마디

파이팅 할 수 있다 

'내일 배움 캠프 > Weekly I learned' 카테고리의 다른 글

WIL 20230820  (0) 2023.08.20
WIL 20230813  (0) 2023.08.13
WIL 20230730  (0) 2023.07.30
WIL 20230723  (0) 2023.07.23
WIL 20230716  (0) 2023.07.16