Skip to Content
문서설계 철학

설계 철학

ASAPJS는 **“As Simple As Possible”**을 지향하는 TypeScript 웹 프레임워크입니다. 반복적인 보일러플레이트를 제거하고, 하나의 정의로 여러 관심사를 동시에 해결하는 것을 목표로 합니다.


핵심 원칙

1. 한 번 정의, 여러 곳에 적용 (Single Source of Truth)

ASAPJS의 가장 핵심적인 설계 원칙은 하나의 정의가 데이터베이스 스키마와 API 문서를 동시에 생성하는 것입니다. TypeIs 데코레이터가 이 원칙을 구현합니다.

// packages/sequelize/src/types/ 에서 정의된 TypeIs 시스템 // 하나의 TypeIs.STRING() 데코레이터가 두 가지 역할을 수행합니다: @TypeIs.STRING({ unique: true, comment: '이메일 (고유)' }) email: string; // 1) Sequelize 컬럼 정의 → toSequelize() // { type: DataTypes.STRING, unique: true, comment: '이메일 (고유)' } // 2) Swagger 스키마 정의 → toSwagger() // { type: 'string', description: '이메일 (고유)' }

이 패턴은 DB 스키마와 API 문서가 항상 동기화되도록 보장합니다. 필드를 추가하거나 수정할 때 한 곳만 변경하면 됩니다.

TypeIs 시스템은 다음과 같은 타입들을 제공합니다:

TypeIsSequelizeSwagger용도
TypeIs.INTDataTypes.INTEGER{ type: 'integer' }정수
TypeIs.STRINGDataTypes.STRING{ type: 'string' }문자열
TypeIs.PASSWORDDataTypes.STRING{ type: 'string', format: 'password' }비밀번호
TypeIs.BOOLEANDataTypes.BOOLEAN{ type: 'boolean' }불리언
TypeIs.DATETIMEDataTypes.DATE{ type: 'string', format: 'date-time' }날짜/시간
TypeIs.FOREIGNKEYForeign key 관계{ type: 'integer' }외래키
TypeIs.BELONGSTOBelongsTo 관계관계 스키마소속 관계
TypeIs.ENUMDataTypes.ENUM{ enum: [...] }열거형
TypeIs.JSONDataTypes.JSON{ type: 'object' }JSON
TypeIs.TEXTDataTypes.TEXT{ type: 'string' }장문 텍스트

2. 데코레이터 기반 선언적 프로그래밍

ASAPJS는 TypeScript 데코레이터를 적극 활용하여 “무엇을” 정의하는 데 집중하고, “어떻게” 동작하는지는 프레임워크가 처리합니다.

라우트 데코레이터 — Express 라우트와 Swagger 문서를 하나의 데코레이터로 선언합니다:

// packages/router/src/decorator/index.ts @Post('/register', { title: '회원 가입', body: CreateUserDto, // 요청 바디 스키마 → Swagger + 유효성 검사 response: UserInfoDto, // 응답 스키마 → Swagger auth: false, // JWT 인증 불필요 }) async register({ body }: ExecuteArgs) { return await this.userService.register(body); }

이 한 줄의 데코레이터가 내부적으로 수행하는 작업:

  1. Express 라우터에 POST /register 핸들러 등록
  2. jwtVerification(false) 미들웨어 적용
  3. Wrapper로 요청/응답 포맷팅 및 에러 핸들링 래핑
  4. Swagger에 경로, 요청/응답 스키마 자동 등록

테이블 데코레이터 — Sequelize 모델과 메타데이터를 선언적으로 정의합니다:

// packages/sequelize/src/table/index.ts 에서 처리 @Table({ tableName: 'users', timestamps: true }) export default class UsersTable extends Model { @TypeIs.INT({ primaryKey: true, autoIncrement: true }) id: number; } // → Sequelize 컬럼 정의, 타임스탬프 설정, DBML 생성, 콘솔 등록이 모두 자동 처리

3. Controller → Application → Entity 레이어드 아키텍처

ASAPJS는 명확한 3계층 구조를 권장합니다:

Controller (HTTP 인터페이스) ↓ ExecuteArgs { body, query, user, paging } Application (비즈니스 로직) ↓ Sequelize API Entity (데이터 접근, @Table 모델)

각 레이어의 책임:

레이어위치책임
Controllercontroller/UserController.tsHTTP 요청 수신, 라우트 데코레이터, Application 호출
Applicationapplication/UserApplication.ts비즈니스 로직, 검증, 트랜잭션 관리
Entitydomain/entity/UsersTable.tsDB 스키마 정의, Sequelize 모델

이 구조의 장점:

  • 테스트 용이성: Application 레이어를 HTTP 서버 없이 직접 테스트 가능
  • 관심사 분리: 각 레이어가 독립적으로 변경 가능
  • 명확한 데이터 흐름: 요청 → 컨트롤러 → 비즈니스 로직 → DB → 응답

4. 자동 탐색 (Auto-Discovery)

ASAPJS는 파일 이름 규칙을 통해 모듈을 자동으로 발견하고 등록합니다. 수동 임포트나 등록 코드가 필요 없습니다.

// packages/sequelize/src/sequelize/index.ts 에서 구현 const loadPath = async (path: string, subPath: string) => { const files = fs.readdirSync(path + subPath, { withFileTypes: true }); for (const file of files) { if (file.isDirectory()) { await loadPath(path, `${subPath}/${file.name}`); } else if (extensionInclude('Table', file.name)) { models.push(require(`${path}/${subPath}/${file.name}`).default); } else if (extensionInclude('Dto', file.name)) { dtos.push(require(`${path}/${subPath}/${file.name}`).default); } } };

자동 탐색 규칙:

파일 패턴발견 시점용도
*Table.tsinitSequelizeModule()Sequelize 모델로 등록
*Dto.tsinitSequelizeModule()Swagger 스키마로 등록
*Socket.tsinitSocketModule()Socket.IO 핸들러로 등록
route.tsRouterModule()컨트롤러 라우트 로딩

5. 통일된 요청/응답 래핑 (Wrapper Pattern)

모든 라우트 핸들러는 Wrapper 함수로 감싸집니다. 이 패턴은 반복적인 요청 파싱과 에러 처리를 추상화합니다.

// packages/router/src/utils/wrapper.ts export interface ExecuteArgs<P = {}, Q = {}, B = {}> { req: Request; res: Response; path?: P; // URL 파라미터 (/users/:id → { id: '1' }) query: Q; // 쿼리 스트링 (?page=0&limit=20) body: B; // 요청 바디 files?: any; // 업로드된 파일 user?: any; // JWT 디코딩된 사용자 정보 paging: PaginationQueryDto; // ?page=&limit= 자동 추출 }

핸들러는 ExecuteArgs를 받아 결과를 반환하기만 하면 됩니다. Wrapper가 자동으로:

  • req.body, req.query, req.params를 구조화된 ExecuteArgs로 변환
  • ?page=0&limit=20에서 페이지네이션 정보 자동 추출
  • 반환값을 res.status(200).json(output)으로 응답
  • 에러 발생 시 HttpException의 status/message로 응답
  • 500 에러 시 Sentry 연동 (설정된 경우)

6. 모노레포 — 관심사별 패키지 분리

ASAPJS는 Lerna + Yarn Workspaces 기반 모노레포로 구성됩니다. 각 패키지는 독립적인 npm 패키지로 발행 가능하며, 필요한 기능만 선택적으로 사용할 수 있습니다.

packages/ core/ @asapjs/core — Application, 설정, 로거 (필수) router/ @asapjs/router — HTTP 라우팅, 데코레이터, Swagger, JWT (필수) sequelize/ @asapjs/sequelize — ORM, TypeIs, DTO, Repository (선택) socket/ @asapjs/socket — Socket.IO 통합 (선택)

extensions 배열을 통해 필요한 패키지만 활성화합니다:

const config = { extensions: ['@asapjs/sequelize'], // Sequelize만 사용 // extensions: ['@asapjs/sequelize', '@asapjs/socket'], // + Socket.IO };

프레임워크 초기화 흐름

Application.run()이 호출되면 다음 순서로 모듈이 초기화됩니다:

Application.run() ├─ RouterModule(dirname) │ ├─ initMiddlewares() CORS, bodyParser, 커스텀 미들웨어 │ ├─ require(route.ts) 컨트롤러 로딩 │ ├─ registerRoutes() @Get/@Post/@Put/@Delete 처리 │ └─ Swagger UI 마운트 /docs/swagger-ui.html ├─ initSequelizeModule(dirname) [extensions에 포함 시] │ ├─ dbInit() DB 연결 │ ├─ loadPath() *Table.ts, *Dto.ts 자동 탐색 │ ├─ addModelsToSequelize() 모델 등록 │ └─ addDtosToSwagger() DTO → Swagger 스키마 ├─ initSocketModule(server) [extensions에 포함 시] │ ├─ loadPath() *Socket.ts 자동 탐색 │ └─ socketInit() Socket.IO 서버 시작 ├─ initBeforeStartServer() 사용자 콜백 (DB sync 등) └─ server.listen(port) HTTP 서버 시작

관련 문서

  • BootstrapApplication 클래스와 초기화 상세
  • Swagger — TypeIs → OpenAPI 스키마 자동 생성
  • Your First API — 레이어드 아키텍처 실습
Last updated on