[Seqeulize] Association Setter AOP 적용하기

피누·2020년 3월 12일
0

Seqeuilize는 Association을 맺은 오브젝트에대해 자동으로 getter와 setter를 생성해준다. 생성된 getter/setter는 Association 이름이 함수명으로 포함된다.

먼저 시나리오를 살펴보자 Vessel(선박), VesselTypeCode(선박종류), Country(선박 국적) 총 3개의 엔티티가 존재한다. 엔티티의 연관관계는 다음과 같다.

Vessel이 VesselTypeCode와 Country를 하나씩 들고있는 구조이다. 해당 연관관계를 코드로 표현하면 다음과 같다.


class VesselTypeCode extends Sequelize.Model {
}

VesselTypeCode.init({
    id: {
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true,
        field: 'vessel_type_id'
    },
    name: {
        type: Sequelize.STRING,
        field: 'vessel_type_name'
    }
}, {sequelize});

class Country extends Sequelize.Model {
}

Country.init({
    id: {
        type: Sequelize.INTEGER,
        primaryKey: true,
        autoIncrement: true,
        field: 'flag_id'
    },
    name: {
        type: Sequelize.STRING,
        field: 'flag_name'
    }
}, {sequelize});


class Vessel extends Sequelize.Model {
}

Vessel.init({
    id: {
        type: Sequelize.STRING,
        primaryKey: true,
        field: 'imo'
    },,
    remarks: {
        type: Sequelize.STRING
    }
}, {sequelize});


Vessel.belongsTo(VesselTypeCode, {foreignKey: 'vessel_type', as:"VesselTypeCode"});
Vessel.belongsTo(Country, {foreignKey: 'flag', as:"Country"});

연관관계에따라 Vessel 인스턴스는 getVesselTypeCode, setVesselTypeCode, getCountry, setCountry 메소드가 자동으로 생성된다.

생성된 메소드에 이름은 관계의 이름(as의 value)로 생성된다.

Vessel 인스턴스에 VesselTypCode와 Country를 설정하는 코드는 다음과 같다

 Vessel.create(countryPk, typePk)
            .then(async vessel => {
                let country = await Country.findByPk(countryPk);
                let vesselType = await VesselTypeCode.findByPk(typePk);
                vessel.setCountry(country);
                vessel.setVesselTypeCode(vesselType);
                 
            })
            .then(vessel => vessel.save());

현재는 Vessel이 두개의 OneToOne 연관관계를 가지고 있어 직접 해주는 것도 나쁘지 않아보인다. 그러나 OneToOne 연관관계가 늘어날 수록 코드량은 두배씩 증가한다.(오브젝트를 찾아오고 set!) 또한 다른 엔티티의 OneToOne 관계 역시 마찬가지다. 추가로 인스턴스를 찾지 못했을 때의 로직과 같이 새로운 로직이 들어가면 중복되는 코드량은 훨씬 많아진다.

그래서 이 부분의 boilerplate를 제거하고자 OneToOne 관계 Set을 해주는 메소드를 하나 만들어보자.

만들 함수는 단순하다. OneToOne 관계를 가지는 두개의 오브젝트를 받아 하나의 오브젝트의 필드로 Set해준다. 여기서 오브젝트를 가지는 쪽을 Parent, 필드로 Set되는 오브젝트를 Child라 부르자.

우선 파라미터로 Parent, Child가 필요하다. Child는 인스턴스가 아니라 Model을 넣어주고 Model과 pk를 통해 해당 인스턴스를 찾아오게 하자.

문제는 Seqeulize에서 setter 메소드가 동적으로 생성된다는 점이다. 그래서 Child에 따라 메소드 이름이 동적으로 변한다. 그러나 걱정할 필요없다. 자바스크립트에서 함수는 일급객체로 파라미터로 넘겨줄 수 있다.

그럼 대략 메소드는 다음과 같은 형태를 가진다

setChild = async (parent, setFunction, child, childValue) => {
    let childInstance = await child.findByPk(childValue);
    setFunction(childInstance);
    return parent;
};

해당 메소드를 호출하는 쪽은 다음과 같은 형태로 호출 할 것이다.

Vessel.create(countryPk, typePk)
            .then(async vessel => {
		vessel = await setChild(vessel,vessel.setVesselTypeCode,VesselTypeCode, typePk);
  		vessel = await setChild(vessel,vessel.setCountry,Country,countryPk)    
            })
            .then(vessel => vessel.save());

위와 같은 형태로 호출하면 아래와 같은 에러가 발생한다.
TypeError: Cannot read property 'set' of undefined
에러의 발생원인을 이해하기 위해서는 자바스크립트의 this biding에 대해서 이해하고 있어야한다. this binding은 이전에 작성했던 [Javascript] This Binding를 참고하자.

따라서 호출하는 쪽에서 this객체를 바인딩해서 넘겨줘야 한다. 호출하는 쪽은 다음과 같은 형태를 가진다.

Vessel.create(countryPk, typePk)
            .then(async vessel => {
		vessel = await setChild(vessel,vessel.setVesselTypeCode.bind(vessel),VesselTypeCode, typePk);
  		vessel = await setChild(vessel,vessel.setCountry.bind(vessel),Country,countryPk)    
            })
            .then(vessel => vessel.save());
profile
어려운 문제를 함께 풀어가는 것을 좋아합니다.

0개의 댓글