<사진은 전혀 상관없음. 그냥 멋있어서 넣어봤다.>
이번에 타입스크립트를 쓰면서 typeORM도 써보기로 했다. 익숙했던 sequelize를 떠나, 새로움을 찾아보기로 한 것이다. 데이터베이스 종류자체도 nosql로 해볼까 했는데, 너무 무리한 도전인가 싶어서 기존에 쓰던 mysql을 쓰기로 했다. (물론 데이터들 간의 관계성이 있기 때문이기도 함).
TypeORM을 쓰면서, 어려웠던 부분은 매우매우 매니하지만, 그중에서도 복잡하고 어려웠던 부분들을 골라서 한번 포스팅 해봐야지. 사실 블로그 기록들이 많지 않고, 전부 공식문서만 그대로 가져온 것들이라서 이해하기까지 시간이 오래걸렸는데, 다음에 내 블로그를 보는 사람들은 좀 쉬웠으면 좋겠다는 마음을 담아본다...하하
다대다 관계에서 가장 많이 참고 했던 블로그는 <TypeOrm의 ManyToMany 와 JoinTable 사용하기> 여기!
처음 typeorm을 쓸 때, 대규모의 데이터를 다룰 때 좋다고 들었다. 그 이유가 뭘까 하고 봤더니 위 두 개 키워드 때문이었다.
우선 내가 이해한 바대로 적어보자면, BaseEntity는 Active Record를 위한 Entity 클래스다. 모든 entity들이 이 BaseEntity를 상속받을 수 있고, 상속을 하게 되면 BaseEntity를 위한 메소드를 entity들이 사용가능하다. 여기서 메소드라 함은 findOne 같은 쿼리문을 이야기 한다.
기본적으로 mysql에서 다루는 쿼리문들이 있는데, 실제 mysql에서는 정해진 쿼리문을 써야하지만, sequelize 같은 경우에는 그러한 쿼리문을 정리해서 하나의 메소드로 쓸 수 있게 api를 만들어놨다.
그러나 typeORM같은 경우는 이러한 쿼리문들을 이용해서 나만의 메소드를 만들 수 있는 것이 특징인데, 사실 많이 쓰는 것들은 보편적인 메소드로 지정해주면 사용하기 편할 거다. 그래서 그런 메소드들을 BaseEntity에 지정해두고, 사용할 수 있게 한다. 그런데 종류가 몇가지 안되기 때문에, 그리고 데이터마다 혹은 로직마다 필요한 메소드는 다르기 때문에 사실상 쿼리문으로 나만의 메소드를 작성하게 된다. 따라서... 쓰다보면 기본 메소드를 사용하는 일이 별로 없다.
BaseEntity 같은 경우는 그래서 Entity 파일에서 static을 이용해 메소드를 작성하고, 그것을 컨트롤러에서 불러다 쓸 수 있다.
그러나 데이터가 크고, entity의 컬럼이 많고, 따라서 사용되는 메소드도 많다면?
이럴 때 Data Mapper pattern을 사용한다.
you define all your query methods in separate classes called "repositories", and you save, remove, and load objects using repositories.
공식문서에서는 data mapper pattern을 이렇게 설명하는데, 메소드를 분리된 클래스들에 정의하면, 그 클래스를 레포지토리라고 부른다. 따라서 메소드를 다른데다가 써놓고, 그것을 getRepository로 불러다 쓴다는 이야기다. 그러니까 아래 예시를 보면,
const timber = await User.findByName("Timber", "Saw");
여기서 User는 Entity다. 이 entity에 테이블 정보뿐만 아니라, 아래와 같은 메소드를 작성해 두었기 때문에,
static findByName(firstName: string, lastName: string) {
return this.createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName })
.andWhere("user.lastName = :lastName", { lastName })
.getMany();
}
컨트롤러에서 위와 같이 불러서 쓸 수 있다. 그러니까 이 방식은 Active Record 방식으로 적혀진 로직이다.
그러나 data mapper는 조금 다른데,
const userRepository = connection.getCustomRepository(UserRepository);
const timber = await userRepository.findByName("Timber", "Saw");
우선 userRepository라고 하는 작성된 클래스가 있고, 거기서 findByName 이라고 하는 메소드를 불러온다.
import {EntityRepository, Repository} from "typeorm";
import {User} from "../entity/User";
@EntityRepository()
export class UserRepository extends Repository<User> {
findByName(firstName: string, lastName: string) {
return this.createQueryBuilder("user")
.where("user.firstName = :firstName", { firstName })
.andWhere("user.lastName = :lastName", { lastName })
.getMany();
}
}
그 userRepository는 이렇게 생겼다. 위의 active record와 다르게, 테이블에 관한 내용은 없고 오로지 메소드만 지정되어 있는 별도의 파일이라고 생각하면 된다.
그래서 entity와 메소드, 적용하기 위한 컨트롤러를 나눠서 작성할 수 있고, 코드를 심플하게 만들 수 있기 때문에 대용량의 데이터를 다룰 때 편리하다고 하는 것 같다.
우선 프로젝트에서 하고 있는 데이터는 유저와 그룹(포스트)의 관계가 다대다이다.
그렇기 때문에 테이블에 데코레이션으로 @ManyToMany를 작성하는 것 외에도, 실제로 메소드로 사용하기 위해서는 아래와 같이 이렇게 조인테이블을 형성해줘야 했다. 그래야 postid와 userid로 구성된 테이블에 값이 들어갔다.
static JoinTheTable(postid: any, userid: any) {
return this.createQueryBuilder()
.relation(Post, "user")
.of(postid)
.add(userid);
}
그리고 유저와 댓글의 관계는 1:N이기 때문에 아래와 같이 다시 조인컬럼에 대한 메소드를 작성해줘야했다.
그리고 이러한 메소드는 post작업으로 인해, commentid가 생성되고 나서, 관계를 형성할 수 있도록 로직이 작성되어야 했다.
static joinUser(id: any, userid: any) {
return this.createQueryBuilder()
.relation(Comment, "user")
.of(id)
.set(userid);
}
뿐만 아니라, 1:N, N:1의 경우는 값을 불러올 때, left, inner등의 조인 쿼리문을 작성해야 했다.
static getComments(postid: string) {
return this.createQueryBuilder("comment")
.innerJoinAndSelect("comment.post", "post")
.where("post.id = :id", { id: postid })
.getMany()
// mysql> select * from comment inner join post on comment.post_id = post.id;
}
innerJoin 경우를 예로 들면, 내장 메소드가 있었는데, 모든 조인은 innerJoinAndSelect / innerJoin로 나뉜다. 이 차이점은 다음 링크를 확인 >> TypeORM으로 Join 하기
typeorm을 이용한 로직 작성은 이제 거의 끝났고, 마무리 작업만 남았다. 사실 공부하면서 재밌었다. 쿼리를 이용해서 메소드를 직접 customize하는 작업도 생각보다 좋았다. typescript를 이용한 작성이라 걱정이되긴 했는데, @types/ 이런 모듈 만든 사람들은 정말 칭찬해... 최대한 애니스크립트를 만들고 싶지 않아서, 타입을 많이 지정해주려고 했는데, 사실 마음 한 켠에선 제대로 못쓰고 있는 것 같아 아쉽다.
백엔드의 가장 큰 희열은 생각한 로직이 제대로 적용되어서 서버가 잘 돌아가고, 데이터가 잘 들어가고 나올 때인 것 같다. 물론 백엔드의 세상은 무궁무진하고 나로서는 발꼬락만 담궈본 거겠지만, 아직까지는 재밌다.
내일부터는 websocket을 하게 될 것 같은데, 그것도 재밌을 것 같아 기대된다. 파이널 화이팅.