rails6 STI(단일 테이블 상속)에 대해서 알아보자

Posted by negabaro kim on Sunday, June 14, 2020 Tags: rails rails/active_record/model   3 minute read

용어정리

슈퍼클래스(=추상클래스)

서브클래스가 상속하고 있는 클래스 클래스와 테이블이 매핑되어 있음

레일즈에서 추상화 클래스를 만드는 기능은 없지만 개념적으로 이 포스트에 등장하는 슈퍼클래스들은 추상클래스임

서브클래스(=구상클래스/concreate class)

슈퍼클래스를 상속받는 클래스들을 일컬음 서브클래스용 테이블이 없고 슈퍼클래스 테이블을 공통으로 사용

이 포스트에 등장하는 서브클래스들은 구상클래스와 동일한 의미를 가짐

STI(Single Table Inheritance/단일 테이블 상속/単一テーブル継承)란?

슈퍼 클래스만이 테이블과 매핑된 상태로 서브 클래스들은 슈퍼 클래스를 상속하고 테이블을 공유해서 사용하는 패턴을 말함.

PofEAA에 나오는 상속 패턴중 하나로 레일즈에서 해당 패턴을 서포트하고 있다.

실무에 있을법한 몇가지 예제를 살펴보자.

예제1

고객을 관리하는 서비스를 상상해보자. 고객은 개인과,법인으로 나누어지고 개인은 개인과 개인사업자가 존재하며 법인은 기업과 관공서가 있다고 하자.

image

이러한 구성을 OOP관점으로 보면 고객(Customer) 클래스가 슈퍼클래스(추상클래스)이고 이 슈퍼클래스를 상속하는 복수의 서브클래스(구상클래스)가 존재하게 된다.(개인,법인,기업,관공서 등)

예제2

전국 아마추어 스포츠를 관할하는 A라는 단체가 있다. A단체에는 전국의 아마추어 스포츠 연맹들과 대학교/학원법인/전문대학들이 가맹/소속되어 있다. 대학교/전문대학들은 여러 운동부를 갖고 있고 운동부들은 특정 연맹에도 소속되어 있다.

위와 같은 구성일때는 조직(organization) 클래스가 슈퍼클래스 그 하위에 연맹,대학교,전문대학,학원법인 클래스가 서브클래스로 존재하게 될것이다.

이렇게 프로그래밍 관점으로는 클래스의 역할을 깔끔하게 나눌 수 있는데 RDB에서는 상속이라는 개념이 없으므로 레일즈와 같이 클래스와 DB를 1:1매핑 시킨 구성으로는 개운치가 않다.

이럴때 STI를 이용해 슈퍼클래스의 테이블을 서브클래스들이 공유해서 사용함으로 ORM에서도 어느정도 OOP개념을 녹여든 구성을 할 수 있게 된다.

STI의 장/단점은 아래와 같다.

STI 장점

서브클래스가 늘어나도 테이블 수는 늘지않음. 조인할 필요가 없음 레코드의 서브클래스 변경이 용이 Rails에서 서포트해주므로 코딩이 용이

STI 단점

특정 서브클래스에서 고유의 속성에 대한 NOT NULL제약을 걸 수 없음

특정 서브클래스에서만 참조하는 타 테이블의 외부키 제약을 실수로 다른 서브클래스를 참조해버리는 문제를 원천봉쇄할 수 없음

테이블안의 컬럼수가 늘어나기 쉬움

일부 서브클래스에서만 사용하는 컬럼이 늘면 NULL컬럼이 늘어나게 되 공간활용이 비효율적

STI는 Rails의 독자적인 기능이 아닌 하나의 패턴임

STI를 Rails의 독자적인 기능으로 알고 있는 사람이 많은데 OR매핑을 위한 범용적인 방법론의 하나임.

근거로 PofEAA를 보면 아래와 같은 문구를 발견할 수 있다.

Note, all the attributes for all the cases are kept in the same table. Read more: www.martinfowler.com/eaaCatalog/singleTableInheritance.html

PofEAA는 리팩토링으로 유명한 Martin Fowler이 저술한 패턴집으로 이 개념은 Rails외에도 Django에도 적용될 수 있는 패턴이다.

STI vs Polymorphic association

STI와 Polymorphic association를 비교하는 글들이 있는데 Polymorphic association은 관계에만 포커스를 두고있으므로 STI와의 비교는 무리가 있다고 봄.

polymorphic association을 참고

STI의 비교대상으로는 Class Table InheritanceConcrete Table Inheritance 이 적절함.

STI없어도 딱히 안불편한데..?

이 말은 ORM에서 상속이나 다형성과 같은 강력한 기능을 제대로 활용하지 않는다는 의미인데 자신의 OOP에 대한 이해도를 의심할 필요가 있음

컬럼수가 늘어나는게 보기 안좋아..

SELECT *할때 열이 길어져서 보기 불편해 -> SELECT *을 하지마..해결??

그렇지만 너무 컬럼이 길어지면 RDB의 테이블당 컬럼수 제한에 걸릴 가능성이 있긴하므로 주의가 필요하긴 함. 예를들어 MySQL의InnoDB는 1000개, PostgreSQL의 경우 1600개의 제한이 있음. STI를 사용하지 않으면 그만큼 테이블이 늘어나겠지만..

다른 방법으로 CTI,CTI로도 조합해 구성하는것도 가능하니 사용가능한 범위안에서 STI를 사용하는 선택지도 있음.

NULL컬럼이 늘어나면 테이블이 Sparse해져서 불필요한 영역이 늘어남

MySQL(InnoDB)이나 PostgreSQL등 최근 보급되는 DB는 NULL컬럼은 실제영역에 포함되지 않으므로 겉으로는 Sparse해도 내부적으로 저장/전송 효율이 나빠지지는 않음.

서브클래스에 독자적인 NOT NULL제약을 지정할 수 없는 문제

공통 테이블을 사용하므로 서브클래스에서 독자적인 제약을 걸 수 없는 문제가 있음. 그런데 이 경우는 굳이 STI가 아니더라도 존재하는 문제임.

예를들어 status가 완료일 경우 완료날짜 timestamp를 set하지 말것이라는 조건이 있을 경우 timestamp에 고정한 제약을 걸 수 없는건 마찬가지인 것.

이럴때 ActiveRecord의 Validation을 선언하는게 최선이라고 생각되어짐.