๐Ÿ› ๏ธ ์ฃผ์š” ์ž‘์—…

  • ์ปค์Šคํ…€ ์—๋Ÿฌ๋กœ ์„œ๋ฒ„ ์—๋Ÿฌ ์ฒ˜๋ฆฌ โœ… 2024-09-10
  • ๊ธฐ์กด ๋ชจ๋ธ, ๋ ˆํฌ์ง€ํ† ๋ฆฌ, ์ปจํŠธ๋กค๋Ÿฌ ๊ธฐ๋Šฅ์„ ๋‹ค์‹œ๊ธˆ ํ™•์ธํ•˜๊ณ  ๊ธฐ๋Šฅ์— ๋งž๊ฒŒ ๋ฆฌํŒฉํ† ๋ง โœ… 2024-09-10
  • ์˜ต์ €๋ฒ„ ํŒจํ„ด์œผ๋กœ ์ƒํƒœ๊ด€๋ฆฌ ์„ค๊ณ„

๐Ÿ“š ํ•™์Šต ํ‚ค์›Œ๋“œ

  • ์ปค์Šคํ…€ ์—๋Ÿฌ ์ •๋ฆฌโœ… 2024-09-10
  • ์ƒํƒœ๊ด€๋ฆฌ โœ… 2024-09-10
  • ์˜ต์ €๋ฒ„ ํŒจํ„ด โœ… 2024-09-10

๊ณ ๋ฏผ ๋ฐ ํ•ด๊ฒฐ๊ณผ์ •

Connection Pool๊ณผ ๋น„๋™๊ธฐ ๋ณ‘๋ ฌ ์ˆ˜ํ–‰

Connection Pool์˜ ๊ฒฝ์šฐ db์— ์—ฐ๊ฒฐ๋œ Connection์„ ๋ฏธ๋ฆฌ ๋งŒ๋“ค์–ด๋‘๊ณ  Pool์— ๋ณด๊ด€ํ•˜์˜€๋‹ค๊ฐ€ ํ•„์š”ํ•  ๋•Œ Connection์„ ๊ฐ€์ ธ๋‹ค๊ฐ€ ์‚ฌ์šฉํ•œ ํ›„, ๋‹ค์‹œ Pool์— ๋ฐ˜ํ™˜ํ•˜๋Š” ๊ธฐ๋ฒ•์ด๋‹ค.

์ด๋Ÿฌํ•œ Connection Pool์„ ์‚ฌ์šฉํ•œ ๋ฐฉ์‹์—์„œ ํ•œ ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์—ฌ๋Ÿฌ๋ฒˆ db์— ์ ‘๊ทผํ•  ๋•Œ Promise.all ์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์€ ์ด์œ ์— ๋Œ€ํ•œ ์งˆ๋ฌธ์ด ๋‚˜์™”๋‹ค.

  async function getCard(username) {
    try {
      const cards = await cardModel.getCard(username);
      const columns = await columnModel.getColumn(username);
      const data = columns.map((col) => {
        return {
          classify: col.classify,
          cards: cards.filter((card) => card.task_column === col.classify),
        };
      });
      return data;
    } catch (error) {
      // ๋‚˜์ค‘์— ์ปค์Šคํ…€ ์—๋Ÿฌ๋กœ ๊ณ ์น˜๊ธฐ
      throw new Error("์—๋Ÿฌ");
    }
  }

์ด๋Ÿฌํ•œ Promise.all์˜ ๊ฒฝ์šฐ ๋ชจ๋“  ๋น„๋™๊ธฐ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•ด์„œ ์ˆ˜ํ–‰์„ ๋ณ‘๋ ฌ์ ์œผ๋กœ ์‹คํ–‰ํ•œ๋‹ค๋Š” ์žฅ์ ์ด ์žˆ์—ˆ๋‹ค. ํ•˜์ง€๋งŒ ์ด๋ฅผ mysql์—์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์„๊นŒ์— ๋Œ€ํ•œ ์ œ๋Œ€๋กœ ๋œ ์ง€์‹์ด ๋ถ€์กฑํ•˜์—ฌ ์ œ๋Œ€๋กœ ๋Œ€๋‹ตํ•˜์ง€ ๋ชปํ–ˆ๋‹ค. ์ฐพ์•„๋ณธ ๊ฒฐ๊ณผ Mysql์€ ๋ฉ€ํ‹ฐ์Šค๋ ˆ๋“œ ๊ธฐ๋ฐ˜์ด๊ธฐ ๋•Œ๋ฌธ์— connection ์ž์ฒด๊ฐ€ ์ด๋Ÿฌํ•œ ๋ณ‘๋ ฌ ์ฒ˜๋ฆฌ์— ๋Œ€ํ•ด์„œ connection pool์„ ๋‘๊ณ  ๋ณ‘๋ ฌ์ ์œผ๋กœ ์ฒ˜๋ฆฌํ•˜๋Š” ํ”„๋กœ๋ฏธ์Šค ์—ฌ๋Ÿฌ๊ฐœ์ผ ๊ฒฝ์šฐ๋ฅผ ๊ฐ๊ฐ์˜ connection์ด ๋‘๊ณ  ์ฒ˜๋ฆฌํ•˜๋Š” ๋ฐฉ์‹์ด์—ˆ๊ธฐ ๋•Œ๋ฌธ์— Promise.all์„ ์‚ฌ์šฉํ•ด๋„ ์ƒ๊ด€ ์—†์„ ๋ฟ๋”๋Ÿฌ ์ถ”๊ฐ€์ ์œผ๋กœ ์„ฑ๋Šฅ ํ–ฅ์ƒ๊นŒ์ง€ ๊ฐ€๋Šฅํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ๋ฐ”๊ฟ”์ฃผ์—ˆ๋‹ค.

export default (function () {
  async function getCard(username) {
    try {
      const [cards, columns] = await Promise.all([
        cardModel.getCard(username),
        columnModel.getColumn(username),
      ]);
      const data = columns.map((col) => {
        return {
          classify: col.classify,
          cards: cards.filter((card) => card.task_column === col.classify),
        };
      });
      return data;
    } catch (error) {
      // ๋‚˜์ค‘์— ์ปค์Šคํ…€ ์—๋Ÿฌ๋กœ ๊ณ ์น˜๊ธฐ
      throw new Error("์—๋Ÿฌ");
    }
  }

์ปค์Šคํ…€ ์—๋Ÿฌ ์‚ฌ์šฉ๊ณผ ์—๋Ÿฌ ๋˜์ง€๊ณ  ๋ฐ›๋Š” ์œ„์น˜ ์„ ์ •

์ด๋ฒˆ์— ์ปค์Šคํ…€ ์—๋Ÿฌ๋ฅผ ์‚ฌ์šฉํ•จ์œผ๋กœ์จ ์–ด๋””์—์„œ ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒ์‹œํ‚ค๊ณ , ์–ด๋””์„œ ๋ฐ›์•„์•ผ ํ• ์ง€์— ๋Œ€ํ•œ ๊ณ ๋ฏผ์ด ๋งŽ์•˜๋‹ค.

๊ธฐ์กด์˜ ๊ฒฝ์šฐ ๋ฐ์ดํ„ฐ๋ฅผ fetch ํ•ด์˜ค๋Š” ๋ชจ๋“  ๊ฒฝ์šฐ(db์—ฐ๊ฒฐ, ๋ ˆํฌ์ง€ํ† ๋ฆฌ ๋“ฑ)์— ๋Œ€ํ•ด์„œ try-catch๋ฌธ์„ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ ์ •์ž‘ ์ด๋Ÿฌํ•œ catch๋ฌธ์—์„œ console๋กœ ์ถœ๋ ฅ๋งŒ ์‹œํ‚ฌ ๋ฟ ์ด์— ๋Œ€ํ•œ ์—๋Ÿฌ์ฒ˜๋ฆฌ๊ฐ€ ๋˜์–ด์žˆ์ง€ ์•Š์•„ ํ•ด๋‹น ํ”„๋กœ๋ฏธ์Šค๋ฅผ ํ˜ธ์ถœํ•˜๋Š” ํ•จ์ˆ˜ ๋‚ด์—์„œ๋Š” ์ œ๋Œ€๋กœ catchํ•˜์ง€ ๋ชปํ•˜๋Š” ๋ฌธ์ œ๊ฐ€ ๋ฐœ์ƒํ–ˆ๋‹ค. ๋”ฐ๋ผ์„œ ์ด๋Ÿฌํ•œ ์—๋Ÿฌ๋ฅผ ์–ธ์ œ ๋˜์ง€๊ณ  ์–ด๋””์—์„œ ์ด๋Ÿฌํ•œ ์—๋Ÿฌ๋ฅผ ๋ฐ›๋Š”์ง€๋ฅผ ๋‹ค์‹œ๊ธˆ ์ƒ๊ฐํ•ด์•ผ ํ•  ํ•„์š”์„ฑ์ด ๋Œ€๋‘๋˜์—ˆ๋‹ค.

๊ณ ๋ฏผํ•œ ๊ฒฐ๊ณผ db์ชฝ์—์„œ ๋Œ€๋ถ€๋ถ„ ์˜ค๋ฅ˜๊ฐ€ ๋‚˜๋Š” ํ˜„ ์ƒํ™ฉ์„ ๊ณ ๋ คํ•˜์—ฌ

  • ์ฟผ๋ฆฌ ์‹คํ–‰๋ฌธ
  • connection ์‹คํ–‰๋ฌธ ์„ ๊ธฐ์ค€์œผ๋กœ ์ปค์Šคํ…€ ์—๋Ÿฌ๋ฅผ ๋‚˜๋ˆ„์–ด ๋˜์ง€๋„๋ก ์„ค๊ณ„ํ–ˆ๋‹ค.
const getConnection = async () => {
  try {
    const connection = await sql.getConnection();
    return connection;
  } catch (error) {
    throw new DBConnectionError(error.message);
  }
};
 
export const transaction = async (logic, query) => {
  const connection = await getConnection();
  try {
    await connection.beginTransaction();
    const result = await logic(connection, query);
    await connection.commit();
    return result;
  } catch (error) {
    console.log("error: rollback:", error.message);
    await connection.rollback();
    throw new QueryError(error.message);
  } finally {
    console.log("release connection");
    connection.release();
  }
};

์ฟผ๋ฆฌ ์‹คํ–‰๋ฌธ์˜ ๊ฒฝ์šฐ transaction์—์„œ try-catch๋ฌธ์—์„œ ์ฟผ๋ฆฌ๋ฅผ ์‹คํ–‰ํ•˜๋Š” ์ค‘ ์˜ค๋ฅ˜๊ฐ€ ๋ฐœ์ƒํ•˜๋ฉด QueryError๋ฅผ ๋˜์ง์œผ๋กœ์จ query๋ฅผ ์‹คํ–‰ํ•˜๋Š” ๊ณผ์ •์—์„œ์˜ ์—๋Ÿฌ๋ฅผ ์žก์•„ ๋”ฐ๋กœ ๋˜์ ธ์ฃผ์—ˆ๊ณ , try-catch๋ฌธ ๋ฐ–์—์„œ ํ™œ์šฉํ•ด์•ผ ํ•˜๋Š” connection์˜ ๊ฒฝ์šฐ

  async function getCard(...args) {
    try {
      const data = await transaction(cardRepository.getCardByUsername, args);
      return data;
    } catch (error) {
      handleDBError(error);
    }
  }

๊ฐ ๋ชจ๋ธ์—์„œ ํŠธ๋žœ์žญ์…˜์„ ํ•˜๋Š” ์ผ๋ จ์˜ ๊ณผ์ •์—์„œ ์ƒ๊ธฐ๋Š” DBConnectionError์™€ QueryError๋ฅผ ๋ชจ๋‘ ์—ฌ๊ธฐ์„œ ์บ์น˜ํ•˜์—ฌ ํ•˜๋‚˜์˜ ์—๋Ÿฌ๋กœ ๊ฐ์‹ธ๋Š” ํ˜•ํƒœ๋กœ ๊ณ ์•ˆํ–ˆ๋‹ค.

export class DBConnectionError extends Error {
  constructor(message) {
    super(message);
    this.name = "DBConnectionError";
  }
}
export class QueryError extends Error {
  constructor(message) {
    super(message);
    this.name = "QueryError";
  }
}
 
export class DBError extends Error {
  constructor(message, cause) {
    super(message);
    this.status = 500;
    this.cause = cause;
    this.name = "DBError";
  }
}
 
export function handleDBError(err) {
  if (err instanceof QueryError) {
    throw new DBError("Server Error", err);
  } else if (err instanceof DBConnectionError) {
    throw new DBError("Server Error", err);
  } else {
    throw new DBError("Server Error", err);
  }
}
 

์ด๋ ‡๊ฒŒ ๊ฐ๊ฐ์˜ ๋ถ€๋ถ„์— ๋Œ€ํ•ด ์—ฐ๊ฒฐ์—์„œ ์ƒ๊ธฐ๋Š” ์˜ค๋ฅ˜์™€ ์ฟผ๋ฆฌ๋ฌธ์„ ์‹คํ–‰ํ•  ๋•Œ ์ƒ๊ธฐ๋Š” ์ปค์Šคํ…€ ์—๋Ÿฌ๋ฅผ ๋งŒ๋“ค๊ณ , ์ด ๋‘˜์„ ๋ชจ๋‘ ๋ฌถ์–ด ํ•˜๋‚˜์˜ ์—๋Ÿฌ๋กœ ๊ด€๋ฆฌํ•  ์ˆ˜ ์žˆ๋Š” DBError๋ฅผ ๋งŒ๋“ค์–ด ์ด๋ฅผ ๋˜์ ธ Controller์—์„œ ๋ฐ›์„ ์ˆ˜ ์žˆ๋„๋ก ํ•˜์˜€๋‹ค.

  async function getCard(username) {
    try {
      ...
    } catch (error) {
      throw error;
    }
  }

์ปจํŠธ๋กค๋Ÿฌ์—์„œ ๋ฐ›์€ ์—๋Ÿฌ์˜ ๊ฒฝ์šฐ ํ˜„์žฌ๊นŒ์ง€๋Š” DBError๋ฐ–์— ์—†์ง€๋งŒ, ๋‚˜์ค‘์— ValidationError์™€ ๊ฐ™์€ ์˜ˆ์™ธ๋“ค์„ ์žก์•„ ๋˜์งˆ ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์— ์ปจํŠธ๋กค๋Ÿฌ๋Š” ๊ฐ ์ข…๋ฅ˜์˜ ์—๋Ÿฌ๋ฅผ ๋ฐ›์•„ ๋‹ค์‹œ๊ธˆ router์—๊ฒŒ ๋˜์ง„๋‹ค.

router.get("/", isLoggedIn, async (req, res, next) => {
  try {
    let todoData = await cardController.getCard(req.user.username);
    res.status(200).json({ data: todoData, path: process.env.DIRNAME });
  } catch (error) {
    next(error);
  }
});

router์—์„œ ๋‹ค์‹œ๊ธˆ ์žก์€ ์—๋Ÿฌ๋Š” next๋ฅผ ํ†ตํ•ด ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด๋กœ ๋„˜์–ด๊ฐ€๊ฒŒ ํ•˜๋Š”๋ฐ, ๋‹ค์Œ ๋ฏธ๋“ค์›จ์–ด๋Š” ์—๋Ÿฌ ์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ฏธ๋“ค์›จ์–ด์ด๋‹ค.

export default function errorMiddleWare(app) {
  app.use((err, req, res, next) => {
    console.error(err);
    res.status(err.status).json(err.message);
  });
}

์—๋Ÿฌ ๋ฏธ๋“ค์›จ์–ด์˜ ๊ฒฝ์šฐ ์•„์ง๊นŒ์ง€๋Š” ๊ฐ„๋‹จํ•˜๊ฒŒ ๊ตฌํ˜„ํ•˜์˜€๋‹ค. error์˜ property๋กœ ์„ค์ •ํ•œ status์ฝ”๋“œ์™€ ์—๋Ÿฌ ๋ฉ”์„ธ์ง€๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋„๋ก ํ•˜์˜€๋‹ค.

๋ฆฌ๋ทฐ ์š”์ฒญ

์•ˆ๋…•ํ•˜์„ธ์š” ๋ฉ˜ํ† ๋‹˜. ๊ณ ์ƒ ๋งŽ์œผ์‹ญ๋‹ˆ๋‹ค. ์–ด์ œ์˜ค๋Š˜๋™์•ˆ ์ €๋Š” MVC ํŒจํ„ด์— ๋Œ€ํ•ด ๋‹ค์‹œ๊ธˆ ํ•™์Šตํ•˜๊ณ  ์ด๋ฅผ ์ง์ ‘ ํ”„๋กœ์ ํŠธ์— ์ ์šฉํ•ด๋ณด๋Š” ์‹œ๊ฐ„์„ ๊ฐ€์กŒ์Šต๋‹ˆ๋‹ค. ํ•˜์ง€๋งŒ ํ•ด๋‹น ํŒจํ„ด์„ ๊ณต๋ถ€ํ•˜๋ฉด์„œ๋„ ์•„์ง๊นŒ์ง€๋„ ์ด๋ฅผ ์ œ๋Œ€๋กœ ์„ค๊ณ„ํ–ˆ๋Š”์ง€ ํ™•์‹ ์ด ๋“ค์ง€ ์•Š์•„ ์ด๋Ÿฌํ•œ ๋ถ€๋ถ„์„ ๋ฆฌ๋ทฐ ์š”์ฒญ ๋“œ๋ฆฌ๋ ค ํ•ฉ๋‹ˆ๋‹ค.

์ œ๊ฐ€ ๋ฆฌ๋ทฐ ์š”์ฒญ๋“œ๋ฆฌ๋Š” ๋ถ€๋ถ„์€ ๋‹ค์Œ๊ณผ ๊ฐ™์Šต๋‹ˆ๋‹ค.

MVC ํŒจํ„ด์˜ ์ ์šฉ๊ณผ ์—๋Ÿฌ์ฒ˜๋ฆฌ

์ด๋ฒˆ์— ์„œ๋ฒ„์˜ ๊ตฌ์กฐ๋ฅผ ๋ฐ”๊พธ๋ฉด์„œ ๊ฐ ๋””๋ ‰ํ† ๋ฆฌ ๊ตฌ์กฐ ๋˜ํ•œ ์•„๋ž˜์™€ ๊ฐ™์ด ๋ฐ”๊พธ์—ˆ์Šต๋‹ˆ๋‹ค

  • Controller
    • ๋ผ์šฐํ„ฐ ๋””๋ ‰ํ† ๋ฆฌ
      • ๊ฐ api ์š”์ฒญ์— ๋Œ€ํ•œ ๋ผ์šฐํ„ฐ๋ฅผ ๋„๋ฉ”์ธ๋ณ„๋กœ ์„ค์ •
    • ๋„๋ฉ”์ธ๋ณ„ controller ํŒŒ์ผ
      • model์— ํ•ด๋‹น ๋ฐ์ดํ„ฐ๋ฅผ ์ œ์–ดํ•˜๋„๋ก ์ „๋‹ฌ
        • ๊ฐ ๋„๋ฉ”์ธ๋ณ„ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ˆ˜ํ–‰ํ•˜๊ณ , ์—ฌ๋Ÿฌ ๋„๋ฉ”์ธ๋ณ„ ๋น„์ฆˆ๋‹ˆ์Šค๊ฐ€ ๊ฒน์ณ์ ธ ์žˆ๋Š” ๊ฒฝ์šฐ ํ•ด๋‹น ์ปจํŠธ๋กค๋Ÿฌ์—์„œ ์ œ์–ดํ•˜์—ฌ ์•Œ๋งž์€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฆฌํ„ดํ•˜๋„๋ก ๋ณ€ํ™˜
  • Model
    • ๋„๋ฉ”์ธ๋ณ„ ๋ชจ๋ธํŒŒ์ผ์„ ๋‹ด์Œ
    • ๋ ˆํฌ์ง€ํ† ๋ฆฌ์— ์˜์กดํ•˜์—ฌ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๋Š” ๋‹จ์ผ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง์„ ์ˆ˜ํ–‰
    • ๋ ˆํฌ์ง€ํ† ๋ฆฌ ๋””๋ ‰ํ† ๋ฆฌ
      • db์— ์ง์ ‘์ ์œผ๋กœ ์ ‘๊ทผํ•˜์—ฌ ๋ฐ์ดํ„ฐ๋ฅผ ์ œ์–ดํ•จ
  • View
    • ๋ ˆ์ด์•„์›ƒ์„ ์ œ์™ธํ•˜๊ณ  ํด๋ผ์ด์–ธํŠธ ์ธก์—์„œ ๋ชจ๋‘ ์ฒ˜๋ฆฌ

๋”ฐ๋ผ์„œ ํ˜„์žฌ ๊ตฌ์กฐ๋Š” api ์š”์ฒญ โ†’ router โ†’ controller(ํ†ตํ•ฉ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง) โ†’ model(๊ฐœ๋ณ„ ๋น„์ฆˆ๋‹ˆ์Šค ๋กœ์ง) โ†’ repository ์ˆœ์œผ๋กœ ์ ‘๊ทผํ•˜์—ฌ ๋ฐ์ดํ„ฐ์— ์ ‘๊ทผํ•˜๊ณ  ์ œ์–ดํ•˜๋Š” ๊ณผ์ •์„ ๊ฑฐ์น˜๊ฒŒ ๋ฉ๋‹ˆ๋‹ค. ๋˜ํ•œ ์ด ๊ณผ์ •์—์„œ db์— ์ ‘๊ทผํ•˜์—ฌ ์ฟผ๋ฆฌ๋ฅผ ์ˆ˜ํ–‰ํ•˜๋˜ ์ค‘ ์ƒ๊ธฐ๋Š” ์—๋Ÿฌ์™€ db์— connection์„ ์ƒ์„ฑํ•˜๋Š” ๊ณผ์ • ์ค‘์— ์ƒ๊ธด ์—๋Ÿฌ๋ฅผ ์ปค์Šคํ…€ ์—๋Ÿฌ๋กœ ์ฒ˜๋ฆฌํ•˜์—ฌ router์—์„œ ๋ฐ›์€ ๋’ค, ๋ฏธ๋“ค์›จ์–ด๋ฅผ ํ†ตํ•ด ์—๋Ÿฌ๋ฅผ response๋กœ ๋ณด๋‚ด๋„๋ก ์„ค๊ณ„ํ•˜์˜€์Šต๋‹ˆ๋‹ค. ์„ค๊ณ„ํ•œ ๊ตฌ์กฐ์™€ ์—๋Ÿฌ์ฒ˜๋ฆฌ ๋ถ€๋ถ„์— ๋Œ€ํ•ด์„œ ๋ณด์™„ํ•  ๋ถ€๋ถ„ ํ”ผ๋“œ๋ฐฑ ์ฃผ์‹œ๋ฉด ๊ฐ์‚ฌํ•˜๊ฒ ์Šต๋‹ˆ๋‹ค!