SoundSquad-back Github : SoundSquad - back Repository
Development Document Notion : Development Document


SoundSquad 프로젝트를 진행하면서 DB, API 설계를 하며 고려한 부분들을 기록했습니다.

DB

테이블 설계 예시

ERD

SoundSquad_ERD
SoundSquad_ERD - ERDCloud 링크

정규화

3정규형까지 도달하는것을 목표로 데이터베이스를 설계, 이 이상 테이블을 나누면 조회 쿼리가 복잡해지고, 성능에도 영향이 있을것이라고 여김, 추후 기능이 완성되고 장기간 변화할 여지가 없는 기능들은 반정규화를 시도해 성능향상을 시도해보고자 함

스쿼드(모임) 기능에 대한 설명

 스쿼드 를 여는 기능이 이 프로젝트의 주된 기능일 것이라고 여겨 ERD를 활용해 기능이 어떤 식으로 구현될 지 계획해보고자 함
스쿼드가 어떤 조건을 가질지 미리 정해보면

  • 스쿼드는 관람하고자 하는 공연에 대해 열린다. (스쿼드와 공연정보는 N:1 관계)
  • 스쿼드는 개최자 와 참여자 2인이 하나의 스쿼드가 된다.
  • 모든 사용자는 하나의 공연에서 하나의 스쿼드에만 개최 또는 참여할 수 있다.

스쿼드는 다음과 같은 정보에 의해 구성된다.

  • 각 정보들을 식별할 식별번호
  • 어떤 공연에 대해서 열리는지 공연정보의 식별번호
  • 공연이 열리는 개최일시 ( 설계 변경에 따른 추가 )
  • 개최자의 식별번호
  • 참여자의 식별번호
  • sequelize를 활용해서 생성 시간정도를 기록할 예정

스쿼드의 열고 닫음에 대해서 논리삭제를 고려하지 않음 - 데이터 무결성 및 정확성, 기능의 특성상 쉽게 열고 닫을 수 있게 할 예정

스쿼드에 대해서 다음과 같은 작업 또는 기능이 존재한다.

  • 스쿼드를 여는 요청(post : /api/squad)
    - 공연의 식별번호와 생성자의 식별번호를 포함해 스쿼드 데이터를 생성하고 식별번호를 부여
  • 스쿼드를 닫는 요청(delete : /api/squad)
    - 이 경우에는 스쿼드 데이터가 DB에서도 삭제
  • 스쿼드에서 다른 유저를 추방하는 요청(patch : /api/squad)
    - 참여자 데이터를 null로 수정
  • 스쿼드에 참여하는 요청(post : /api/squad/member)
    - 참여자 데이터를 참여자의 식별번호로 수정
  • 스쿼드에서 떠나는 요청(delete : /api/squad/member)
    - 참여자 데이터를 null로 수정

위 와 같은 데이터 관리를 통해 스쿼드 기능을 구현할 예정

API

RESTful API 설계 원칙

이전 프로젝트에서도 노력은 했었지만 부족한 점들이 있었기에, 특별히 부족하다고 느꼈던 부분들은 다음과 같은데, 이번에는 조금 더 신경써서 고민해보고자 한다.

  • HTTP메서드( 클라이언트에서의 의도 )의 적절한 사용
  • 리소스 중심 URL 구조
  • 쿼리 파라미터, 패스 파라미터의 적절한 사용
  • 적절한 status code (404, 200, 500 등)을 부여

url 설계

- 스쿼드를 여는 요청(post : /api/squad)
- 스쿼드를 닫는 요청(delete : /api/squad)
- 스쿼드에서 다른 유저를 추방하는 요청(patch : /api/squad)
- 스쿼드에 참여하는 요청(post : /api/squad/member)
- 스쿼드에서 떠나는 요청(delete : /api/squad/member)

라우트 핸들러 함수 예시

참여자가 스쿼드에서 떠나려는 요청( delete : /api/squad/member )을 구현한 코드이다.

export const deleteLeaveSquad = async (req: Request, res: Response) => {
  let transaction: Transaction | null = null;

  try {
    const { squad_num, user_num } = req.body;

    if (!user_num || !squad_num ) {
      logger.error(' deleteLeaveSquad - 400');
      return res.status(400).json({ 
        msg: '필수 정보가 누락되었습니다.',
        flag: false 
      });
    }

    transaction = await db.sequelize.transaction();
    
    const squadInfo = await db.SquadInfo.findOne({
      where: { squad_num },
      lock: Transaction.LOCK.UPDATE,
      transaction
    });

    if( squadInfo?.member_num !== user_num || squadInfo?.member_num === null){
      await transaction.rollback();
      logger.error(' deleteLeaveSquad - 403');
      return res.status(403).json({ 
        msg : '권한이 없는 접근입니다.',
        flag: false 
      });
    }
    
    if( squadInfo ) {
      squadInfo.member_num = null,
      await squadInfo.save({ transaction });
    }else{
      await transaction.rollback();
      logger.error(' deleteLeaveSquad - 404');
      return res.status(404).json({ 
        msg : '존재하지 않는 스쿼드에 대한 접근입니다.',
        flag: false 
      });
    }

    await transaction.commit();

    logger.info(' deleteLeaveSquad - 200');
    return res.status(200).json({ 
      msg: 'Squad 스쿼드를 떠나는 데 성공했습니다.',
      flag: true 
    }); 
    
  } catch (err) {
    logger.error(' deleteLeaveSquad - 500');
    if (transaction) await transaction.rollback();
    console.error('Squad 스쿼드를 떠나는 중 오류 발생했습니다.', err);
    return res.status(500).json({ 
      msg: 'Squad 스쿼드를 떠나는 중 오류가 발생했습니다.',
      flag: false
    });
  }
};
  • 트랜잭션 사용으로 데이터 일관성 보장
  • 락(Lock) 사용으로 병행 제어
  • 권한 검증과 상태 확인을 통합하여 처리
  • 다양한 예외 상황에 대한 적절한 HTTP 상태 코드 반환
  • 에러 처리 및 로깅
profile
점심 뭐먹지

0개의 댓글