일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | 5 | ||
6 | 7 | 8 | 9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 | 19 |
20 | 21 | 22 | 23 | 24 | 25 | 26 |
27 | 28 | 29 | 30 |
- 친절한SQL튜닝
- 선형대수
- 처음 만나는 AI수학 with Python
- /etc/network/interfaces
- Kernighan의 C언어 프로그래밍
- 스프링부트핵심가이드
- 스프링 시큐리티
- 리눅스
- 자료구조와함께배우는알고리즘입문
- d
- 티스토리 쿠키 삭제
- network configuration
- 알파회계
- 구멍가게코딩단
- GIT
- 코드로배우는스프링웹프로젝트
- 페이징
- 이터레이터
- ㅒ
- 네트워크 설정
- 자바편
- 처음 만나는 AI 수학 with Python
- 자료구조와 함께 배우는 알고리즘 입문
- 데비안
- 목록처리
- resttemplate
- iterator
- 서버설정
- 코드로배우는스프링부트웹프로젝트
- baeldung
- Today
- Total
bright jazz music
[nestjs] Repository pattern으로 변경하기 본문
상황:
기존에는 nest -g resource로 users 모듈을 만든 상태였다. 이렇게 하면 컨트롤러, 서비스, 모듈과 유저 생성과 업데이트를 위한 dto까지는 만들어진다. 이 상태에서도 엔티티를 만들어 테이블을 만들고 거기에 값을 넣는 방식을 테스트할 수 있다. 만약 그렇게 하려고 한다면 서비스 레이어에서 typeORM을 사용해서 데이터베이스와 통신해야 했을 것이다.
그러나 나는 서비스 레이어에서는 비즈니스 로직만을 처리하고 데이터베이스와의 통신 로직은 별도로 분리하고 싶었다. 따라서 이전에 경험했던 스프링 프로젝트에서 그러했던 것처럼 리포지토리 패턴을 적용하려고 하였다. 또한 추후 이 모듈을 템플릿으로 삼아 여러 기능을 개발하려는 계획을 가지고 있었으므로 유저 모듈을 먼저 쓸만하도록 만드는 것이 우선이었다.
본문요약
- /src/users 디렉토리에 아래와 같은
user 도메인에 대해서 리포지토리 패턴으로 변경한다.
- - TypeORM을 사용해서 디비에 접근한다.
- - 우선 users 디렉토리 내에 아래의 디렉토리들이 생성된다.
[entities, interfaces, mappers, repositories]
- - entities 디렉토리 내에 user.entity.ts이라는 클래스 파일이 생성되고, 이 파일에서 직접적으로 TypeORM의 함수들을 사용하게 된다.
- - interface내에 user.interface.ts라는 클래스 파일이 생성된다. 이는 서비스 레이어인 user.service.ts에서 타입을 강제하기 위해 사용된다. 그런데 여기서는 생성하기는 했지만 관리가 너무 복잡해서 사용을 보류하였다.
- - mapper 디렉토리에는 user.mapper.ts를 생성한다. dto를 엔티티로, 엔티티를 dto로 사용하기 위한 로직이 들어있다.
- - repositories 디렉토리에는 user.repository.ts파일이 들어있고, 여기에서는 TypeORM의 클래스들을 임포트해서 객체화 한 뒤 그 메서드를 사용하여 db와 통신한다.
1. 엔티티 만들기
// src/users/entities/user.entity.ts
// 유저 엔티티
import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn } from 'typeorm';
@Entity()
export class User {
@PrimaryGeneratedColumn()
id: number;
@Column({ unique: true })
email: string;
@Column()
password: string;
@CreateDateColumn()
createdAt: Date;
@UpdateDateColumn()
updatedAt: Date;
}
2. DTO수정 및 생성
2.1. 유저 생성 DTO 수정
// src/users/dto/create-user.dto.ts
// 유저 생성 DTO
import { IsEmail, IsString, MinLength } from 'class-validator';
export class CreateUserDto {
@IsEmail()
email: string;
@IsString()
@MinLength(8)
password: string;
}
2.2. 유저 업데이터 DTO 수정
// src/users/dto/update-user.dto.ts
// 유저 업데이트 DTO
import { PartialType } from '@nestjs/mapped-types';
import { CreateUserDto } from './create-user.dto';
export class UpdateUserDto extends PartialType(CreateUserDto) {}
2.3. 유저 응답 DTO 수정
// src/users/dto/user-response.dto.ts
// 유저 응답 DTO
export class UserResponseDto {
id: number;
email: string;
createdAt: Date;
updatedAt: Date;
}
3. 매퍼 만들기
// src/users/mappers/user.mapper.ts
// 유저매퍼
import { User } from '../entities/user.entity';
import { UserResponseDto } from '../dto/user-response.dto';
import { CreateUserDto } from '../dto/create-user.dto';
import { UpdateUserDto } from '../dto/update-user.dto';
export class UserMapper {
static toDto(user: User): UserResponseDto {
const dto = new UserResponseDto();
dto.id = user.id;
dto.email = user.email;
// password는 제외
dto.createdAt = user.createdAt;
dto.updatedAt = user.updatedAt;
return dto;
}
static toDtoList(users: User[]): UserResponseDto[] {
return users.map(user => this.toDto(user));
}
static toEntity(dto: CreateUserDto | UpdateUserDto): Partial<User> {
const entity = new User();
if (dto.email) entity.email = dto.email;
if (dto.password) entity.password = dto.password;
return entity;
}
}
4. 리포지토리 만들기
// src/users/repositories/user.repository.ts
// 유저 리포지토리
import { Injectable } from '@nestjs/common';
import { DataSource, Repository } from 'typeorm';
import { User } from '../entities/user.entity';
@Injectable()
export class UserRepository {
private repository: Repository<User>;
constructor(private dataSource: DataSource) {
this.repository = this.dataSource.getRepository(User);
}
async findOne(id: number): Promise<User | null> {
return this.repository.findOne({ where: { id } });
}
async findByEmail(email: string): Promise<User | null> {
return this.repository.findOne({ where: { email } });
}
async findAll(): Promise<User[]> {
return this.repository.find();
}
async create(user: Partial<User>): Promise<User> {
const newUser = this.repository.create(user);
return this.repository.save(newUser);
}
async save(user: User): Promise<User> {
return this.repository.save(user);
}
async delete(id: number): Promise<{ affected?: number }> {
return this.repository.delete(id);
}
}
5. 모듈에 등록
// src/users/users.module.ts
// 유저 모듈
import { Module } from '@nestjs/common';
import { TypeOrmModule } from '@nestjs/typeorm';
import { User } from './entities/user.entity';
import { UsersService } from './users.service';
import { UsersController } from './users.controller';
import { UserRepository } from './repositories/user.repository';
@Module({
imports: [TypeOrmModule.forFeature([User])],
controllers: [UsersController],
providers: [UsersService, UserRepository],
exports: [UsersService],
})
export class UsersModule {}
6. 에러 메시지 상수로 관리( 나중에 해도 되는데 하는 김에 미리 해버렸다. 서비스에서 에러 메시지를 반환하기 위해서 먼저 써준다. )
// src/common/constants/error-messages.constant.ts
// 에러 메시지 상수
export const ERROR_MESSAGES = {
USER_NOT_FOUND: 'User not found',
EMAIL_ALREADY_EXISTS: 'Email already exists',
// ... 기타 에러 메시지
} as const;
7. 서비스 수정
// src/users/users.service.ts
// 유저 서비스
import { Injectable, NotFoundException, ConflictException } from '@nestjs/common';
import { UserRepository } from './repositories/user.repository';
import { UserMapper } from './mappers/user.mapper';
import { UpdateUserDto } from './dto/update-user.dto';
import { UserResponseDto } from './dto/user-response.dto';
import { DataSource } from 'typeorm';
import { ERROR_MESSAGES } from '../common/constants/error-messages.constant';
import { CreateUserDto } from './dto/create-user.dto';
@Injectable()
export class UsersService {
constructor(
private readonly userRepository: UserRepository,
private readonly dataSource: DataSource,
) {}
async findAll(): Promise<UserResponseDto[]> {
const users = await this.userRepository.findAll();
return UserMapper.toDtoList(users);
}
async findOne(id: number): Promise<UserResponseDto> {
const user = await this.userRepository.findOne(id);
if (!user) {
throw new NotFoundException(ERROR_MESSAGES.USER_NOT_FOUND);
}
return UserMapper.toDto(user);
}
async findByEmail(email: string): Promise<UserResponseDto | null> {
const user = await this.userRepository.findByEmail(email);
return user ? UserMapper.toDto(user) : null;
}
async update(id: number, updateData: UpdateUserDto): Promise<UserResponseDto> {
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const user = await this.userRepository.findOne(id);
if (!user) {
throw new NotFoundException(ERROR_MESSAGES.USER_NOT_FOUND);
}
const updatedUser = await this.userRepository.save({
...user,
...updateData,
});
await queryRunner.commitTransaction();
return UserMapper.toDto(updatedUser);
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}
}
async remove(id: number): Promise<void> {
const result = await this.userRepository.delete(id);
if (result.affected === 0) {
throw new NotFoundException(ERROR_MESSAGES.USER_NOT_FOUND);
}
}
async create(createUserDto: CreateUserDto): Promise<UserResponseDto> {
const existingUser = await this.userRepository.findByEmail(createUserDto.email);
if (existingUser) {
throw new ConflictException(ERROR_MESSAGES.EMAIL_ALREADY_EXISTS);
}
const queryRunner = this.dataSource.createQueryRunner();
await queryRunner.connect();
await queryRunner.startTransaction();
try {
const user = await this.userRepository.create(createUserDto);
const newUser = await this.userRepository.save(user);
await queryRunner.commitTransaction();
return UserMapper.toDto(newUser);
} catch (err) {
await queryRunner.rollbackTransaction();
throw err;
} finally {
await queryRunner.release();
}
}
}
8. 컨트롤러 수정
// src/users/users.controller.ts
// 유저 컨트롤러
import {
Controller,
Get,
Put,
Delete,
Param,
Body,
ParseIntPipe,
HttpStatus,
HttpCode,
Post
} from '@nestjs/common';
import { UsersService } from './users.service';
import { UpdateUserDto } from './dto/update-user.dto';
import { UserResponseDto } from './dto/user-response.dto';
import { CreateUserDto } from './dto/create-user.dto';
@Controller('users')
export class UsersController {
constructor(private readonly usersService: UsersService) {}
@Get()
async findAll(): Promise<UserResponseDto[]> {
return this.usersService.findAll();
}
@Get(':id')
async findOne(@Param('id', ParseIntPipe) id: number): Promise<UserResponseDto> {
return this.usersService.findOne(id);
}
@Put(':id')
async update(
@Param('id', ParseIntPipe) id: number,
@Body() updateUserDto: UpdateUserDto,
): Promise<UserResponseDto> {
return this.usersService.update(id, updateUserDto);
}
@Delete(':id')
@HttpCode(HttpStatus.NO_CONTENT)
async remove(@Param('id', ParseIntPipe) id: number): Promise<void> {
return this.usersService.remove(id);
}
@Post()
@HttpCode(HttpStatus.CREATED)
async create(@Body() createUserDto: CreateUserDto): Promise<UserResponseDto> {
return this.usersService.create(createUserDto);
}
}
삭제는 204 상태 코드만 보냄
'Framework > NestJS' 카테고리의 다른 글
[nestjs] 회원 관련 엔티티와(member.entity.ts) 과 관련 파일 작성(enum등) (0) | 2024.12.23 |
---|---|
[nestjs] TypeORM을 사용해 DB에 테이블 생성 (0) | 2024.12.21 |
[nestjs] DB연결을 위한 설정(config모듈 패키지, TypeORM 사용) (0) | 2024.12.21 |
[nestjs] 2.1. auth구현 (1) : 회원가입,로그인,로그아웃 (0) | 2024.12.19 |
[nestjs] 1. 테스트 프로젝트 생성 (0) | 2024.12.19 |