moeum 프로젝트와 JitPack 배포

Always·2026년 3월 22일

Backend&Devops

목록 보기
16/17

moeum 프로젝트와 JitPack 배포

기획 의도

모음(moeum) 프로젝트는

"ERD와 DB를 하나로 MOEUM" 이라는 캐치프라이즈처럼,

ERD를 통해 실제 DB를 직접 조작하고 동기화할 수 있으면 편리하지 않을까라는 고민에서 출발했다.

기존 방식에서는 다음과 같은 불편함이 존재한다.

  1. ERD와 실제 DB의 분리
    MySQL Workbench에서는 ERD를 수정한 후 SQL을 추출하여 직접 실행해야 하는 번거로움이 존재한다.
  2. 과도한 DB 권한 부여
    개발자가 DELETE, DROP과 같은 위험한 권한을 가지고 있어 실수로 데이터 손실이 발생할 수 있다.
  3. ddl-auto=update의 한계

ddl-auto=update의 문제점

Spring Data JPA의 운영 환경에서 ddl-auto=update를 사용할 경우 다음과 같은 문제점을 내포하고 있어,

마이그레이션 과정에서 entity와 실제 DB 스키마 사이의 불일치가 발생한다.

  • 필드 삭제와 같은 위험한 변경은 DB에 자동 반영되지 않아 불필요한 스키마가 잔존하게 된다.
  • 컬럼 타입 변경 및 제약조건 변경이 의도대로 반영되지 않을 가능성이 존재한다.
  • 변경 이력이 체계적으로 남지 않아 스키마 진화 과정을 추적하기 어렵다.
  • 환경별 DB 상태 차이로 인해 동일한 코드라도 서로 다른 스키마가 유지될 수 있다.
  • 자동으로 실행되는 DDL이 운영 환경에서 예기치 않은 문제를 발생시킬 수 있다.

이러한 한계를 해결하기 위해 moeum은

ERD 기반 스키마 관리 + Migration 이력 관리 + 승인 기반 배포 구조를 제공한다.

핵심 도메인 구조

User
└── Workspace (환경 구분 / 승인 정책)
    └── Project (DB 연결 정보 + ERD JSON)
        ├── Schema (ERD 설계 → DDL 생성 → DB 반영)
        └── Migration (diff 계산 → 승인 → 적용)

Spring Boot Starter 도입 이유

모음의 핵심 기능 중 하나는

외부 Spring Boot 서비스의 DB 구조를 자동으로 수집하는 것이다.

하지만 마이크로서비스 환경에서는 각 서비스의 DB를 직접 등록하기 때문에 매우 번거로울 것 같았다.

이를 해결하기 위해

moeum-spring-boot-starter를 통해서 자동으로 연동할 수 있도록 maven에 라이브러리로써,도입하고자한다.

Starter 동작 방식

  1. 외부 서비스에 dependency 한 줄 추가한다
  2. application.yml에 API Key만 설정한다
    • 해당 API 키는 모음 서비스로부터 발급 받는다.
  3. 애플리케이션 기동 시 자동 실행된다
  4. DB 메타데이터를 수집한다
  5. moeum 서버로 전송한다
Spring Boot 실행
→ ApplicationReadyEvent 발생
→ DB metadata 수집
→ moeum 서버 등록

즉, 개발자는 별도의 코드 없이 DB를 자동으로 등록할 수 있다.

Starter 설계 고민

Starter를 설계할 때 두 가지 방식이 존재한다.

1. 서비스 내부에 moeum 내장

  • /moeum UI를 앱 내부에 제공한다
  • ERD / Migration / DDL 기능을 Starter에 포함한다

장점

  • 외부 서버 없이 독립 실행 가능 → 개발시 번거롭지 않다.
  • 네트워크 의존성 없음

단점

  • Starter가 매우 무거워진다
  • 멀티 서비스 통합 관리 불가능
  • 협업 기능 구현이 어려움
  • 서비스마다 UI가 따로 존재함

2. moeum 서버 + Starter

  • Starter는 DB 정보 수집 + 전송만 수행한다
  • 모든 기능은 moeum 서버에서 처리한다
외부 서비스
  → starter
    → DB metadata 수집
      → POST /api/integrations/register
        → moeum 서버 저장

2를 선택한 이유

  • Starter에는 DB 메타데이터 수집 및 전송 기능만 포함하여 의존성을 최소화하고, 서비스에 불필요한 UI 및 관리 기능이 포함되지 않도록 한다.

  • ERD 관리, DDL 생성, Migration 승인 및 이력 관리와 같은 기능은 moeum 서버에서 중앙 집중적으로 처리하여 책임을 분리한다.

  • 각 서비스 내부에 관리용 UI를 노출하지 않아도 되므로 운영 환경 보안과 관리 복잡도를 줄일 수 있다.

  • 기능 개선이나 정책 변경이 발생하더라도 중앙 moeum 서버만 업데이트하면 되므로 각 서비스의 Starter를 반복적으로 수정·배포할 필요가 줄어든다.

  • 여러 서비스의 DB 메타데이터를 한 곳에 모아 워크스페이스 단위의 통합 관리와 협업 흐름을 자연스럽게 지원할 수 있다.

내가 여기에서 2를 선택한 가장 큰 이유는 1을 채택하면 멀티서비스로써 실행이 안되기 때문이다.

따라서 moeum은 중앙 서버 + 경량 Starter 구조로 설계하고, Starter는 JitPack을 통해 배포한다.


JitPack을 이용한 Maven 배포

1. JitPack이란?

JitPack은 GitHub 저장소를 Maven 저장소처럼 사용할 수 있도록 해주는 서비스이다.

즉, GitHub repo를 통해서 Maven dependency로 바로 사용 가능하게 해주는 것이 JitPack이다.

2. 왜 사용하는가

기존 Maven Central 배포는:

  • GPG 서명 필요
  • Sonatype 등록 필요
  • staging 과정 필요
    한마디로 너무 귀찮다..

반면 JitPack은: GitHub에 push → 즉시 배포할 수 있어서 매우 간단하다.

3. 로컬 배포 (개발용)

그냥 로컬에서 사용할 땐 단순하게 아래처럼 세팅후 하면 되지만, 실제로는 4처럼 jitpack을 이용해서 라이브러리르 배포해야함

./gradlew publishToMavenLocal
repositories {
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation 'io.moeum:moeum-spring-boot-starter:1.0.0'
}

4. JitPack 배포 (공개)

repositories {
    maven { url 'https://jitpack.io' }
    mavenCentral()
}

dependencies {
    implementation 'com.github.{GitHub계정}:moeum-spring-boot-starter:1.0.0'

JitPack Maven 배포 가이드

프로젝트 구조

moeum-spring-boot-starter/
├── jitpack.yml                          ← JitPack 빌드 환경 정의
├── build.gradle                         ← maven-publish 설정
├── settings.gradle
└── src/main/
├── java/io/moeum/starter/
│   ├── config/MoeumAutoConfiguration.java    ← 자동 설정 진입점
│   ├── properties/MoeumProperties.java       ← moeum.* 프로퍼티 바인딩

│   ├── service/MoeumRegistrationService.java ← DB 메타데이터 수집 + 전송

│   └── client/
│       ├── MoeumClient.java                  ← HTTP 클라이언트
│       └── RegisterPayload.java              ← 전송 데이터 구조
└── resources/
└── META-INF/spring/
└── org.springframework.boot.autoconfigure.AutoConfiguration.imports

JitPack 배포 설정

1. jitpack.yml

jdk:
  - openjdk17
JitPack 기본 JDK는 8/11 → Java17 코드 에러 발생

2. build.gradle

plugins {
    id 'java-library'
    id 'maven-publish'
}

3. publishing 설정

publishing {
    publications {
        mavenJava(MavenPublication) {
            from components.java
        }
    }
}

이게 있어야 Maven 형태로 배포됨

4. JitPack 빌드

jitpack.io 접속 → Get it 클릭 → 빌드

아래처럼 git에 push후 jitpack사이트에서 리포지토리를 찾은 후 get it을 누르면, 빌드가 되어서,성공하면 Maven처럼 사용 가능

외부 서비스에서 사용

모음의 고객사나 외부 서비스에서 사용시에는 아래처럼 추가후 사용가능.

repositories {
    maven { url 'https://jitpack.io' }
}

dependencies {
    implementation 'com.github.{계정}:moeum-spring-boot-starter:v1.0.0'
    ```
moeum:
  server-url: http://...
  api-key: ...

}

실제 moeum-starter에서 일어나는 일

실행 전

@EventListener(ApplicationReadyEvent.class)

를 통해서 Spring Boot 완전히 뜬 이후 실행될 때까지 대기함 DataSource 연결이 준비되고,DB 접근 가능 상태여야하기 때문임.

실행 동작 흐름

앱 실행
→ ApplicationReadyEvent
→ DB 메타데이터 수집
→ payload 생성
→ moeum 서버 전송

DB 메타데이터 수집 방식

JPA Entity가 아니라 아래와 같이 실제 DB 기준으로 수집한다

핵심은 meta.getTables(catalog, schema, "%", new String[]{"TABLE"})이다
내부적으로 MySQL 기준 information_schema.TABLES를 조회하는 것과 동일한 원리로 내부에 어떤 테이블이 있는지를 수집한다.

 private List<RegisterPayload.TableInfo> collectTableMetadata() throws Exception {
        List<RegisterPayload.TableInfo> tables = new ArrayList<>();

        try (Connection conn = dataSource.getConnection()) {
            DatabaseMetaData meta = conn.getMetaData();
            String catalog = conn.getCatalog();
            String schema = conn.getSchema();

            try (ResultSet tableRs = meta.getTables(catalog, schema, "%", new String[]{"TABLE"})) {
                while (tableRs.next()) {
                    String tableName = tableRs.getString("TABLE_NAME");

                    // 시스템 테이블 제외
                    if (isSystemTable(tableName)) {
                        continue;
                    }

                    Set<String> primaryKeys = getPrimaryKeys(meta, catalog, schema, tableName);
                    List<RegisterPayload.ColumnInfo> columns = getColumns(meta, catalog, schema, tableName, primaryKeys);

                    tables.add(RegisterPayload.TableInfo.builder()
                            .name(tableName)
                            .columns(columns)
                            .build());
                }
            }
        }
        return tables;
    }

    private Set<String> getPrimaryKeys(DatabaseMetaData meta, String catalog, String schema, String tableName) {
        Set<String> pks = new HashSet<>();
        try (ResultSet pkRs = meta.getPrimaryKeys(catalog, schema, tableName)) {
            while (pkRs.next()) {
                pks.add(pkRs.getString("COLUMN_NAME"));
            }
        } catch (Exception e) {
            log.debug("[Moeum] PK 조회 실패: table={}, error={}", tableName, e.getMessage());
        }
        return pks;
    }

수집 내용

  • 테이블 목록
  • 컬럼
  • PK
  • 타입

타입 정규화

DB마다 다른 타입을 통일된 ERD로 만들기 위해
DB마다 타입이 다르기 때문에 통일한다.

VARCHAR→VARCHAR
TEXT→TEXT
BIGINT→BIGINT

서버로 전송

moeum의 서버에 POST /api/integrations/register(X-Api-Key: xxx
)를 통해서 db정보 및 사용자 api를 전송한다.
이를 통해서 사용자 서비스와 moeum의 서비스를 연동한다.

전체 흐름

외부 서비스
→ starter
→ DB metadata 수집
→ HTTP 요청
→ moeum 서버 저장
profile
🐶개발 블로그

0개의 댓글