Basic ORM-Sequelize

유웅조·2020년 5월 8일
0

Basic

목록 보기
10/13

ORM

Object Relational Mapping은 사물을 추상화시켜 이해하려는 OOP적 사고방식과 Data Model을 정형화하여 관리하려는 RDB 사이를 연결할 계층의 역할로 제시된 패러다임으로 RDB의 모델을 OOP에 Entity 형태로 투영시키는 방식을 사용한다.

시스템에 따라, 혹은 사용하는 Database 및 DB Connector에 따라 달라질 수 있는 데이터 매핑 구조를 객체지향형태로 통일시켜서 SQL 구조의 Database를 OOP 구조의 형태로 매핑시키려는 하나의 패러다임이라고 볼 수 있다.

OOP적 구조와 SQL 구조의 차이는 데이터를 다루는 방법에서 나타난다. OOP 적 구조에서 모든 데이터는 객체이며, 각 객체는 독립된 데이터와 독립된 함수를 지닌다.

반면 SQL에서 데이터는 테이블 단위이고 이 데이터를 조회하기 위한 명령어를 사용한다.
ORM은 각 테이블 또는 구분하고자 하는 데이터 단위로 객체를 구현하고 데이터 간의 관계를 형성한다.

ORM은 장, 단점이 있고, SQL 구조와 ORM 사이에는 계속되는 논쟁이 있다. 순서대로 장, 단점을 논해보자면 다음과 같다.

  • 장점

    우선 ORM을 사용하게 되면 SQL 언어를 잘 모르더라도 개발 언어를 사용해 데이터베이스와 상호작용이 가능하다. 이것이 장점인지, 단점인지는 모호하지만 어쨋든 진입 장벽에 대한 관점으로 보았을 때는 장점이다.

    더불어 객체지향으로 작성하게 되면 부수적인 코드와 중복, 가독성 등에도 많은 이점이 생긴다. 이는 전반적인 개발 생산성의 향상으로 이어진다.

    그리고 훗날 극단적으로 데이터베이스를 교체하는 작업이 이루어지더라도 추상적인 모델을 객체형태로 만들어놓았기 때문에 몇가지 데이터베이스에 대한 정보 수정만으로도 손쉬운 이전이 가능하다.

    마지막으로 ORM에서 제공하는 쿼리는 이미 몇 만줄이 넘어간 코드 양과 수많은 테스트로 (최적일지 아닐지는 모르지만) 안정성과 효율성을 보장한다.

  • 단점

    장점 중 첫번째로 언급했던 문제는 여러모로 단점으로도 작용하기 쉽다. 개발자가 자신이 사용하는 라이브러리의 구동 방식을 잘 모르게되면 부딫히게되는 한계는 명확하다. 만약 SQL에 숙련된 개발자라면 라이브러리가 제공하는 쿼리보다 상황에 맞추어 더욱 최적화를 할 수 있을 것이고, 통계와 같은 대형 쿼리 작성에서도 큰 무리 없이 튜닝해서 사용하는 것이 가능하지만 이 모든 것에는 SQL의 숙련도라는 문제가 걸려있다.

    두번째로 RDB의 데이터 타입이 Vendor마다 다르기 때문에 세밀한 것에서 맞지 않을 수 있다.

    세번째로는 객체지향과의 미묘한 불일치이다. 단적으로 객체 지향에서는 상속을 구현할 수 있지만 RDB에는 상속이 없다. 따라서 하위 타입 문제가 발생할 수 있따.

    마지막으로 객체-관계형의 임피던스 부정합에 대한 문제이다.

    객체-관계형의 임피던스 부정합(때로 패러다임 부정합으로 불리는)은 객체 모델과 관계형 모델이 서로 잘 어울리지 않는다는 것을 말한다. RDBMS는 데이터를 테이블 형태로 표현하는 반면 객체지향은 데이터를 상호연결된 객체의 그래프 형태로 나타낸다. 테이블 형태의 관계형 데이터베이스를 사용하여 객체의 그래프를 불러오고 저장하는 것은 5가지 부정합 문제를 드러낸다.

    1. Granularity
      데이터베이스 내에 상응하는 테이블의 수보다도 많은 클래스를 가지고 있는 객체 모델을 갖게 될 수도 있다. 객체 모델이 관계형 모델보다 좀 더 잘게 나뉘어 질 수도 있다.

    2. Subtypes(Inheritance)
      상속은 객체지향언어의 자연적 패러다임이다. 그러나 RDBMS는 상속을 전혀 정의하지 못한다.(일부 데이터베이스는 하위타입을 지원하지만 비표준이다.)

    3. Identity
      RDBMS는 Primary Key를 통해서 동일성의 개념을 정확히 정의한다. 그러나 프로그램 언어에서는 객체의 내용의 같음과 주소값의 같음이 모두 다르기 때문에 RDBMS와 동일성의 개념이 맞지 않는다.

    4. Association
      연관관계는 객체지향언어에서는 단방향 참조로 표현되는 반면에 RDBMS는 외래키를 사용한다. 그래서 양방향의 참조를 해야 한다면 두번의 관계를 지정해줘야 한다. 그리고 이러한 관계의 다양성을 객체 모델만을 보고서 지정할 수는 없다.

    5. Data navigation
      객체지향에서 객체의 데이터에 접근하는 방법은 근본적으로 관계형 데이터베이스에서 접근하는 것과 다르다. 하나의 객체가 관계가 있는 다른 쪽의 객체로 네트워크를 통해 데이터를 주고 받는다. 이러한 방법은 관계형 데이터베이스에서 데이터를 얻는데 효과적이지 않다. 기본적으로 RDBMS에서는 객체의 네트워크를 활용하기 전에 SQL 쿼리의 수를 최소화하여 JOIN을 사용해 몇 가지의 엔티티를 불러온 뒤, 목표점인 엔티티를 읽어들인다.

Sequelize

Association

  1. 1 : 1

예를 들어, FooBar 두개의 모델이 있다고 가정한다. 이 두 모델이 1:1 관계를 갖는다는 것은 각각의 모델의 한 레코드가 다른 테이블의 한 레코드와 관계가 있다는 것을 이야기 한다. RDBMS에서는 이를 Foreign Key를 만들어 서로를 연결지어줄 수 있다. 이러한 작업은 Sequelize에서도 가능한데, 문제는 누가 누구의 Foreign Key를 갖고 있느냐는 문제이다.

1:1 관계에서 둘 중 누가 갖고 있어도 사실 관계는 성립할 수 있다. 그러나 이 둘 사이에 1:1 관계가 있다고 할 때, 이러한 연결 관계가 필수인 것인지, 선택적인 것인지, FooBar 없이도 존재할 수 있는 것인지 혹은 그 반대인지 알 수 없다. 이러한 문제에 답을 하기 위해서는 어디에 Foreign Key를 두어야 할지를 정해야 한다.

Barfoo_id를 갖는 형태로 1:1 관계를 갖는다고 했을 때, 그 구현은 다음과 같다. Foo.hasOne(Bar); Bar.belongsTo(Foo); 이외에도 여러가지 옵션들에 의해 두 모델의 성격이 결정될 수 있다.

우선 onDelete, onUpdate 옵션을 통해 RESTRICT, CASCADE, NO ACTION, SET DEFAULT, SET NULL를 선택할 수 있다. 기본적으로 ON DELETE SET NULL ON UPDATE CACADE가 적용된다.

또 고려해야하는 것은 기본적으로 외래키 생성과정에서 NULL 옵션이 들어간다는 점이다. 따라서 Foreign Key를 필수 옵션으로 설정하고 싶다면, allowNull: false를 적용시켜야 한다.

  1. 1 : N

1:N 관계는 한개의 소스를 여러 개의 타겟과 연결 짓는 것을 말한다. 1:1 관계에서 Foreign Key의 위치를 정해야 했던 것과는 달리 1:N 관계에서는 한 가지 옵션 밖에 없다. 만약 다수의 Bar가 하나의 Foo를 바라보고 있다면, Barfoo_id를 갖고 있는 형태로 구현되어야 한다.

만약 여러 명의 Player가 하나의 Team에 소속되어 있다고 한다면, 그 구현은 다음과 같다. Team.hasMany(Player); Player.belongsTo(Team);

여러가지 옵션 사항은 1:1 관계에서의 옵션들과 동일하다.

  1. N : M

N:M 관계는 한 개의 소스가 여러 개의 타갯과 연결될 수 있으면서 동시에 그 타겟 또한 다른 소스들과 연결될 수 있는 관계를 말한다.

이 관계는 한개의 테이블에 한개의 Foreign Key를 넣는 것으로는 해결되지 않는다. 따라서 Junction Model 개념을 사용하는데, 이는 추가적인 모델 하나를 더 만들어 두개의 Foreign Key의 행을 갖고 둘 사이의 관계만을 추적하는 모델을 말한다. Join Table, Through Table와 같은 의미이다.

예를 들어, Movie, Actor 두 개의 모델이 있다고 가정했을 때 하나의 Movie에는 여러 명의 Actor가 나올 수도 있고, 한 명의 Actor는 여러 개의 Movie에 나올 수도 있다. 따라서 Join Table을 통해 이 둘의 관계를 매핑해줄 수 있고, 각각은 movie_id, actor_id를 갖게 된다.

Sequelize에서 그 구현은 다음과 같다.

Movie.belongsToMany(Actor, { through: 'ActorMovies' });
Actor.belongsToMany(Movie, { through: 'ActorMovies' });

through 옵션으로 주어진 ActorMovies라는 Join Table 모델을 자동으로 생성한다.

String을 통한 옵션 말고 직접적인 모델을 지정하는 것도 가능하다. 예를 들어,

const ActorMovies = sequelize.define('ActorMovies', {
    movie_id: {
        type: DataType.INTEGER,
        references: {
            model: Movie,
            key: 'id',
        },
    },
    actor_id: {
        type: DataTypes.INTEGER,
        references: {
            model: Actor,
            key: 'id',
        },
    },
});

Movie.belongsToMany(Actor, { through: 'ActorMovies' });
Actor.belongsToMany(Movie, { through: 'ActorMovies' });

이런 방식으로도 가능하다.

0개의 댓글