--- kind: CONCEPT slug: independent-flyway-streams title: 독립 Flyway 스트림과 baseline version 0 topic: schema-ownership-and-capability-streams project: clean-architecture-backend-template status: 게시 전 sourceRevision: 21234e38cdb9a926cbc92bb97a2aee2e4a7d2916 basisVersion: Flyway 11.14.1 · PostgreSQL 16 · 리비전 21234e38 rootTreeNode: concept:independent-flyway-streams evidenceCapturedOn: 2026-09-01 assets: - key: independent-flyway-streams file: ../../../final/evidence/rendered/independent-flyway-streams.svg evidence: - ../../../final/evidence/raw/independent-flyway-streams.txt source: - 원본 분석 절은 final/document.md#4-1 · final/document.md#a05 §8.3 이다. --- # 독립 Flyway 스트림과 baseline version 0 능력마다 자기 마이그레이션 위치와 자기 히스토리 테이블을 갖는다. 그래야 각 능력의 버전 계열이 독립적이고, 켜지 않은 능력은 테이블도 히스토리도 남기지 않는다. ## 관계 - **두 트리가 다 V1부터 번호를 매겨 공유 history가 하나를 건너뛸 수 있었다** 이 구조가 필요해진 사례다. - **마이그레이션 스트림은 자기 history 테이블을 갖는다** 이 개념을 규칙으로 옮긴 것이다. - **capability_schema_registry — 스키마 적용과 사용 승인의 분리** 스트림이 설치되었다는 사실을 기록하는 쪽이다. ## 본문 이 저장소에 Flyway 스트림이 여덟 개이고 각각 자기 history 테이블을 갖는 이유의 설명이다. 뻔한 해법(디렉터리를 primary location 목록에 추가)이 틀린 이유는 두 트리가 다 V1부터 번호를 매기고 공유 history가 두 V1을 같은 버전으로 만들어 하나를 거부하거나 resolution 순서에 따라 건너뛰기 때문이다. ## 뻔한 해법이 틀린 이유 :::evidence key="independent-flyway-streams" alt="분석 문서 final/document.md 에서 이 기록의 근거 절을 그대로 잘라낸 18줄. 코드베이스를 측정한 것이 아니라 원본 판정이 무엇을 적었는지를 보여 준다." caption="final/document.md 발췌 — 18줄" zoom="true" ::: ## baselineOnMigrate 가 필요한 이유 core가 이미 채운 스키마 위에 자기 history를 만들어야 하므로, 없으면 Flyway가 "history 없는 비어있지 않은 스키마"라며 거부한다. ## baseline이 0이어야 하는 이유 더 높은 baseline은 그 스트림의 마이그레이션을 건너뛴다 — 이 설정이 보통 두려워하는 실패 모드다. :::note 마이그레이션 레인 미실행 ::: ## 왜 위치만 더하면 안 되는가 ```java /** * The notification schema as its own Flyway stream, with its own history table. * *
The migrations live in {@code db/migration/jpa/notification-platform} while the primary Flyway * location is {@code db/migration/postgresql}, so nothing applied them. The obvious fix — adding * the directory to the primary location list — is the wrong one: both trees number from V1, and a * shared history table would make {@code V1__notification_platform_core} and {@code * V1__initial_schema} the same version. Flyway would either refuse the second or, worse, record one * and skip the other depending on resolution order. */ ``` 두 트리가 각자 V1 부터 번호를 매긴다. 히스토리를 공유하면 두 개의 V1 이 같은 버전이 된다. Flyway 의 반응이 두 가지인 것이 문제다. 거절하면 발견되지만, 해석 순서에 따라 하나를 기록하고 다른 하나를 건너뛰면 발견되지 않는다. ## 별도 스트림이 만드는 것 ```java /** *
A separate stream with {@code flyway_jpa_notification_history} keeps the two version series * independent, which is what makes the capability genuinely optional: a deployment that never * enables notifications has no notification history table and no notification tables, and a * deployment that enables it later applies a stream that starts at its own V1. */ ``` ```java public static final String LOCATION = "classpath:db/migration/jpa/notification-platform"; public static final String HISTORY_TABLE = "flyway_jpa_notification_history"; ``` 능력을 켜지 않은 배포에는 그 능력의 히스토리 테이블도 없다. 나중에 켜면 자기 V1 부터 적용한다. ## 선택 가능성이 스키마 수준에서 성립한다 능력이 선택적이라는 말이 코드 수준에서만 참이면, 스키마에는 쓰지 않는 테이블이 남는다. 스트림이 독립적이면 그 테이블도 생기지 않는다. :::tip 이 구조에서 "이 능력을 쓰지 않는다"는 것과 "이 능력의 스키마가 없다"는 것이 같은 사실이 된다. 두 사실이 갈라지면 나중에 어느 쪽이 진실인지 알 수 없다. ::: ## 버전 공간을 공유하는 경우 같은 Flyway 위치 목록에 병합되는 트리들은 하나의 버전 공간을 공유한다. 샘플 컴포지션이 두 위치를 병합하므로, 한쪽이 이미 쓴 번호를 다른 쪽이 쓸 수 없다. ```sql -- Numbered V9, not V8 or V7. `db/migration/postgresql` and `db/sample-migration` are merged into -- one Flyway location list by the sample composition root, so the two trees share a single version -- space: the sample already owns V2, V7 and V8. A duplicate version is not a merge conflict Flyway -- resolves — it refuses to start at all. ``` 번호를 고를 때 다른 트리를 봐야 한다는 뜻이고, 그 사실이 마이그레이션 헤더에 적혀 있다. ## 두 위치가 같은 테이블을 만들 때 ```sql -- Both locations create capability_schema_registry, so both have to grow the column. A deployment -- that migrated through only one of them would otherwise still reject a stream path over 32 -- characters, and the error would name the column rather than the reason. ``` 한쪽만 고치면 그 경로로만 마이그레이션한 배포가 옛 제약을 유지한다. 그리고 그때의 오류 메시지는 컬럼 이름을 부르지 이유를 말하지 않는다.