카테고리 없음

4주차 리뷰

namu445 2022. 6. 6. 02:09

TypeORM - MySQL

더보기
  • 관계형 데이터 베이스 사용을 도와주는 ORM의 하나
  • 이번 프로젝트에서는 nestjs에 타입스크립트를 사용하기 때문에 nest와 호환이 좋은 TypeORM을 사용했다.
  • Entity를 생성하고 스키마를 구성할 수 있다.

Entity 구성 예시

//Type ORM을 사용한 Entity 구성 예시
@Entity()
export class Product {
  @PrimaryGeneratedColumn('uuid')
  id: string;

  @Column()
  name: string;

  @Column()
  description: string;

  @Column()
  price: number;

  @Column()
  isSoldout: boolean;

  @JoinColumn()
  @OneToOne(() => ProductSaleslocation) //뭐랑 연결할지, 1:1
  productSalesloaction: ProductSaleslocation; //데이터 타입

  @ManyToOne(() => ProductCategory) //Many 쪽에 JoinColumn이 포함되어있어 작성하지 않아도 된다.
  productCategory: ProductCategory;

  @ManyToOne(() => User)
  user: User;

  @JoinTable()
  @ManyToMany(() => ProductTag, (productTags) => productTags.products) // M:N 테이블 설정
  productTags: ProductTag[];

 Join 테이블 검색 예시

// Type ORM을 이용한 스키마 구성 후에 연결된 테이블 join 검색 설정
async findAll() {
    return await this.productRepository.find({
      relations: ['productSaleslocation', 'productCategory'], // join해서 검색하기
    });
  }

M:N 외례키 등록 예시

async create({ createProductInput }) {
    const { subCategoryId, brandId, modelId, ...product } = createProductInput;

    const subCategory = await this.subCategoryRepository.findOne(
      {
        id: subCategoryId,
      },
      { relations: ['mainCategory'] },
    );

    const brand = await this.brandCategoryRepository.findOne({
      id: brandId,
    });
    const model = await this.modelRepository.findOne({ id: modelId });

    const result = await this.productRepository.save({
      ...product,
      subCategory: {
        id: subCategoryId,
        name: subCategory.name,
        mainCategory: {
          id: subCategory.mainCategory.id,
          name: subCategory.mainCategory.name,
        },
      },
      brand: {
        id: brandId,
        name: brand.name,
      },
      model: {
        id: modelId,
        name: model.name,
      },
    });

    return result;
  }

TypeORM - graphQL

더보기
  • nestjs는 graphQL을 지원하며 코드First 형식으로 작성할 수 있다.
@Entity() // Type ORM
@ObjectType() // GraphQL
export class Product {
  @PrimaryGeneratedColumn('uuid') // Primary Key 설정
  @Field(() => String) //GraphQl 설정
  id: string;
  
  @Column({ default: false })
  @Field(() => Boolean, { defaultValue: false })
  isDeliver: boolean;
  
  @ManyToOne(() => SubCategory)
  @Field(() => SubCategory)
  subCategory: SubCategory;

  @JoinTable()
  @ManyToMany(() => Color, (colors) => colors.products)
  @Field(() => [Color])
  colors: Color[];
}

 

// 리졸버 작성 예시
@Resolver()
export class ProductResolver {
  constructor(private readonly productservice: ProductService) {} // 의존성 주입

  @Query(() => Product) // 쿼리
  fetchProduct(@Args('productId') productId: string) { // 인자 받기
    return this.productservice.findOne({ productId });
  }

  @Mutation(() => Product) // 뮤테이션
  createProduct(
    @Args('createProductInput') createProductInput: CreateProductInput,
  ) {
    return this.productservice.create({ createProductInput });
  }

}

물리 삭제, 논리 삭제

더보기

물리삭제

  • DB에서 데이터 row를 완전히 삭제하는 것

논리 삭제

  • DB에서 row를 완전히 삭제하지 않고 삭제 여부를 확인할 수 있는 속성을 만들어 삭제여부를 확인할 수 있게 하는 것
논리 삭제 예시 deletedAt 속성이 만들어져있다.
  • 삭제한 정보를 복구할 필요가 있거나 따로 관리할 목적으로 사용한다.
  • Type ORM은 DeletedDate 속성을 지정하는 데코레이터를 달아줄 수 있다.
 @DeleteDateColumn()
  deletedAt: Date;
  • 데코레이터를 달아주면 softDelete와 restore 기능을 간단하게 사용할 수 있다.
 async delete({ productId }) {
    const result = await this.productRepository.softDelete({ id: productId });
    return result.affected ? true : false;
  }
  async restore({ productId }) {
    const result = await this.productRepository.restore({ id: productId });
    return result.affected ? true : false;
  }

회원가입 기능

더보기

쿠키, 세션

  • 쿠키와 세션 방식에 대한 간단한 정리
 

쿠키, 세션

HTTP는 connectionless, stateless 특성을 가지고있음 쿠키와 세션은 이 특성으로 인한 문제를 보완하기 위해서 사용한다. connectionless 클라이언트가 요청에 응답을 받으면 연결을 끝는 특성 Keep-alive라는.

namu445.tistory.com

  • 최근에는 쿠키와 세션방식 모두 JWT등을 이용한 토큰 방식을 같이 사용한다.
  • 클라이언트 정보를 서버에 저장하고 인증을 진행하는 경우 서버의 부하를 줄이기 위해서 별도의 DB를 사용하기도 한다. 
    • 서버의 부하는 스케일 업이나 스케일 아웃으로 대응할 수 있지만 stateful 상태로 인한 문제를 해결하기 어렵다.
  • 데이터를 디스크에 저장하는 DB(Mysql 등의 RDB)에 클라이언트 정보를 저장하기도 하지만 이 경우 서버에 부하가 늘어났던 것과 같이 DB에 부하를 줄 수 있기 때문에 REDIS와 같은 In-memory DB를 따로 사용하기도 한다. 
    • DB에서도 stateful 상태를 해결하기 어렵다. 때문에 아예 인증 DB를 따로 만들어 stateless 상태를 만든다.
    • In-memory DB를 사용하면 디스크에 저장하는 것에 비해 속도도 빠르다.
    • 휘발성은 문제가 되지 않는가? -> 인증 기록을 남기거나 관리하기 위해서 로그를 따로 저장하거나 인증 서버를 따로 두기도 한다.

DB의 사용자 테이블을 이용한 간단한 회원가입 기능

 @Mutation(() => User)
  createUser(
    @Args('createUserInput')
    createUserInput: CreateUserInput,
  ) {
    return this.userService.create({ createUserInput });
  }
import { Field, InputType } from '@nestjs/graphql';

@InputType()
export class CreateUserInput {
  @Field(() => String)
  name: string;

  @Field(() => Date)
  birthDay: Date;

  @Field(() => String)
  phoneNumber: string;

  @Field(() => String)
  email: string;

  @Field(() => String, { nullable: false })
  password: string;
}
 async create({ createUserInput }) {
    const { ...user } = createUserInput;

    const hasUser = await this.userRepository.findOne({ name: user.name });

    if (hasUser) {
      return new ConflictException('이미 가입된 사용자입니다.');
    }

    const result = await this.userRepository.save({
      ...user,
    });

기타

더보기

시나리오에 맞는 에러 코드 return 방법

  • nestjs에서는 상황에 맞는 에러코드를 간단히 보낼 수 있는 기능을 제공합니다.
// status 422
if (user === null) {
  throw new UnprocessableEntityException('없는 사용자입니다.');
}
    
// status 409    
if (hasUser) {
  return new ConflictException('이미 가입된 사용자입니다.');
}

Try Catch 기능을 대체할 수 있는 기능

  • nestjs에서는 Try Catch를 글로벌 설정으로 간편하게 사용할 수 있는 기능을 제공한다.
app.useGlobalFilters(new HttpExceptionFilter());

///////////////////////////////////////////////////

import { Catch, ExceptionFilter, HttpException } from '@nestjs/common';

@Catch(HttpException)
export class HttpExceptionFilter implements ExceptionFilter {
  catch(exception: HttpException) {
    const status = exception.getStatus;
    const message = exception.message;
    const response = exception.getResponse;

    console.log('===========================================');
    console.log('에러가 발생했어요!!');
    console.log(`에러코드: ${status}`);
    console.log(`에러메세지: ${message}`);
    console.log(`에러내용: ${response}`);
    console.log('===========================================');
  }
}