5. 협업규칙 및 컨벤션 - Licruit/Licruit-backend GitHub Wiki

📚 협업 방법

회의 시간

  • 전체 회의: 매주 월요일 20:00 PM
  • 멘토링: 매주 월요일 20:30 PM

Agile 방법론

저희 팀은 짧은 주기로 개발과 테스트, 배포를 반복하는 Agile 방법론을 도입하여 개발의 유연성을 제고하였습니다. 특히 Agile 방법론 중에서도 Scrum을 기반으로 하되, 일부 규칙을 우리 팀에 맞게 조정하였습니다.

스크린샷 2024-09-04 오후 2 54 31

✔️ 협업 규칙

  • 전체 개발 기간을 반복적인 개발 주기인 스프린트(Sprint)로 나누어 개발을 진행합니다.
    • 스프린트 단위는 1주로 합니다. (그러나, 상황에 따라 조절 가능합니다.)
  • 제품 백로그는 기능 명세서로 대체합니다.
  • 매 스프린트 마다 본인이 맡은 기능을 구현하기 위한 task를 task board에작성합니다.
  • 매 스프린트 마다 release를 반복합니다.
  • 스프린트 목표 달성이 완료되지 않은 경우, 차기 스프린트로 연장할 수 있습니다.
  • 정기회의의 주목적은 ‘스프린트 리뷰’입니다.
    • 스프린트 결과물에 대한 테스트를 진행합니다.
    • 해당 스프린트 동안 개선할 점, 요구사항의 변화, 이슈 사항을 체크하고 공유합니다.
  • 스프린트 리뷰 이후 차기 스프린트 계획 회의를 진행합니다.
    • 기능 선정 → 조율이 필요한 사항 논의 → task 설정

✔️ Sprint Task board

노션의 task board를 활용하여 스프린트마다 task를 작성하였고, 모든 팀원의 task를 서로 공유했습니다.

스크린샷 2024-09-04 오후 1 12 20

🖥️ FE 컨벤션

✔️ 협업 flow

  1. 이슈 생성

본인이 어떤 작업을 할 것인지, 맡은 기능에 대한 세부적인 작업 태스크를 나열합니다.

  1. 이슈 넘버 기반으로 브랜치 생성

생성된 이슈 넘버를 이용하여 작업 브랜치를 생성합니다. (ex. feat/#1)

  1. 작업 후 dev 브랜치에 PR

작업이 완료된 후, dev 브랜치에 PR을 올립니다. PR 내용으로 본인이 작업한 내용에 대해 상세하게 설명합니다.

  1. 코드 리뷰

PR은 모든 팀원이 코드 리뷰를 마친 후 approve를 받아야 합니다.

  1. 브랜치 병합

작업자 본인은 코드 리뷰를 확인한 후 수정사항/이슈사항 등을 반영한 뒤, 작업 브랜치를 dev 브랜치에 병합합니다.


📁 기능 기반 폴더 구조 (feature-based)

지금까지 저희 팀원들은 프로젝트 구조를 파일 유형별로 그룹화한 형태로 사용해왔습니다. (pages, components…)

그러나 하나의 페이지에 속하는 수많은 컴포넌트들을 한 폴더(components/pagename) 내에서 관리하다보면 프로젝트 규모가 점차 커졌을 때 점점 어떤 기능과 연관된 코드를 탐색하는 과정이 어려워지고, 이걸 또 다시 하위 디렉토리로 구분하자니 폴더의 depth가 계속해서 깊어져 그다지 좋은 방식이라 생각하지 않았습니다.

따라서 저희는 여러가지 레퍼런스를 찾아보았고 결론적으로 기능 기반(feature-based) 폴더 구조가 지금의 난관을 해결해줄 수 있을 것이라 판단했습니다.

public
 │	└─ assets
 │	     ├─ icons
 │	     └─ images
src
 ├─ pages         # 페이지
 ├─ constants     # 상수
 ├─ store         # 전역 상태 관리 스토어
 ├─ styles        # 전역 스타일링
 ├─ types         # 전역 타입
 ├─ features
 │   └── Review  # 기능
 │	   ├── components
 │         ├── hooks
 │         ├── api
 │         ├── models
 │	   ├── utils
 │	   └── index.ts  # export
 
 ├─ components        # 재사용 가능한 공통 컴포넌트
 │     ├── Input  
 │     ├── Button  
 │     └── Layouts
 
 ├─ utils         # 유틸리티 함수 (자주 사용되는 기능 모듈화)
 ├─ hooks         # 커스텀 훅
 ├─ routes        # router
 ├─ App.tsx
 └─ index.tsx

✔️ features 폴더

features 폴더는 기능별 폴더입니다.

이곳에 기능 별로 폴더를 나누고, 그 내부에는 그 기능과 관련된 UI 컴포넌트, 커스텀 훅, 유틸 함수 등을 위치시켰습니다.

기존의 파일 유형 기반 폴더 구조에서는 이 모든 것들이 components, hooks, utils 폴더에 각각 나뉘어 위치하고 있었다면, 현재 저희의 폴더 구조는 기능에 필요한 파일들만 모아 한 곳에 묶어 놓은 형태입니다.

이와 같은 폴더 구조를 통해 저희는 어떠한 기능에 대해서 수정 작업이 필요할 때, 기능에 연관된 요소들이 한 곳에 모여있어 코드를 탐색하는 과정이 현저히 줄어들었습니다.

(fyi. features 폴더와 같은 depth에 위치하는 hooks, utils, components 폴더의 경우, 어떠한 기능에 종속되지 않고 여러 곳에서 공통적으로 사용 가능한 것들이 포함됩니다.)


✔️ Public API

각 기능별 폴더에는 public API 역할이 되는 index.ts 를 생성했습니다.

이 파일에서는 외부에서 사용해야하는 public 컴포넌트를 기능 폴더에서 외부로 내보내는 역할을 합니다.

export { default as Banner } from './components/Banner';

이 방법을 통한다면 개발자들은 폴더 내부 구조를 자세히 알지 않더라도 컴포넌트를 손쉽게 import 하여 사용할 수 있고, 외부에서 활용되지 않는 컴포넌트들은 기능 폴더 내부에 private화 하여 캡슐화를 구현할 수 있습니다.

또한 import 경로를 훨씬 더 직관적이고 단순하게 만든다는 장점도 있습니다.

// 직접 import
import Banner from '@/features/Main/components/Banner';

// public API를 통한 import
import { Banner } from '@/features/Main';

🖥️ BE 컨벤션

📁 폴더 구조

src
 ├─ config
 ├─ routes
 ├─ validators
 ├─ controllers
 ├─ services
 ├─ dto
 ├─ utils
 ├─ errorHandler
 ├─ models
    └─ index.ts
 ├─ auth.ts
 └─ app.ts

✔️ Naming Rules

  • 모든 함수는 가급적이면 Arrow Function으로 작성하고, 선언할 때 export 합니다.
  • 함수명은 동사형으로 작성하고, 카멜 케이스를 사용합니다.
    • controller에서는 함수명에 add, get, put/change, remove를 사용합니다.
    • service에서는 함수명에 SQL 명령어(insert, select, update, delete)를 사용합니다.
  • 파일명은 복수형대상.단수형폴더명.ts로 작성합니다.
    • routes: users.route.ts
    • controllers: users.controller.ts
    • services: users.service.ts
    • validators: users.validator.ts
      • 모든 함수 이름 끝에 Validate를 붙입니다.
    • models: users.model.ts
    • dto: users.dto.ts
    • tests: users.test.ts
  • env파일 변수명은 대문자와 언더바(_)를 사용합니다.

✔️ 에러 처리

  • route
    • controller 함수는 Error Handler로 감싸서 작성합니다. controller에서 try, catch 구문을 없애기 위함입니다.
router.post('/login', ..., wrapAsyncController(login));
  • controller
    • try, catch를 사용하지 않습니다.
    • 예상 가능한 모든 에러는 controller에서 utils/HttpException 함수를 통해 발생시킵니다.
export const addUser = async (req: Request, res: Response) => {
  // ...
  if (foundUser) {
    throw new HttpException(StatusCodes.BAD_REQUEST, '이미 사용된 사업자번호입니다.');
  }
  // ...
  return res.status(StatusCodes.CREATED).end();
};
  • service
    • try, catch 구문을 사용합니다.
    • catch에서 기능에 대한 에러 메시지를 작성합니다.

✔️ 유효성 검사

  • express-validator 라이브러리를 사용하여 유효성 검사를 합니다.
  • validators/validate 파일에 있는 validate 함수는 유효성 검사 결과를 확인하여 400 응답을 보내는 역할을 하므로 이 파일은 건들지 말아야 합니다.
  • 유효성 검사 필드가 2개 이상일 경우, 배열로 작성하여 export 합니다.
import { body } from 'express-validator';

// 각 필드에 대한 유효성 검사 함수 작성
export const contactValidate = body('contact').notEmpty().isString().withMessage('연락처 확인 필요');
export const companyNumberValidate = body('companyNumber')
  .notEmpty()
  .isString()
  .isLength({ min: 10, max: 10 })
  .withMessage('사업자번호 확인 필요');
  
// 해당 route에 대해 유효성 검사 항목이 2개 이상일 경우 배열로 export
export const resetPwValidate = [companyNumberValidate, contactValidate];
  • 작성한 유효성 검사 함수는 router의 미들웨어로 넣습니다. 마지막에 validate 함수도 함께 작성해야 유효성 검사가 정상적으로 이루어집니다.
router.post('/register', [...registerValidate, validate], wrapAsyncController(addUser));
⚠️ **GitHub.com Fallback** ⚠️