Skip to content

ymkim97/typescript-task

Repository files navigation

[2024] λ°±μ—”λ“œ (Node.js) 기술 과제 - κΉ€μ˜λͺ…

Table of Contents

μ‹€ν–‰ 방법

Docker desktop λ˜λŠ” Docker μ‹€ν–‰ μƒνƒœ ν”„λ‘œμ νŠΈ λ””λ ‰ν† λ¦¬μ˜ ν„°λ―Έλ„μ—μ„œ docker-compose up -d λ₯Ό μž…λ ₯ν•˜λ©΄ 1개의 Image, 3개의 Containerκ°€ μƒμ„±λ©λ‹ˆλ‹€.

Container

  • youngmyung-Nodejs-Express: <Port 3000> Nodejs API μ„œλ²„μž…λ‹ˆλ‹€.
  • youngmyung-mysql: <Port 3306> MySQL μ„œλ²„μž…λ‹ˆλ‹€.
  • youngmyung-mysql-test: <Port 3307> Test code 싀행을 μœ„ν•œ MySQL μ„œλ²„μž…λ‹ˆλ‹€.

Test codeλ₯Ό μ‹€ν–‰ν•˜κΈ° μœ„ν•΄μ„œλŠ” docker-compose ν›„ npm iλ₯Ό μž…λ ₯ν•˜κ³  npm testλ₯Ό μž…λ ₯ν•΄μ£Όμ„Έμš”.

mysql, mysql-test λͺ¨λ‘ 강사(instructor) ν…Œμ΄λΈ”μ€ 9개의 ν…ŒμŠ€νŠΈ 데이터가 λ“€μ–΄μžˆμŠ΅λ‹ˆλ‹€ (id = 1 ~ 9).

기술 μŠ€νƒ

  • NodeJS (TypeScript)
  • Express
  • MySQL
  • mysql2
  • TSyringe
  • ts-node-dev
  • class-validator
  • express-validator
  • class-transformer
  • Jest / ts-jest
  • ts-mockito
  • supertest
  • Winston

섀계 λ°©ν–₯

ν”„λ‘œμ νŠΈλ₯Ό μ„€κ³„ν• λ•Œ λ ˆμ΄μ–΄λ“œ μ•„ν‚€ν…μ²˜λ‘œ κ΅¬μ„±ν•˜μ˜€μŠ΅λ‹ˆλ‹€. μ΄λ•Œ dtoλ₯Ό μ‚¬μš©ν•˜μ—¬ μš”μ²­κ³Ό 응닡이 계측간 단방ν–₯으둜 이동할 수 μžˆλ„λ‘ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

  • Presentation layer - Router, Controller
  • Application/Business layer - Service
  • Persistence layer - Repository
  • Database layer - MySQL

이둜써 계측과 관심사λ₯Ό λΆ„λ¦¬ν•˜μ—¬ 각자의 역할을 μˆ˜ν–‰ν•˜λ„λ‘ ν•˜κ³ , 각 계측 μ‚¬μ΄μ˜ μ˜μ‘΄μ„±μ„ μ€„μ˜€μŠ΅λ‹ˆλ‹€.
Presentation layerλ₯Ό 톡해 μš”μ²­μ„ λ°›μ•„ μž…λ ₯값을 검증 ν›„ Application layerκ°€ λΉ„μ¦ˆλ‹ˆμŠ€ λ‘œμ§μ— 집쀑할 수 μžˆλ„λ‘ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
νŠΉμ • 데이터에 μ ‘κ·Όν•˜λŠ” 같은 쿼리λ₯Ό λ‹€λ₯Έ μ„œλΉ„μŠ€λ“€μ΄ μ‚¬μš©ν•  수 μžˆμ–΄ μ½”λ“œμ˜ μž¬μ‚¬μš©μ„±μ„ μœ„ν•΄ Persistence layer만 DB와 μƒν˜Έμž‘μš©ν•˜λ„λ‘ μ„€κ³„ν–ˆμŠ΅λ‹ˆλ‹€.
ν”„λ‘œμ νŠΈμ˜ 크기가 아직 μž‘μœΌλ©° 크게 λ³΅μž‘ν•˜μ§€ μ•Šκ³ , DI νŒ¨ν„΄κ³Ό μ–΄μšΈλ¦¬λŠ” 효율적인 μ•„ν‚€ν…μ²˜λΌκ³  μƒκ°ν•˜μ—¬ ν˜„μž¬ 제 상황에 잘 λ§žλŠ” 섀계라고 μƒκ°ν•˜μ—¬ μ„ νƒν•˜μ˜€μŠ΅λ‹ˆλ‹€.
λ˜ν•œ, 이런 ꡬ쑰와 DI νŒ¨ν„΄μœΌλ‘œ 인해 μ˜μ‘΄μ„±μ΄ 쀄어든 만큼 λ‹¨μœ„ ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό 보닀 μœ μ—°ν•˜κ²Œ μž‘μ„±ν•  수 μžˆλŠ” 것을 λŠλ‚„ 수 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.

κ΅¬ν˜„ κ³Όμ • 및 κ³ λ―Όλ“€

  • ν”„λ‘œμ νŠΈ μ‹œμž‘ μ‹œ 각쒅 κΈ°λŠ₯ λ‘œλ”©, MySQL Pool 생성 확인 방법 κ³ λ―Ό

    ν˜„μž¬ NodeJS + Express κ΅¬μ‘°λ‘œλŠ” μ œκ°€ ν‰μ†Œμ— μ‚¬μš©ν•˜λ˜ SpringBoot와 같이 섀정듀을 μžλ™μœΌλ‘œ μž‘μ•„μ£Όμ§€ μ•ŠλŠ”λ‹€λŠ” 것을 ν•΄κ²°ν•˜κΈ° μœ„ν•΄ κ³ λ―Όν–ˆμŠ΅λ‹ˆλ‹€.
    κ·Έ κ²°κ³Ό μ„œλ²„λ₯Ό μ‹œμž‘ν•˜λŠ” server.tsλ₯Ό 두고 이에 ν•„μš”ν•œ DI container μ΄ˆκΈ°ν™”, express app 생성 등을 μ§„ν–‰ν•˜κ³  3000포트λ₯Ό listenν•˜λ„λ‘ μ„€μ •ν–ˆμŠ΅λ‹ˆλ‹€.
    효율적인 DB connection을 μœ„ν•΄ μ„œλ²„ μ΄ˆκΈ°ν™” μ‹œ MySQL Pool μΈμŠ€ν„΄μŠ€λ₯Ό μƒμ„±ν•˜μ—¬ container에 λ“±λ‘ν•˜κ³ , repositoryμ—μ„œ ν•΄λ‹Ή μΈμŠ€ν„΄μŠ€λ₯Ό μ΄μš©ν•˜μ—¬ queryλ₯Ό μ‹€ν–‰ν•˜λ„λ‘ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

  • μ΅œλŒ€ν•œ μ—λŸ¬ 이유λ₯Ό ν΄λΌμ΄μ–ΈνŠΈμ—κ²Œ 전달해주기. λ‚΄λΆ€ μ—λŸ¬λŠ” λ…ΈμΆœν•˜μ§€ μ•ŠκΈ°

    λ°±μ—”λ“œ 개발자의 κ΅¬ν˜„ λŠ₯λ ₯도 정말 μ€‘μš”ν•˜μ§€λ§Œ μ—λŸ¬λ₯Ό 잘 닀루고 μ°Ύμ•„λ‚΄λŠ” 것도 그만큼 μ€‘μš”ν•˜λ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€.
    μ„œλΉ„μŠ€ 및 μ„œλ²„μ˜ μž₯μ•  λŒ€μ‘μ€ 물둠이고, λ‹€λ₯Έ λ°±μ—”λ“œ, ν”„λ‘ νŠΈμ—”λ“œ, 데브옡슀 λ“±μ˜ νŒ€λ“€κ³Όμ˜ ν˜‘μ—…μ—λ„ 큰 영ν–₯을 μ€€λ‹€κ³  μƒκ°ν•˜κΈ° λ•Œλ¬Έμž…λ‹ˆλ‹€.
    λ”°λΌμ„œ 잘λͺ»λœ μš”μ²­μ΄λ‚˜ λ‚΄λΆ€ μ„œλ²„μ˜ μ—λŸ¬ 등을 μ»€μŠ€ν…€ μ—λŸ¬λ₯Ό 톡해 μ„ΈλΆ„ν™”ν•˜κ³ , λ³΄μ•ˆμ„ μœ„ν•΄ λ‚΄λΆ€ μ—λŸ¬ μŠ€νƒμ€ λ…ΈμΆœλ˜μ§€ μ•Šλ„λ‘ν•˜λ©°, 각 μ—λŸ¬μ— λ”°λ₯Έ λ©”μ‹œμ§€λ₯Ό 좜λ ₯ν•  수 μžˆλ„λ‘ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.
    κ°„ν˜Ή μ—λŸ¬λ₯Ό catch ν›„ λ‹€μ‹œ throw ν• λ•Œ κΈ°μ‘΄ μ—λŸ¬ μŠ€νƒμ΄ μ‚¬λΌμ§€λŠ” ν˜„μƒμ„ λ°œκ²¬ν•˜μ—¬ 이λ₯Ό λ°©μ§€ν•˜κΈ° μœ„ν•΄ μŠ€νƒμ„ 계속 μΆ•μ ν•˜λ„λ‘ ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
    μ—λŸ¬λ₯Ό μΆ”μ ν•˜κΈ° μœ„ν•œ λ‘œκ·Έλ„ ν•„μˆ˜λΌκ³  μƒκ°ν•˜μ—¬ Winston 라이브러리λ₯Ό 톡해 둜그 μˆ˜μ§‘λ„ λ„μž…ν•˜μ˜€μŠ΅λ‹ˆλ‹€.

  • 반볡 λ˜λŠ” DB try-catch-finally ν•˜λ‚˜λ‘œ -> κ³ μ°¨ ν•¨μˆ˜ μ‚¬μš©

    Repositoryμ—μ„œ connection을 생성 ν›„ 쿼리λ₯Ό μˆ˜ν–‰ν•˜κ³ , λ‹€μ‹œ releaseν•˜λŠ” μž‘μ—…μ΄ try-catch-finally둜 λͺ¨λ“  λ©”μ„œλ“œμ—μ„œ λ°˜λ³΅λ˜μ–΄ 이λ₯Ό 쀄일 수 μžˆλŠ” 방법을 κ³ λ―Όν–ˆμŠ΅λ‹ˆλ‹€.
    ν•¨μˆ˜ν˜• ν”„λ‘œκ·Έλž˜λ°μ˜ νŠΉμ§•μ„ μ§€λ‹Œ μžλ°”μŠ€ν¬λ¦½νŠΈμ—μ„œ ν•¨μˆ˜λ₯Ό νŒŒλΌλ―Έν„°λ‘œ μ „λ‹¬λ°›κ±°λ‚˜ μ—°μ‚°μ˜ 결과둜 λ°˜ν™˜ν•΄μ£ΌλŠ” λ©”μ„œλ“œμΈ κ³ μ°¨ ν•¨μˆ˜λ₯Ό μ΄μš©ν•˜μ—¬ ν•΄κ²°ν–ˆμŠ΅λ‹ˆλ‹€. mysqlUtil.ts
    ν•΄λ‹Ή νŒŒμΌμ—λŠ” νŠΈλžœμž­μ…˜μ΄ μžˆλŠ” 쿼리와 μ—†λŠ” 쿼리둜 λ©”μ„œλ“œλ₯Ό κ΅¬λΆ„ν•˜μ—¬ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

  • 포괄적인 μ—λŸ¬ 핸듀링과 비동기 μ—λŸ¬

    포괄적인 μ—λŸ¬ 핸듀링 섀정을 직접 μˆ˜ν–‰ν•˜μ§€ μ•ŠλŠ” 경우 λ‚΄μž₯된 κΈ°λ³Έ μ—λŸ¬ ν•Έλ“€λŸ¬κ°€ 호좜되고 μŠ€νƒ 슀레이슀λ₯Ό λ°˜ν•œν•˜μ—¬ 외뢀에 λ…ΈμΆœλ©λ‹ˆλ‹€.
    λ”°λΌμ„œ app.use((err, req, res, next))λ₯Ό μ΄μš©ν•˜μ—¬ 포괄적인 μ—λŸ¬ 핸듀링을 직접 μ„€μ •ν–ˆμŠ΅λ‹ˆλ‹€.
    ν•˜μ§€λ§Œ μ΄κ²ƒμœΌλ‘œλŠ” 비동기 μ—λŸ¬λ₯Ό μž‘μ§€ λͺ»ν•˜λŠ” λ¬Έμ œκ°€ μžˆμ—ˆμŠ΅λ‹ˆλ‹€. try-catch둜 ν•΄κ²° ν•  수 μžˆμ§€λ§Œ 이것도 λ°˜λ³΅λ˜μ–΄ 곡톡화가 ν•„μš”ν•˜λ‹€κ³  μƒκ°ν–ˆμŠ΅λ‹ˆλ‹€.
    λ”°λΌμ„œ routerμ—μ„œ controller둜 μš”μ²­μ„ λ„˜κ²¨μ£ΌκΈ° 전에 wrapAPI ν•¨μˆ˜λ₯Ό λ§Œλ“€μ–΄ ν•΄λ‹Ή ν•¨μˆ˜λ₯Ό κ°μ‹Έμ„œ 보내도둝 κ΅¬ν˜„ν•˜μ˜€μŠ΅λ‹ˆλ‹€. wrapAsync.ts
    κ·Έ κ²°κ³Ό 비동기 μ—λŸ¬κ°€ λ°œμƒν•΄λ„ ν•¨μˆ˜λ₯Ό 톡해 포괄적인 μ—λŸ¬ ν•Έλ“€λ§μœΌλ‘œ 갈 수 μžˆλ„λ‘ μœ λ„ν–ˆμŠ΅λ‹ˆλ‹€.

  • κΈ°μ‘΄ λ‹€λ₯Έ repository의 μ—¬λŸ¬ λ©”μ†Œλ“œλ₯Ό μ‚¬μš©ν•  λ•Œ νŠΈλžœμž­μ…˜ 처리

    Repository λ‚΄μ˜ ν•˜λ‚˜μ˜ λ©”μ†Œλ“œμ—μ„œ νŠΈλžœμž­μ…˜μ„ μ‚¬μš©ν•˜λŠ” 것이 μ•„λ‹Œ, μ„œλΉ„μŠ€μ—μ„œ μ—¬λŸ¬ repositoryλ₯Ό ν•œλ²ˆμ— μ΄μš©ν• λ•Œ νŠΈλžœμž­μ…˜μ„ μ–΄λ–»κ²Œ μ²˜λ¦¬ν• μ§€ κ³ λ―Όν–ˆμŠ΅λ‹ˆλ‹€.
    그러기 μœ„ν•΄μ„œλŠ” serviceμ—μ„œ connection을 μƒμ„±ν•˜κ³ , μœ„μ—μ„œ κ΅¬ν˜„ν•œ κ³ μ°¨ ν•¨μˆ˜λ₯Ό μ΄μš©ν•˜μ—¬ νŠΈλžœμž­μ…˜μ„ μ‚¬μš©ν•˜λ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€.
    Serviceμ—μ„œ μ‹œμž‘ν•œ connection은 repositoryμ—μ„œλ„ μœ μ§€ λ˜λ„λ‘ parameterλ₯Ό λ§Œλ“€κ³  λΆ„κΈ° 처리λ₯Ό ν•˜μ—¬, ν•΄λ‹Ή repository λ©”μ„œλ“œκ°€ μƒˆλ‘œμš΄ connection이 ν•„μš”ν•œμ§€ λΆˆν•„μš”ν•œμ§€ κ΅¬λΆ„ν•˜λ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€.

    public async deleteAllByStudentId(
    id: number,
     prevConnection?: PoolConnection,
    ): Promise<void> {
     const sql = 'DELETE FROM class WHERE id = ?;';
     const value = [id];
    
    if (!prevConnection) {
      const connection = await this.mysqlPool.getConnection();
    
      return await executeQuery(connection, async () => {
        await connection.query<ResultSetHeader>(sql, value);
      });
    } else {
      await prevConnection.query<ResultSetHeader>(sql, value);
      }
    }
    
  • κ°•μ˜λ₯Ό μ˜€ν”ˆ, μˆ˜μ •, μ‚­μ œν• λ•Œ (select ~ where id = ? and ins_id ?)둜 μ•ˆν•˜κ³  id만으둜 courseλ₯Ό λ¨Όμ € find ν•˜κΈ°

    κ°•μ˜λ₯Ό μ˜€ν”ˆ, μˆ˜μ •, μ‚­μ œλ₯Ό ν•˜κΈ° μœ„ν•΄μ„œ μœ„μ™€ 같은 쿼리둜 ν•œλ²ˆμ— μ‹€ν–‰ν•˜μ—¬ 성곡 λ˜λŠ” μ‹€νŒ¨λ₯Ό λ°˜ν™˜ν•  μˆ˜λ„ μžˆμ—ˆμŠ΅λ‹ˆλ‹€.
    κ·ΈλŸ¬λ‚˜ 이 λ°©λ²•μœΌλ‘œ μ§„ν–‰ν•˜λ©΄ ν…Œμ΄λΈ”μ„ 찾을 수 μ—†μ„λ•Œ κ°•μ˜μ˜ 쑴재 유무, μš”μ²­μžκ°€ ν•΄λ‹Ή κ°•μ˜μ˜ 강사가 μ•„λ‹Œμ§€λ₯Ό μ •ν™•νžˆ 확인 ν•  수 μ—†μ—ˆμŠ΅λ‹ˆλ‹€.
    λ”°λΌμ„œ λ¨Όμ € κ°•μ˜λ₯Ό id둜 μ°Ύκ³  순차적으둜 강사λ₯Ό κ²€μ¦ν•˜λŠ” λ°©λ²•μœΌλ‘œ κ΅¬ν˜„ν–ˆμŠ΅λ‹ˆλ‹€.

  • Course table κ°•μ˜ μ†Œκ°œ Data type - VARCHAR(65535) or TEXT?

    κ°•μ˜ μ†Œκ°œλŠ” 길이가 클수 μžˆμ–΄ ν•΄λ‹Ή 데이터 νƒ€μž…μ„ μ–΄λ–€ κ²ƒμœΌλ‘œ 섀정할지 κ³ λ―Όν•˜λ©° 이 λ‘κ°œμ˜ 차이λ₯Ό μ•Œμ•„λ³΄μ•˜μŠ΅λ‹ˆλ‹€.
    λ ˆμ½”λ“œμ—λŠ” μ΅œλŒ€ μ‚¬μ΄μ¦ˆκ°€ μ„€μ •λ˜μ–΄ 있고, VARCHARλŠ” ν…Œμ΄λΈ”μ˜ λ‹€λ₯Έ μΉΌλŸΌλ“€μ΄ μ‚¬μš©ν•  수 μžˆλŠ” μ΅œλŒ€ κ³΅κ°„μ˜ 크기에 영ν–₯을 쀄 수 μžˆμŠ΅λ‹ˆλ‹€.
    ν•˜μ§€λ§Œ TEXTλŠ” νŠΉμ • μž„κ³„μΉ˜κ°€ λ„˜μœΌλ©΄ InnoDB의 Off-page 방식을 톡해 λ ˆμ½”λ“œμ™€ λ³„λ„μ˜ 곡간에 μ €μž₯λ˜μ–΄ λ ˆμ½”λ“œμ—λŠ” 9~12 λ°”μ΄νŠΈ 밖에 μ°¨μ§€ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.
    λ”°λΌμ„œ μΆ”ν›„ ν™•μž₯성을 μƒκ°ν•˜μ—¬ TEXTλ₯Ό μ‚¬μš©ν•˜κΈ°λ‘œ κ²°μ •ν–ˆμŠ΅λ‹ˆλ‹€.
    https://dev.mysql.com/doc/refman/8.0/en/column-count-limit.html#row-size-limits

  • κ°•μ˜ λͺ©λ‘ 쑰회 - Covering Index 적용

    ν•΄λ‹Ή μ„œλΉ„μŠ€μ˜ νŠΉμ„±μƒ, λ³΄ν†΅μ˜ μ„œλΉ„μŠ€κ°€ 그렇듯이 μ“°κΈ°λ³΄λ‹€λŠ” 읽기가 주둜 μ΄λ£¨μ–΄μ§ˆ 것이라고 μ˜ˆμƒμ΄ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
    λ˜ν•œ 여기에 νŽ˜μ΄μ§• κΈ°λŠ₯κΉŒμ§€ μžˆμ–΄ 검색 μ„±λŠ₯을 κ°œμ„ ν•˜λŠ” 방법을 μƒκ°ν•΄λ³΄μ•˜κ³ , 검색 μ‹œ 인덱슀λ₯Ό μ μš©ν•΄λ³΄κ³ μž ν–ˆμŠ΅λ‹ˆλ‹€.
    λ¨Όμ € No offset을 μƒκ°ν•΄λ³΄μ•˜μŠ΅λ‹ˆλ‹€. ν•˜μ§€λ§Œ μ΄λŠ” λ¬΄ν•œ μŠ€ν¬λ‘€μ— μ ν•©ν•˜κ³ , 순차적으둜 λ‹€μŒνŽ˜μ΄μ§€ μ΄λ™λ§Œ κ°€λŠ₯ν•˜μ—¬ 일반적인 νŽ˜μ΄μ§• λ²„νŠΌμ€ μ‚¬μš©ν•˜μ§€ λͺ»ν•œλ‹€λŠ” 점이 μžˆμ—ˆμŠ΅λ‹ˆλ‹€.
    ν”„λ‘œμ νŠΈμ˜ μ„œλΉ„μŠ€λ₯Ό 생각 ν–ˆμ„λ•Œ, μ΄λŠ” μ ν•©ν•˜μ§€ μ•Šλ‹€κ³  μƒκ°ν•˜μ˜€μŠ΅λ‹ˆλ‹€.
    κΈ°μ‘΄ νŽ˜μ΄μ§• κΈ°λŠ₯을 μœ μ§€ν•˜λ©΄μ„œ 검색 μ„±λŠ₯을 κ°œμ„ ν•˜κΈ° μœ„ν•΄ 컀버링 인덱슀λ₯Ό μ μš©ν•˜κ³ μž ν–ˆμŠ΅λ‹ˆλ‹€. 즉 select, where, order by, group by λ“± μ‚¬μš©λ˜λŠ” λͺ¨λ“  칼럼이 index μΉΌλŸΌμ— ν¬ν•¨λ˜λ„λ‘ ν–ˆμŠ΅λ‹ˆλ‹€.

  • κ°•μ˜ λͺ©λ‘ 쑰회 - Covering Index, using temporary, using filesort ν•΄κ²°

    인덱슀λ₯Ό μƒμ„±ν•˜κ³  EXPLAIN으둜 ν™•μΈν•œ κ²°κ³Ό, extra에 using temporary, using filesortκ°€ 좜λ ₯λ˜μ—ˆκ³  컀버링 μΈλ±μŠ€κ°€ μ œλŒ€λ‘œ μž‘λ™ν•˜μ§€ μ•ŠλŠ”λ‹€λŠ” 것을 ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€.
    초기 λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ κ°•μ˜ ν…Œμ΄λΈ”μ—λŠ” ν•™μƒμˆ˜κ°€ μžˆμ§€ μ•Šμ•˜κ³ , count(cl.id) as student_id둜 인해 이런 ν˜„μƒμ΄ μžˆλ‹€λŠ” 것을 μ•Œκ²Œ λ˜μ—ˆμŠ΅λ‹ˆλ‹€.
    이λ₯Ό ν•΄κ²°ν•˜κΈ° μœ„ν•΄ μ œκ°€ μ²˜μŒμ— μ„€κ³„ν–ˆλ˜ λ°μ΄ν„°λ² μ΄μŠ€μ—μ„œ κ°•μ˜κ°€ ν•™μƒμˆ˜λ₯Ό class(κ°•μ˜-μˆ˜κ°•μƒ mapping table)의 갯수둜 μ„ΈλŠ” κ²ƒμ—μ„œ 칼럼으둜 가지도둝 λ³€κ²½ν–ˆμŠ΅λ‹ˆλ‹€.
    κ·Έ ν›„ 이에 ν•„μš”ν•œ 인덱슀λ₯Ό μƒμ„±ν•˜κ³ , EXPLAIN을 톡해 'Using Index'둜 컀버링 μΈλ±μŠ€κ°€ 잘 μˆ˜ν–‰λ˜λŠ” 것을 ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€.

  • κ°•μ˜ λͺ©λ‘ 쑰회 - Covering Index, Backward Index Scan

    μ΅œμ‹ μˆœ, μˆ˜κ°•μƒμˆ˜ 정렬을 ν•˜λŠ” 컀버링 인덱슀λ₯Ό μœ„ν•΄ (group by, order by μˆœμ„œ λ•Œλ¬Έμ—) λ‚ μ§œκ°€ 맨 μ•žμ— μžˆλŠ” μΈλ±μŠ€μ™€, μˆ˜κ°•μƒ μˆ˜κ°€ 맨 μ•žμ— 인덱슀λ₯Ό λ§Œλ“€μ–΄ λ†“μ•˜μŠ΅λ‹ˆλ‹€.
    κ·ΈλŸ¬λ‚˜ μ΅œμ‹ μˆœμ΄λ‚˜ μˆ˜κ°•μƒμˆ˜μ™€ λ°˜λŒ€λ‘œ μΈλ±μŠ€λŠ” μ˜€λ¦„μ°¨μˆœμœΌλ‘œ μ •λ ¬λ˜μ–΄ backward index scan이 λ°œμƒν•˜λŠ” 것을 ν™•μΈν–ˆμŠ΅λ‹ˆλ‹€.
    ν•˜μ§€λ§Œ InnoDBμ—μ„œλŠ” νŽ˜μ΄μ§€ 잠금이 forward index에 μ ν•©ν•œ ꡬ쑰이고, Double linked list둜 μ—°κ²°λœ B-Tree의 리프 νŽ˜μ΄μ§€ κ΅¬μ‘°μ™€λŠ” 달리 νŽ˜μ΄μ§€ λ‚΄μ—μ„œ 인덱슀 λ ˆμ½”λ“œκ°€ 단방ν–₯으둜만 μ—°κ²°λ˜μ–΄ backward index scan이 forward index scan에 λΉ„ν•΄ 느릴 수 밖에 μ—†μŠ΅λ‹ˆλ‹€.
    Forward Index scan을 μœ λ„ν•˜κΈ° μœ„ν•΄μ„œ 각각 맨 μ•žμ˜ 인덱슀λ₯Ό Descending Index둜 κ΅¬μ„±ν–ˆμŠ΅λ‹ˆλ‹€.
    > INDEX idx_covering_course_create_order (create_date DESC, title, instructor_id, price, category, student_count, is_public),
    > INDEX idx_covering_course_student_count_order (student_count DESC, create_date, title, instructor_id, price, category, is_public)

  • ν…ŒμŠ€νŠΈ μ½”λ“œ μž‘μ„±

    ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•¨μœΌλ‘œμ¨ μž…λ ₯값듀을 κ²€μ¦ν•˜κ³ , 예기치 λͺ»ν•œ μž‘λ™μ„ 미리 방지할 수 μžˆμ–΄ μ΄λŠ” ν•„μˆ˜λΌκ³  μƒκ°ν•©λ‹ˆλ‹€.
    특히 μ €μ—κ²ŒλŠ” 이λ₯Ό ν¬ν•¨ν•˜μ—¬, λ‹€μŒ 두가지가 ν…ŒμŠ€νŠΈ μ½”λ“œλ₯Ό μž‘μ„±ν•˜κ²Œ λ˜λŠ” 원동λ ₯이 λ©λ‹ˆλ‹€.
    μ²«λ²ˆμ§ΈλŠ” λ¦¬νŒ©ν† λ§μ„ 보닀 더 μ•ˆμ „ν•˜κ³  ν™•μ‹€ν•˜κ²Œ κ°€λŠ₯ν•˜λ„λ‘ ν•œλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€. ν…ŒμŠ€νŠΈ μ½”λ“œλŠ” κ΅¬ν˜„ 과정이 μ•„λ‹Œ κ²°κ³Όλ₯Ό κ²€μ¦ν•˜λŠ” 것이기 λ•Œλ¬Έμ—, μ½”λ“œλ₯Ό λ¦¬νŒ©ν† λ§ 해도 κ²°κ³Όκ°€ 달라지지 μ•ŠλŠ” 이상 항상 ν†΅κ³Όν•΄μ•Όλ§Œ ν•©λ‹ˆλ‹€. λ”°λΌμ„œ λ¦¬νŒ©ν† λ§ ν›„ κΈ°μ‘΄ ν…ŒμŠ€νŠΈ μ½”λ“œκ°€ ν†΅κ³Όν•˜λ©΄ 잘 μ΄λ£¨μ–΄μ‘Œλ‹€λŠ” 것을 λ°”λ‘œ μ•Œ 수 μžˆμŠ΅λ‹ˆλ‹€.
    λ‘λ²ˆμ§ΈλŠ” μ½”λ“œλ₯Ό μƒˆλ‘œ λ³΄λŠ” μ‚¬λžŒλ“€μ˜ κ°€μ΄λ“œκ°€ 될 수 μžˆλ‹€λŠ” κ²ƒμž…λ‹ˆλ‹€. μ›λž˜μ˜ μ½”λ“œλ§Œ λ³΄μ•„μ„œλŠ” μ–΄λ–€ ν˜•μ‹μ˜ 데이터가 λ“€μ–΄κ°€κ³  λ°˜ν™˜μ΄ λ˜μ•Όν•˜λŠ”μ§€ ν•œλˆˆμ— 보기 μ–΄λ ΅μ§€λ§Œ, ν…ŒμŠ€νŠΈ μ½”λ“œμ—μ„œλŠ” μ˜ˆμ‹œλ₯Ό 톡해(given, then) λͺ…ν™•ν•˜κ²Œ μ–΄λ–€ 데이터가 λ“€μ–΄κ°€κ³  λ°˜ν™˜λ˜λŠ”μ§€ λͺ…μ‹œν•˜κΈ° λ•Œλ¬Έμ— λ§Žμ€ 도움이 λœλ‹€κ³  μƒκ°ν•©λ‹ˆλ‹€.
    κ·Έ κ²°κ³Ό 이번 κ³Όμ œμ—μ„œ μ„œλ²„μ˜ 전체적인 λ™μž‘μ„ 확인할 수 μžˆλŠ” 톡합 ν…ŒμŠ€νŠΈμ™€, λΉ„μ¦ˆλ‹ˆμŠ€ 둜직만 λΉ λ₯΄κ²Œ 검증할 수 μžˆλŠ” λ‹¨μœ„ ν…ŒμŠ€νŠΈλ₯Ό μ΅œλŒ€ν•œ μž‘μ„±ν–ˆμŠ΅λ‹ˆλ‹€.
    > Test

ERD

ERD

API

κ°•μ˜ λͺ©λ‘ 쑰회

http://localhost:3000/courses/search?type={}&keyword={}&category={}&pageNumber={}&pageSize{}&sort={}

URL query stringλ₯Ό 톡해 검색어 일치 / 포함 ν•˜λŠ” 곡개 μƒνƒœμΈ κ°•μ˜λ“€μ„ μ‘°νšŒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

(강사λͺ… / κ°•μ˜λͺ…) λ˜λŠ” (μˆ˜κ°•μƒ id)λ₯Ό κ²€μƒ‰μ–΄λ‘œν•΄μ„œ 검색이 κ°€λŠ₯ν•©λ‹ˆλ‹€.
κ°•μ˜ μΉ΄ν…Œκ³ λ¦¬λŠ” all(전체 μΉ΄ν…Œκ³ λ¦¬), web, app, game, algorithm, infra, database λ₯Ό 검색 쑰건으둜 μ‚¬μš©ν•©λ‹ˆλ‹€.
검색 κ²°κ³ΌλŠ” μ΅œμ‹ μˆœ / μˆ˜κ°•μƒμˆ˜λ‘œ μ •λ ¬ λ˜λ„λ‘ 쑰건을 μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
νŽ˜μ΄μ§•μ΄ κ°€λŠ₯ν•˜μ—¬ μ›ν•˜λŠ” νŽ˜μ΄μ§€ μ‹œμž‘κ³Ό 크기λ₯Ό μ„€μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.
λͺ¨λ“  query string은 μž…λ ₯ν•΄ μ£Όμ–΄μ•Ό ν•˜λ©°, ν˜•μ‹μ΄ λ§žμ§€ μ•Šμ„ 경우 μ—λŸ¬λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

EX) <GET> http://localhost:3000/courses/search?type=instructorAndTitle&keyword=Node&category=web&pageNumber=1&pageSize=10&sort=recent

μš”μ²­ (GET)

  • 검색 쑰건 - type (string)

    • 강사λͺ… / κ°•μ˜λͺ…μœΌλ‘œ 검색: instructorAndTitle
    • μˆ˜κ°•μƒ id 검색: studentId
  • 검색 단어 - keyword (string) or (integer)

    • 쑰건에 λ”°λ₯Έ 검색어
  • μΉ΄ν…Œκ³ λ¦¬ - category (string)

    • 전체: all
    • μ›Ή: web
    • μ•±: app
    • κ²Œμž„: game
    • μ•Œκ³ λ¦¬μ¦˜: algorithm
    • 인프라: infra
    • λ°μ΄ν„°λ² μ΄μŠ€: database
  • νŽ˜μ΄μ§€ 번호 - pageNumber (integer)

    • νŽ˜μ΄μ§€ 크기에 따라 λͺ‡ 번째 νŽ˜μ΄μ§€λ₯Ό λ‚˜νƒ€λ‚΄λŠ”μ§€
  • νŽ˜μ΄μ§€ 크기 - pageSize (integer)

    • 검색 μ‹œ λͺ©λ‘μ˜ 크기
  • μ •λ ¬ 쑰건 - sort (string)

    • μ΅œμ‹ μˆœ: recent
    • μˆ˜κ°•μƒμˆœ: student-count

응닡

{
  "courses": [
      {
          "id": 3,
          "category": "μ›Ή",
          "title": "NodeJs와 Express",
          "instructorName": "κΉ€μ˜λͺ…",
          "price": 13000,
          "studentCount": 0,
          "publishedOn": "2024-09-01 15:59:32"
      },
      {
          "id": 1,
          "category": "μ›Ή",
          "title": "이것이 NodeJsλ‹€",
          "instructorName": "ν–₯둜",
          "price": 65000,
          "studentCount": 2,
          "publishedOn": "2024-09-01 15:56:39"
      }
      ...
  ]
}


κ°•μ˜ 상세 쑰회

http://localhost:3000/courses/{}

Path parameter둜 κ°•μ˜ idλ₯Ό μž…λ ₯ν•˜μ—¬ 곡개 / λΉ„κ³΅κ°œ κ°•μ˜λ₯Ό 상세 μ‘°νšŒν•  수 μžˆμŠ΅λ‹ˆλ‹€.

Path parameter의 ν˜•μ‹μ΄ λ§žμ§€ μ•ŠμœΌλ©΄ μ—λŸ¬λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

EX) <GET> http://localhost:3000/courses/2

μš”μ²­ (GET)

  • κ°•μ˜ id - (integer)

응닡

{
    "title": "이것이 NodeJsλ‹€",
    "description": "κ°•μ˜ μ†Œκ°œλž€ μž…λ‹ˆλ‹€.",
    "category": "μ›Ή",
    "price": 65000,
    "studentCount": 2,
    "students": [
        {
            "nickname": "aaa",
            "appliedOn": "2024-09-01 15:57:27"
        },
        {
            "nickname": "spring",
            "appliedOn": "2024-09-01 16:11:47"
        }
    ],
    "publishedOn": "2024-09-01 15:56:39",
    "updatedOn": "2024-09-01 16:11:47"
}

κ°•μ˜ 등둝

http://localhost:3000/courses

κ°•μ˜λ₯Ό λ“±λ‘ν•˜λ©° 처음 μƒμ„±μ‹œ μžλ™μœΌλ‘œ λΉ„κ³΅κ°œ μƒνƒœλ‘œ μ €μž₯λ©λ‹ˆλ‹€.

μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 강사, μ€‘λ³΅λœ κ°•μ˜λͺ…, ν˜•μ‹μ— λ§žμ§€ μ•ŠλŠ” 값을 μž…λ ₯ν•˜λŠ” 경우 μ—λŸ¬λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

EX) <POST> http://localhost:3000/courses

μš”μ²­ (POST)

{
    "instructorId": 2,
    "title": "NodeJs와 Express",
    "description": "μ΅μŠ€ν”„λ ˆμŠ€λ₯Ό μ μš©ν•˜μž",
    "price": 13000,
    "category": "μ›Ή"
}

  • 강사 id - instructorId (integer)
  • 제λͺ© - title (string)
  • κ°•μ˜ μ†Œκ°œ - description (string)
  • 가격 - price (integer)
  • μΉ΄ν…Œκ³ λ¦¬ - category (string)

응닡

{
    "insertedCourseId": 3
}
  • λ“±λ‘λœ κ°•μ˜ id - insertedCourseId

κ°•μ˜ λŒ€λŸ‰ 등둝

http://localhost:3000/courses/bulk

κ°•μ˜ 등둝과 λ™μΌν•˜μ§€λ§Œ ν•˜λ‚˜μ˜ 강사 id둜 1개 이상 μ΅œλŒ€ 10κ°œκΉŒμ§€ κ°•μ˜λ₯Ό λ™μ‹œμ— 등둝할 수 μžˆμŠ΅λ‹ˆλ‹€.

λͺ¨λ“  강사 idκ°€ λ™μΌν•˜μ§€ μ•Šκ±°λ‚˜, μ€‘λ³΅λœ κ°•μ˜λͺ…, μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” 강사, ν˜•μ‹μ— λ§žμ§€ μ•ŠλŠ” 값을 μž…λ ₯ν•˜λŠ” 경우 μ—λŸ¬λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

EX) <POST> http://localhost:3000/courses/bulk

μš”μ²­ (POST)

{
    "courses": [
        {
            "instructorId": 7,
            "title": "CS",
            "description": "Computer Science",
            "price": 6700,
            "category": "인프라"
        },
        {
            "instructorId": 7,
            "title": "Redisλž€",
            "description": "μΊμ‹œκ°€ μ—†μ—ˆλ‹€λ©΄",
            "price": 8000,
            "category": "λ°μ΄ν„°λ² μ΄μŠ€"
        }
    ]
}

응닡

{
    "insertedCourseIds": [
        4,
        5
    ]
}

κ°•μ˜ μˆ˜μ •

http://localhost:3000/courses/{}

ν•΄λ‹Ή 강사가 κ°•μ˜λ₯Ό μˆ˜μ •ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” κ°•μ˜, μΌμΉ˜ν•˜μ§€ μ•ŠλŠ” 강사, μ€‘λ³΅λœ κ°•μ˜λͺ…, ν˜•μ‹μ— λ§žμ§€ μ•ŠλŠ” 값을 μž…λ ₯ν•˜λŠ” 경우 μ—λŸ¬λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

EX) <PUT> http://localhost:3000/courses/2

μš”μ²­ (PUT)

  • κ°•μ˜ id - (integer)
{
    "instructorId": 2,
    "title": "μƒˆλ‘œμš΄ κ°•μ˜λͺ…",
    "description": "바뀐 μ†Œκ°œμž…λ‹ˆλ‹€.",
    "price": 12000
}

응닡

{
    "updatedCourseId": 2
}
  • μˆ˜μ •λœ κ°•μ˜ id - updatedCourseId

κ°•μ˜ μ˜€ν”ˆ

http://localhost:3000/courses/open/{}

Path parameter둜 κ°•μ˜ idλ₯Ό μž…λ ₯ν•˜μ—¬ ν•΄λ‹Ή 강사가 κ°•μ˜λ₯Ό μ˜€ν”ˆν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” κ°•μ˜, μΌμΉ˜ν•˜μ§€ μ•ŠλŠ” 강사, 이미 μ˜€ν”ˆλœ κ°•μ˜, ν˜•μ‹μ— λ§žμ§€ μ•ŠλŠ” 값을 μž…λ ₯ν•˜λŠ” 경우 μ—λŸ¬λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

EX) <PUT> http://localhost:3000/courses/open/3

μš”μ²­ (PUT)

  • κ°•μ˜ id - (integer)
{
    "instructorId": 2
}

응닡

{
    "openedCourseId": 3
}
  • μ˜€ν”ˆλœ κ°•μ˜ id - openedCourseId

κ°•μ˜ μ‚­μ œ

http://localhost:3000/courses/{}

Path parameter둜 κ°•μ˜ idλ₯Ό μž…λ ₯ν•˜μ—¬ ν•΄λ‹Ή 강사가 κ°•μ˜λ₯Ό μ‚­μ œν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” κ°•μ˜, μΌμΉ˜ν•˜μ§€ μ•ŠλŠ” 강사, μˆ˜κ°•μƒμ΄ μžˆλŠ” κ°•μ˜, ν˜•μ‹μ— λ§žμ§€ μ•ŠλŠ” 값을 μž…λ ₯ν•˜λŠ” 경우 μ—λŸ¬λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

EX) <DELETE> http://localhost:3000/courses/5

μš”μ²­ (DELETE)

  • κ°•μ˜ id - (integer)
{
    "instructorId": 7
}

응닡

{
    "deletedCourseId": 5
}
  • μ‚­μ œλœ κ°•μ˜ id - deletedCourseId

μˆ˜κ°•μƒ (νšŒμ›) κ°€μž…

http://localhost:3000/students

이메일과 λ‹‰λ„€μž„μœΌλ‘œ μˆ˜κ°•μƒ νšŒμ› κ°€μž…μ„ ν•©λ‹ˆλ‹€.

μ€‘λ³΅λœ 이메일, ν˜•μ‹μ— λ§žμ§€ μ•ŠλŠ” 값을 μž…λ ₯ν•˜λŠ” 경우 μ—λŸ¬λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

EX) <POST> http://localhost:3000/students

μš”μ²­ (POST)

{
    "email": "[email protected]",
    "nickname": "spring"
}

응닡

{
    "signUpId": 4
}
  • κ°€μž…λœ μˆ˜κ°•μƒ id - signUpId

μˆ˜κ°•μƒ (νšŒμ›) νƒˆν‡΄

http://localhost:3000/students/{}

Path parameter둜 μˆ˜κ°•μƒ idλ₯Ό μž…λ ₯ν•˜μ—¬ νƒˆν‡΄λ₯Ό ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

νƒˆν‡΄ λ™μ‹œμ— μˆ˜κ°•μƒμ˜ μˆ˜κ°• 내역이 μ‚­μ œλ˜λ©° κ·Έ 이메일은 λ‹€λ₯Έ μ‚¬μš©μžκ°€ μž¬μ‚¬μš©μ΄ κ°€λŠ₯ν•©λ‹ˆλ‹€.

μ‘΄μž¬ν•˜μ§€ μ•ŠλŠ” μˆ˜κ°•μƒ, ν˜•μ‹μ— λ§žμ§€ μ•ŠλŠ” 값을 μž…λ ₯ν•˜λŠ” 경우 μ—λŸ¬λ₯Ό λ°˜ν™˜ν•©λ‹ˆλ‹€.

EX) <DELETE> http://localhost:3000/students/4

μš”μ²­ (DELETE)

  • μˆ˜κ°•μƒ id - (integer)

응닡

{
    "removedStudentId": 4
}
  • νƒˆν‡΄λœ μˆ˜κ°•μƒ id - removedStudentId

κ°•μ˜ μˆ˜κ°• μ‹ μ²­

http://localhost:3000/students/apply-class

κ°€μž…λœ μˆ˜κ°•μƒμœΌλ‘œ 1~N 개의 κ°•μ˜ id듀을 톡해 곡개된 κ°•μ˜λ₯Ό λ™μ‹œμ— μˆ˜κ°•μ‹ μ²­ν•  수 μžˆμŠ΅λ‹ˆλ‹€.

μš”μ²­μ— ν¬ν•¨λœ κ°•μ˜λ“€μ€ 각각 신청을 μ„±κ³΅ν•˜κ±°λ‚˜ μ‹€νŒ¨ μ‹œ μ‘λ‹΅μœΌλ‘œ 이유λ₯Ό μ•Œλ €μ€λ‹ˆλ‹€.

EX) <POST> http://localhost:3000/students/apply-class

μš”μ²­ (POST)

{
    "studentId": 7,
    "courseIds": [1, 2, 3, 4, 5, 6, 7]
}

응닡

{
    "createdClassIds": [
        3,
        4,
        5
    ],
    "alreadyAppliedCourseIds": [],
    "appliedCourseIds": [
        3,
        2,
        1
    ],
    "noExistCourseIds": [
        5,
        6
    ],
    "noPublicCourseIds": [
        7,
        4
    ]
}
  • μ‹ μ²­ μ„±κ³΅ν•˜μ—¬ μƒμ„±λœ μˆ˜κ°• λ‚΄μ—­ id 리슀트- 'createdClassIds'
  • 이미 μˆ˜κ°•μ€‘μΈ κ°•μ˜ id 리슀트- 'alreadyAppliedCourseIds'
  • μ‹ μ²­ μ„ κ³΅ν•œ κ°•μ˜ id 리슀트 - 'appliedCourseIds'
  • μ‘΄μž¬ν•˜μ§€ μ•Šμ•„ μ‹ μ²­ν•˜μ§€ λͺ»ν•œ κ°•μ˜ id 리슀트- 'noExistCourseIds'
  • λΉ„κ³΅κ°œ μƒνƒœλ‘œ μ‹ μ²­ν•˜μ§€ λͺ»ν•œ κ°•μ˜ id 리슀트- 'createdClassIds'

About

No description, website, or topics provided.

Resources

Stars

Watchers

Forks

Releases

No releases published

Packages

No packages published