Spring Boot + Gradle 기반의 멀티모듈 프로젝트를 구성하던 중,
공통 엔티티와 Enum, 도메인 객체를 모아둔 domain-common 모듈을 분리하여 관리하고 있었습니다. 프로젝트 구조는 다음과 같았습니다.
Wecam-project
├── domain-common
│ └── build.gradle
├── wecam-backend
│ └── build.gradle
├── wecamadminbackend
│ └── build.gradle
├── build.gradle (root)
└── settings.gradle
이 구조에서 domain-common은
- @Entity
- @Table
- @Enumerated
- Lombok 어노테이션 (@Getter, @Builder 등)
을 포함한 순수 도메인 모듈 역할을 하고 있었습니다.
2. 처음 마주한 에러
처음에는 단순히 domain-common 모듈이 잘 빌드되는지 확인하기 위해 아래 명령을 실행했습니다.
./gradlew :domain-common:build
그러자 바로 다음과 같은 에러가 발생했습니다.
Could not resolve all files for configuration ':domain-common:compileClasspath'.
> Could not find org.projectlombok:lombok:
처음에는 단순히 Lombok 의존성이 누락된 줄 알았습니다.
3. Lombok 버전 명시 → 그래도 실패
그래서 domain-common/build.gradle에 Lombok을 명시적으로 추가했습니다.
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'
그리고 다시 빌드를 시도했지만,
이번에는 에러가 JPA 관련 어노테이션 전부를 인식하지 못하는 형태로 폭발했습니다.
4. @Entity, @Table, @Getter 전부 인식 안 됨
에러 메시지는 다음과 같은 형태였습니다.
error: cannot find symbol
@Entity
symbol: class Entity
error: cannot find symbol
@Getter
symbol: class Getter
이 시점에서 확인한 사실은 다음과 같았습니다.
- Lombok 어노테이션 전부 인식 안 됨
- JPA 어노테이션 전부 인식 안 됨
- @Id, @Column, @ManyToOne 등도 전부 에러
즉, 컴파일러가 이 어노테이션들을 아예 모른다는 상태였습니다.
5. “다른 모듈에서는 잘 되는데?”라는 의문
여기서 가장 헷갈렸던 점은 이것이었습니다.
“같은 엔티티를 사용하는 다른 백엔드 모듈들은 문제없이 실행되는데
왜 domain-common만 빌드하면 전부 깨질까?”
그래서 이 문제를 기준으로 하나씩 정리해보기로 했습니다.
6. 원인 ① domain-common은 Spring Boot 모듈이 아니다
제가 찾아본 결과, 가장 중요한 핵심은 이것이었습니다.
domain-common 모듈은 애플리케이션 모듈이 아니다
즉,
- @SpringBootApplication이 없음
- 단독 실행 대상이 아님
- JPA 컨텍스트가 없음
그런데 domain-common 안에는 @Entity가 들어있습니다.
이 말은 즉슨,
domain-common을 단독으로 빌드하거나 실행하면
JPA 관점에서는 “관리되지 않는 엔티티”가 된다
라는 의미였습니다.
7. 원인 ② JPA 어노테이션은 “라이브러리”가 필요함
또 하나 놓치고 있었던 부분은 이것이었습니다.
잘못된 생각
Spring Boot 프로젝트니까
JPA 어노테이션은 자동으로 다 되는 거 아니야?
실제로는
- @Entity, @Table 등은 jakarta.persistence API
- Spring Boot가 아니라 라이브러리가 제공함
그래서 domain-common에는 아래 의존성이 반드시 필요했습니다.
implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
implementation 'jakarta.validation:jakarta.validation-api:3.0.2'
이걸 추가하지 않으면,
컴파일 타임에 어노테이션 자체를 인식하지 못합니다.
8. 원인 ③ spring-boot-starter-data-jpa를 넣었는데도 안 되는 이유
저는 처음에 이렇게 생각했습니다.
“그럼 그냥 spring-boot-starter-data-jpa를 넣으면 되겠네?”
그래서 추가했습니다.
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
그런데 또 이런 에러가 발생했습니다.
Could not find org.springframework.boot:spring-boot-starter-data-jpa:
이유는 간단했습니다.
- domain-common 모듈에는
Spring Boot BOM (dependency management) 이 적용되지 않음 - 그래서 버전이 자동으로 해석되지 않음
즉,
Spring Boot 플러그인을 적용하지 않은 모듈에서는
starter 의존성을 그냥 쓰면 안 된다
는 사실을 이때 명확히 알게 되었습니다.
9. 최종 정리: domain-common은 이렇게 구성하는 게 맞다
domain-common의 역할
- 엔티티 정의
- Enum
- 공통 VO, ID 클래스
- 실행 X
- Repository X
- Service X
domain-common에 필요한 의존성
dependencies {
// JPA 어노테이션
implementation 'jakarta.persistence:jakarta.persistence-api:3.1.0'
// Validation
implementation 'jakarta.validation:jakarta.validation-api:3.0.2'
// Lombok
compileOnly 'org.projectlombok:lombok:1.18.30'
annotationProcessor 'org.projectlombok:lombok:1.18.30'
}
Spring Boot Starter는 넣지 않는다
10. “그럼 실행할 때는 왜 문제 없었나?”
이 부분도 많이 헷갈릴 수 있는데, 이유는 명확합니다.
- 실제 실행은 wecam-backend, wecamadminbackend에서 이루어짐
- 해당 모듈들은:
- @SpringBootApplication 존재
- spring-boot-starter-data-jpa 포함
- @EntityScan 또는 패키지 스캔으로
domain-common의 엔티티를 정상 인식
즉,
domain-common은 실행 시점에는
“라이브러리처럼 로딩”되기 때문에 문제가 없었던 것입니다.
11. 정리하며 배운 점
이번 이슈를 통해 명확히 정리된 포인트는 다음과 같습니다.
- 멀티모듈에서 공통 domain 모듈은 단독 실행 대상이 아니다
- @Entity는 Spring이 아니라 jakarta.persistence가 제공
- Lombok은 반드시 annotationProcessor까지 설정해야 한다
- Spring Boot BOM은 모든 모듈에 자동 적용되지 않는다
- domain-common:build는 성공하지 않아도 프로젝트 설계상 문제는 아닐 수 있다
12. 마무리
이번 경험은 단순한 에러 해결을 넘어서,
멀티모듈에서 “역할 분리”를 어떻게 해야 하는지를 명확히 이해하게 해주었습니다.
특히,
- 공통 도메인 모듈 분리하려는 분
- JPA 엔티티를 별도 모듈로 관리하려는 분
- Lombok, JPA 인식 오류로 삽질 중인 분들께
분명 도움이 될 경험이라고 생각합니다.
[참고]
이 글은 실제 공개 프로젝트인 wecam 에서 발생한 에러입니다!
전체 코드가 궁금하다면 깃허브를 참고해주세요!!