JOOQ가 DB Dialect 차이를 해결하기 위해 선택한 추상화와 객체 모델링 #9
sangjun121
started this conversation in
인사이트
Replies: 0 comments
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
-
도입
이번 포스트는 jOOQ의 codegen 과정(DB 정보를 java 코드로 옮기는 과정, 즉 어플리케이션으로 옮기는 과정)에서 DB Dialect 차이를 어떻게 추상화하고, 이를 Java 객체 모델로 변환한 뒤 코드 생성까지 연결하는지 살펴본다.
jOOQ codegen은 단순히 DB에 접속해서 CREATE TABLE 정보를 읽고 문자열로 .java 파일을 찍어내는 구조가 아니다. DB마다 메타데이터를 제공하는 방식이 다르고, SQL Dialect마다 타입, 스키마, 키, 시퀀스, 루틴을 표현하는 방식도 다르다. jOOQ는 이 차이를 직접 JavaGenerator가 처리하지 않도록, 중간에 Database, AbstractDatabase, AbstractMetaDatabase, TableDefinition, ColumnDefinition과 같은 추상화 계층을 둔다.
즉, jOOQ의 codegen은 다음과 같은 흐름으로 이해할 수 있다.
DB별 메타데이터 조회 방식 -> Database 계층에서 추상화 -> TableDefinition, ColumnDefinition 같은 객체 모델로 변환 -> JavaGenerator가 객체 모델을 기반으로 Java 코드 생성이제 jOOQ가 DB Dialect 차이를 어떻게 감추고, Java 객체지향 설계를 통해 안정적인 코드 생성 파이프라인을 어떻게 구성하는지 살펴보자.
0. DB Dialect 차이가 왜 문제가 되는가?
DB는 모두 SQL을 사용하지만, 실제 내부 구현이나 메타데이터 제공 방식은 완전히 동일하지 않다.
예를 들어 PostgreSQL, MySQL, H2, SQLite는 모두 테이블과 컬럼을 가지고 있지만, 이를 조회하는 방식은 다르다. 즉, 테이블 목록을 가져온다는 요구사항은 같지만, DB마다 실제 쿼리나 메타데이터 접근 방식이 다르다.
만약 codegen 로직 안에서 이런 차이를 직접 처리한다고 생각해보자.
이런 코드가 JavaGenerator 안에 들어가면 어떤 문제가 생길까?
따라서 jOOQ는 DB Dialect 차이를 JavaGenerator가 직접 알지 않도록 설계한다. 이 문제를 해결하기 위해 등장하는 것이 Database 계층이다.
1. jOOQ의 핵심 아이디어: DB 차이를 먼저 객체 모델로 변환한다
jOOQ codegen의 핵심 아이디어는 다음과 같다.
즉, PostgreSQL이든 MySQL이든 SQLite든 최종적으로는 다음과 같은 자바 객체 모델로 변환된다.
예를 들어 DB에 다음과 같은 테이블이 있다고 해보자.
jOOQ 내부에서는 이를 대략 다음과 같은 객체 그래프로 바라본다.
중요한 점은 JavaGenerator가 더 이상 PostgreSQL의 pg_catalog에서 읽었는지, MySQL의 information_schema에서 읽었는지를 확인하지 않아도 된다는 것이다. JOOQ는 각 DB의 차이를 알 필요없이 단순히 해당 객체 모델을 순회하며 메타데이터를 가져오면 된다.
위의 예시에 이어 설명하자면, JavaGenerator는 단지
database.getTables(schema)로 테이블 정보를 불러오면 되고, 반환받은 TableDefinition들을 순회하면서 아래 메타데이터도 함께 불러오게 된다. 이를 기반으로 Java 코드를 생성한다.2. Database 인터페이스: codegen이 바라보는 공통 계약
jOOQ에서 메타데이터 조회의 핵심 추상화는 Database이다. Database는 codegen 입장에서 필요한 메타데이터 조회 기능을 제공한다. 예를 들면 다음과 같은 메서드들이 있다.
여기서 중요한 것은 반환 타입이다. ResultSet이나 DatabaseMetaData를 그대로 반환하지 않는다. 대신 jOOQ 내부의 메타 모델인 TableDefinition, ColumnDefinition, UniqueKeyDefinition 같은 객체를 반환한다. 즉, 해당 Database 추상체는 DB별 메타데이터 조회 결과를 JOOQ codegen이 이해할 수 있는 Definition 객체로 변환한다.
이 구조 덕분에 codegen 쪽에서는 DB별 세부 구현을 몰라도 된다. 발표에서 이 부분은 이렇게 설명할 수 있다.
3. AbstractDatabase: 공통 흐름을 제공하는 추상 클래스
Database가 인터페이스라면, AbstractDatabase는 DB 메타데이터 조회의 공통 흐름을 제공하는 추상 클래스이다. 여기서 자바의 중요한 설계 기법인 Template Method 패턴을 볼 수 있다.
예를 들어 테이블 목록을 가져오는 흐름은 대략 다음과 같다.
여기서 핵심은 getTables()와 getTables0()의 분리이다.
즉, 공통 흐름은 AbstractDatabase가 잡고, DB별 세부 조회 방식만 하위 클래스에 맡긴다. 이 구조를 단순화하면 다음과 같다.
그리고 DB별 구현체는 getTables0()만 구현하면 된다.
이게 Template Method 패턴이다. 상위 클래스가 알고리즘의 큰 틀을 정의하고, 하위 클래스가 특정 단계만 구현한다.
4. 왜 이런 구조가 필요한가?
DB마다 다른 것은 “테이블을 어떻게 조회하느냐”이다. 하지만 테이블 조회 이후의 공통 처리, 예를 들어 필터링, 정렬, 로깅, 캐싱은 DB마다 반복될 필요가 없다. 만약 이 공통 로직이 각 DB 구현체에 흩어져 있다면 다음과 같은 문제가 발생한다.
이렇게 되면 필터링 정책 하나가 바뀌어도 모든 DB 구현체를 수정해야 한다. jOOQ는 이를 피하기 위해 공통 처리는 AbstractDatabase에 모으고, DB별 차이만 getTables0() 같은 hook method에 남긴다.
즉, jOOQ는 이렇게 설계한다. 해당 부분이 우리가 지금까지 알아본 객체지향의 핵심이라고 볼 수 있다.
Beta Was this translation helpful? Give feedback.
All reactions