지금까지 User라는 하나의 Entity만 사용하여 ORM을 처리했습니다. 이제 User와 OneToMany 관계인 Product Entity를 생성하여 유효한 사용자가 Product Entity를 생성하고 정보를 조회하는 기능을 구현해보겠습니다.
먼저 Relation을 제외한 Product Entity를 생성해줍니다.
@Column
내에서 먼저 type을 정하고, name
, nullable
, length
와 같은 설정을 해줄 수 있습니다.name
은 Database 상에서 저장되는 Column의 이름을 설정해줍니다.CURRENT_TIMESTAMP()
를 default로 설정해줍니다.Default 값을 CURRENT_TIMESTAMP(6)
으로 지정하면 ER_INVALID_DEFAULT: Invalid default value for 'created_at'
에러가 나타나며 Database 연결이 되지 않았습니다. 구글링을 통해 MySQL 설정에서 sql_mode
에서 "NO_ZERO_IN_DATE,NO_ZERO_DATE"를 제거하는 방법을 시도해봤는데, 해결되지 않아 CURRENT_TIMESTAMP()
로 변경하여 시도하니 잘 되었습니다. Column Type은 datetime
과 timestamp
두 경우 모두 같은 결과가 나왔습니다.
Product Entity를 만들었으니, User와 Product의 관계를 설정해줘야 합니다. 한 명의 User가 여러 Product를 제작할 수 있고, 하나의 Product는 자신을 제작한 한 명의 User만 관계가 형성되므로 OneToMany 관계입니다. TypeOrm에서 관계 형성은 다음과 같이 해줍니다.
하나의 User에 대응하여 다수의 Product가 관계될 수 있으므로, @ManyToOne
Decorator를 사용합니다. @ManyToOne
Decorator의 parameter를 살펴보면,
Product
Entity 안에 User
Column이 들어가는 것이므로 Type을 Users
라고 설정해줍니다.User
에서 Product
에 접근하기 위해 user.products
를 사용한다는 것을 명시합니다. onDelete
와 onUpdate
를 설정해주었는데, user
의 Data를 지우거나 수정해도 그와 연관된Product
의 Data가 영향받지 않도록 No Action
으로 지정해줍니다.User
Entity에도 @OneToMany
Decorato로 Column을 생성합니다. User
에서 저장되는 Product
는 다수이므로 Column Type을 Product[]
과 같이 배열로 설정합니다.
User
와Product
의 관계를 형성해주었으니 이제Product
를 생성해봅시다.
Product
를 생성하기 위해 Product의 이름, 색깔, 사이즈가 필요합니다. 회원가입을 할 때 사용했던 것처럼 Product
의 데이터 전송 객체(Dto)를 작성해줍니다.
Product
와 관련된 Request에 대응하는 Controler를 작성합니다. Guard와 Pipe를 활용하여 유효성 검사를 한 후 Service에서 Data를 처리해줍시다.
생성자에서 ProductService
를 호출하며 Dependency Injection을 해줍니다. 우리가 Product
를 생성하기 위해 필요한 Data는 생성하는 User
의 정보와 Product
의 Data입니다.
@UseGuards
를 통해 유효한 JWT를 가진 User
가 Request를 보내는지에 대한 판별을 하고, 유효하다면 JWT Payload에 포함되어 있는 User
의 정보를 @GetUser
데코레이터를 통해 user
Parameter로 추출할 수 있습니다.@Body
에서 생성하고자 하는 Product
의 Data를 Dto 타입에 맞게 가져오고, ValidationPipe
로 유효성 검사를 해줍니다. User
정보와 Product
정보로 Service의 createProduct
함수를 실행하여 Response로 반환합니다.ProductService에서
user
와productCreatDto
Data로Product
를 생성하여 DB에 저장하는 로직을 작성합니다.
product
Type에 맞게 user
, productCreateDto
의 Data와 createUserId
가 포함된 객체를product
에 할당해줍니다. 그 후 product
를 create
하여 save
하여 Database에 저장합니다.
PostMan으로 Login하여 발급받은 JWT를 Header에 넣고 Product를 생성해봅시다.
User 정보와 Product 정보가 반환되며 제대로 생성되는 것을 확인할 수 있습니다!
Product
를 생성했으니 이제 다양한 조건에 따라 적절하게 QueryBuilder를 사용하여Product
의 정보를 불러와봅시다.
특정 User가 생성한 Product의 정보를 가져오는데, User의 Information은 제외하고 연관된 Product의 정보만 불러오기 위해
InnerJoin
을 사용합니다.
관계된 User의 정보도 불러오고 싶은 경우
InnerJoinAndSelect
를 사용합니다.
단순히
InnerJoinAndSelect
만 사용하니 비밀번호를 포함한 모든 User의 정보를 불러왔습니다. 불필요하거나 민감한 정보를 제외하기 위해InnerJoin
과Select
를 따로 사용해줍니다.
ProductName과 ProductColor, 그리고 10을 곱한 ProductSize로 새로운 ProductCode Column을 만들고 싶습니다.
CONCAT
,ROUND
,AS
를 활용해봅시다.GetMany
를 사용하면 Database에 있는 Data만 불러오기 때문에GetRawMany
를 사용합니다.
NestJS를 사용한지 1주일 남짓이 지나며 어느정도 큰 그림이 그려지기 시작했습니다. Architecture뿐 아니라 Pipe, Intercepter, Guard, Exception Filter, Swagger 등 여러가지 기능에 대해 살펴보고, 실습을 통해 조금이나마 활용하며 이해를 도왔습니다.
이제 협업 프로젝트를 본격적으로 진행하게 되는데, 가장 걱정되는 점은 Jest 방식의 Unit Test와 QueryBuilder를 활용한 Data 처리 방식입니다. Django에서 ORM을 사용할때는 SQL문을 깊게 이해하지 않아도 활용가능했지만, TypeOrm은 SQL문을 직관적으로 사용하는 느낌이 강하기에 SQL Query에 대해 많이 공부해야겠다고 생각했습니다.
NestJS Unit Test는 사실상 1주일동안 가장 시간을 많이 투자한 부분입니다. Unit Test를 하기 위해서는 Test Code에 대해 어떤 Input과 Output이 도출되는지와 Test Function에 대한 의미를 정확히 파악해야합니다. jest.fn(), jest.spyOn(), mockImplemen, expect().ToHaveBeenCalledWith()등과 같은 함수들의 동작방식을 이해하고 제대로 사용하는 데도 시간이 걸렸고, 테스트 방식도 spyOn을 사용하거나 UserStub과 같이 가짜 Data를 활용하거나, 함수의 호출 여부만 확인하는 등 천차만별이었기 때문에 많이 헤맸습니다.
그래도 어느정도 방향성은 잡게되었고, 이제 본격적으로 프로젝트를 진행하며 많이 성장할 것이라고 생각합니다. 협업이 끝날 즈음에는 NestJS, TypeOrm, Jest 셋 모두 능숙하게 다룰수 있도록 더 노력해보려 합니다.