애증의 핀토스 끝..
핀토스한테 지고 말았다..😔
다만, 책으로 이론적, 개념적으로 알고 있던 내용을
실제 커널 코드를 통해서 이해할 수 있어서 매우 매우 유익한 프로젝트였다.
오늘부터는 프로젝트! 달려본다!!
인증과 CRUD 기능 구현 기본 구현 +
Nest.js + Mongoose로 구현 예정
애자일 프로젝트 관리 Jira 유데미 강의(10시간) 듣기!
이슈(Issue): Jira에서 이슈는 작업, 버그, 기능 요청, 태스크 또는 기타 추적할 항목을 나타냅니다. 각 이슈는 고유한 식별자와 상태, 우선순위 등의 세부 정보를 가집니다.
프로젝트(Project): 프로젝트는 관련 이슈들의 집합입니다. 각 프로젝트는 특정 목표나 목적을 가지며, 이에 따라 여러 이슈를 관리합니다.
워크플로우(Workflow): 워크플로우는 이슈의 생명주기를 정의합니다. 이는 이슈가 생성되어 완료될 때까지 거치는 상태 변화의 순서를 나타냅니다. 예를 들어, "To Do" → "In Progress" → "Done" 과 같은 순서일 수 있습니다.
보드(Board): 보드는 이슈들을 시각적으로 표시하는 공간입니다. 스크럼 보드와 칸반 보드 두 가지 유형이 일반적입니다.
에픽(Epic): 에픽은 여러 관련 스토리나 태스크를 포함하는 큰 단위의 작업입니다. 프로젝트의 주요 기능이나 목표를 나타낼 수 있습니다.
스토리(Story): 스토리는 사용자 관점에서 작성된 기능 요구사항을 나타냅니다. 일반적으로 "As a [user], I want [feature], so that [benefit]" 형식으로 작성됩니다.
스프린트(Sprint): 스프린트는 스크럼 방법론에서 정해진 기간 동안 수행되는 작업의 세트입니다. 일반적으로 2-4주의 기간을 갖습니다.
백로그(Backlog): 백로그는 아직 시작되지 않은 이슈들의 목록입니다. 프로젝트 팀은 백로그에서 이슈를 선택하여 스프린트나 다른 작업 단계로 이동시킵니다.
버전(Version): 프로젝트의 특정 출시를 나타내는 레이블입니다. 이는 소프트웨어 개발에서 특히 중요하며, 각 버전은 고유한 목표와 마감 기한을 가질 수 있습니다.
컴포넌트(Component): 컴포넌트는 프로젝트 내에서 특정 기능이나 책임 영역을 나타냅니다. 이를 통해 이슈를 더 세분화하고 관리할 수 있습니다.
그루밍: 백로그 정리(Backlog Grooming) 또는 백로그 정제(Backlog Refinement)라고도 불립니다. 이 과정은 프로젝트의 제품 백로그를 지속적으로 업데이트하고 최적화하는 활동. 백로그 최신화가 목적.
스토리 포인트: 특정 스토리의 난이도를 상대적으로 측정
많은 팀들이 피보나치 수열로 스토리 포인트를 할당함.
(1, 2, 3, 5, 8, 13)
하위이슈(서브 태스크): 큰 작업이나 이슈를 더 작고 관리하기 쉬운 부분으로 분할
에릴리 스탠드업 미팅: 매일 정해진 시간에 팀원들이 짧게 서서 진행하는 미팅으로, 일반적으로 15분 이내로 제한. 주요 목적은 팀원들이 서로의 작업 진행 상황을 공유하고, 협업을 촉진하며, 잠재적인 문제를 신속하게 해결
세 가지 주요 질문:
어제 무엇을 했나요? 각 팀원은 전날 수행한 작업에 대해 보고합니다.
오늘 무엇을 할 계획인가요? 각 팀원은 그날의 작업 계획을 공유합니다.
작업을 방해하는 장애물이 있나요? 잠재적인 문제나 장애물을 식별하고, 필요한 경우 도움을 요청합니다.
클라이언트 ->
1. 파이프(요청에 대한 유효성 검증)
2. 가드(인증/인가)
3. 콘트롤러(라우팅)
4. 서비스(비지니스 로직)
5. 리포지터리(데이터 저장)
nest g resource [name]
import { Controller, Get, Req } from '@nestjs/common';
import { Request } from 'express';
@Controller('cats')
export class CatsController {
@Get()
findAll(@Req() request: Request): string {
return 'This action returns all cats';
}
}
근데 굳이 가져올 필요는 없다. 왜냐면 데코레이터로 왠만하면 처리 가능.
Data Transfer Object 스키마
네트워크를 통해 데이터가 전송되는 방법을 정의하는 개체
인터페이스나 클래스를 사용할 수 있음.
클래스를 사용하는 게 좋음
인터페이스는 타입스크립트만의 구문임.
컴파일된 최종 JS에는 존재하지 않음.
그 외에 다양한 장점이 있음.
export class UpdateUserDto extends PartialType(CreateUserDto) {}
CreateUserDto를 상속받지만, 해당 필드를 모두 옵셔널로 만들어서 가져옴.
@Injectable()
은 주입가능한 의존성임을 컨테이너한테 알려주는 것
constructor(private readonly userService: UserService)
private 접근 지정자는 멤버 변수 선언과 동시에 초기화 함.
속성 기반 주입도 가능하지만, 생성자 기반 주입을 하는 게 좋음.
따로 하는 게 좋음.
DTO는 데이터 전송 관점에서 객체 구조 정의하는 것
Interface는 타입 체킹에 사용되는 것
제어 역전
프로그램의 흐름을 사용자가 아닌 프레임워크나 라이브러리가 제어
제어권이 넘어감.
의존성 주입을 통해 구현됨. 객체의 생성과 생명 주기 관리를 프레임워크가 담당.
개발자는 의존성을 주입받아 사용.
특정 클래스의 인스턴스가 애플리케이션 전체에서 하나만 존재하도록 보장하는 패턴
하나의 클래스 인스턴스를 여러 객체들이 공유할 수 있게 하여, 자원의 중복 생성을 방지하고 전역적으로 접근 가능한 단일 인스턴스를 제공하는 것.
nest에서 인스턴스를 공유하려면 export로 클래스를 내보내야 함.
import { Module, Global } from '@nestjs/common';
import { CatsController } from './cats.controller';
import { CatsService } from './cats.service';
@Global()
@Module({
controllers: [CatsController],
providers: [CatsService],
exports: [CatsService],
})
export class CatsModule {}
@Global() 데코레이터로 import없이 다른 모듈에서 사용할 수 있으나, global은 좋은 디자인은 아님.
라우터 이전에 호출되는 함수
요청 및 응답 개체에 엑세스 가능
요청과 응답 사이에서 동작함.
next()
로 req, res 의 다음 사이클로 넘어갈 수 있게 함.
(미호출 시 요청이 멈추고 응답 보내지 않음.)
next()
는 next
변수로 재표시됨.(변수에 함수 할당)
import { Injectable, NestMiddleware } from '@nestjs/common';
import { Request, Response, NextFunction } from 'express';
@Injectable()
export class LoggerMiddleware implements NestMiddleware {
use(req: Request, res: Response, next: NextFunction) {
console.log('Request...');
next();
}
}
LoggerMiddleward를 CatsController에 등록하는 코드
import { Module, NestModule, MiddlewareConsumer } from '@nestjs/common';
import { LoggerMiddleware } from './common/middleware/logger.middleware';
import { CatsModule } from './cats/cats.module';
import { CatsController } from './cats/cats.controller';
@Module({
imports: [CatsModule],
})
export class AppModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(LoggerMiddleware)
.forRoutes(CatsController);
}
}
consumer
.apply(LoggerMiddleware)
.exclude(
{ path: 'cats', method: RequestMethod.GET },
{ path: 'cats', method: RequestMethod.POST },
'cats/(.*)',
)
.forRoutes(CatsController);
전역 예외 필터가 있음.
{
"statusCode": 500,
"message": "Internal server error"
}
@Get()
async findAll() {
throw new HttpException('Forbidden', HttpStatus.FORBIDDEN);
}
내장 HTTP 예외
BadRequestException
UnauthorizedException
NotFoundException
ForbiddenException
NotAcceptableException
RequestTimeoutException
ConflictException
GoneException
HttpVersionNotSupportedException
PayloadTooLargeException
UnsupportedMediaTypeException
UnprocessableEntityException
InternalServerErrorException
NotImplementedException
ImATeapotException
MethodNotAllowedException
BadGatewayException
ServiceUnavailableException
GatewayTimeoutException
PreconditionFailedException
필터는 예외 처리에 특화되어 있으면 예외 발생 시에만 실행됨!
@Post()
@UseFilters(HttpExceptionFilter)
async create(@Body() createCatDto: CreateCatDto) {
throw new ForbiddenException();
}
new로 인스턴스를 생성할 수 있으나 클래스는 사용하는게 메모리 사용량이 줄어듦.
근데 이런 식으로 전역 범위 필터 만들수도 있음.
APP_FILTER로 필터를 등록하면 등록 모듈에 무관하게 전역적으로 적용됨.
import { Module } from '@nestjs/common';
import { APP_FILTER } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_FILTER,
useClass: HttpExceptionFilter,
},
],
})
export class AppModule {}
근데 이렇게 전역으로 등록하면 특정 모듈에 등록했다고 해도, 전체에 다 적용됨.
그래서 필터를 설정할 때는 그 필터를 정의한 모듈에서 설정하는 게 좋음.
즉, app모듈이 아닌 HttpExceptionFilter가 정의된 모듈에서 등록하는 게 좋음. 예를 들어 core나 common과 같은 모듈.
다음 두 가지 경우에 사용함.
컨트롤러 경로 핸들러에 의해 수행.
Nest는 메소드 호출 직전 파이프 삽입
기본 내장 파이프가 있으며 커스텀도 가능
ValidationPipe
ParseIntPipe
ParseFloatPipe
ParseBoolPipe
ParseArrayPipe
ParseUUIDPipe
ParseEnumPipe
DefaultValuePipe
ParseFilePipe
파이프는 파라미터를 처리한다고 이해하면 될 듯!
// 이건 클래스를 주입하는 예시
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number) {
return this.catsService.findOne(id);
}
// 이건 인스턴스를 주입하는 예시
@Get(':id')
async findOne(
@Param('id', new ParseIntPipe({ errorHttpStatusCode: HttpStatus.NOT_ACCEPTABLE }))
id: number,
) {
return this.catsService.findOne(id);
}
// UUID인지 분석하는 파이프
@Get(':uuid')
async findOne(@Param('uuid', new ParseUUIDPipe()) uuid: string) {
return this.catsService.findOne(uuid);
}
// null이나 undefined가 수신되면 예외 대신 기본 값 설정
@Get()
async findAll(
@Query('activeOnly', new DefaultValuePipe(false), ParseBoolPipe) activeOnly: boolean,
@Query('page', new DefaultValuePipe(0), ParseIntPipe) page: number,
) {
return this.catsService.findAll({ activeOnly, page });
}
만약 여기서 id가 '1'과 같이 정수 형태의 문자열로 넘어오면 핸들러가 작동하는데,
'abc'같이 정수 변환 불가능한 문자열로 넘어오면 예외를 발생시킴.
{
"statusCode": 400,
"message": "Validation failed (numeric string is expected)",
"error": "Bad Request"
}
npm i --save class-validator class-transformer
DTO에 데코레이터 추가해서 유효성 검사할 수 있음.
import { IsString, IsInt } from 'class-validator';
export class CreateCatDto {
@IsString()
name: string;
@IsInt()
age: number;
@IsString()
breed: string;
}
데코레이터로 별도 유효성 검증 클래스를 대체하니까 편리함!
런타임 조건(권한, 역할, ACL(접근제어목록))에 따라 핸들러 처리 가능한지 결정 = 승인
가드는 미들웨어 이후, 인터셉터/파이트 이전에 실행
미들웨어는 본질적으로 멍청하다? next()는 함수 호출 후 어떤 핸들러가 실행될지 알 수 없다.
가드는 다음 실행될 내용을 알 수 있다.
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class AuthGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
const request = context.switchToHttp().getRequest();
return validateRequest(request);
}
}
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
import { Observable } from 'rxjs';
@Injectable()
export class RolesGuard implements CanActivate {
canActivate(
context: ExecutionContext,
): boolean | Promise<boolean> | Observable<boolean> {
return true;
}
}
import { Module } from '@nestjs/common';
import { APP_GUARD } from '@nestjs/core';
@Module({
providers: [
{
provide: APP_GUARD,
useClass: RolesGuard,
},
],
})
export class AppModule {}
메서드 실행 전후 전체 요청과 응답을 가로채고 수정 가능
미들웨어: 라우터 전에 실행
가드: 미들웨어 이후 실행
인터셉터: 메서드 전후 실행
파이프: 핸들러 메서드 중 파라미터 처리 시 실행
필터: 예외 처리
순서를 보면
미들웨어 - 가드 - 인터셉터 - (핸들러 메서드) - 파이프 - 필터