Skip to main content

개발 가이드

Lumie 백엔드 로컬 개발 환경 설정, 빌드·실행 방법, 새 도메인 모듈을 추가하는 절차를 안내합니다.


사전 요구사항

도구버전설치 방법
JDK21 이상Eclipse Temurin 권장
Gradle8.12 (Wrapper 사용)별도 설치 불필요
Docker최신로컬 인프라 실행용
kubectl최신Tilt 워크플로우 사용 시

JDK 버전 확인:

java -version
# openjdk version "21.0.x" ...

로컬 인프라 실행

백엔드를 로컬에서 실행하려면 PostgreSQL, Redis, RabbitMQ, MinIO가 필요합니다. Docker Compose로 한 번에 시작합니다.

# lumie-backend 루트에서
docker compose -f docker-compose.dev.yml up -d

빌드

전체 빌드

# 루트에서 전체 모듈 빌드 (테스트 포함)
./gradlew build

# 테스트 제외 빌드 (빠른 빌드)
./gradlew build -x test

배포용 JAR 빌드

# app 모듈의 bootJar 태스크 실행 → app/build/libs/lumie-backend.jar 생성
./gradlew :app:bootJar

빌드 결과물 위치: app/build/libs/lumie-backend.jar

특정 모듈만 빌드

# exam 모듈만 컴파일 확인
./gradlew :modules:exam:compileJava

# 특정 모듈 테스트만 실행
./gradlew :modules:exam:test

로컬 실행

# dev 프로필로 전체 애플리케이션 실행
./gradlew :app:bootRun --args='--spring.profiles.active=dev'

서버 시작 후 http://localhost:8080/actuator/health 에서 상태를 확인할 수 있습니다.


테스트

전체 테스트 실행

./gradlew test

통합 테스트 실행

@Tag("integration") 테스트는 기본 test 태스크에서 제외됩니다. TestContainers (Postgres)가 필요합니다.

./gradlew :app:integrationTest

테스트 전략

레이어도구특징
도메인 단위 테스트JUnit 5순수 Java, 의존성 없음
유스케이스 테스트JUnit 5 + Mockito포트를 Mock으로 교체
JPA 어댑터 테스트@DataJpaTest + Testcontainers실제 PostgreSQL 컨테이너
REST 통합 테스트@SpringBootTest + MockMvc전체 컨텍스트 로드
// 유스케이스 테스트 예시
@ExtendWith(MockitoExtension.class)
class ExamServiceTest {

@Mock LoadExamPort loadExamPort;
@Mock SaveExamPort saveExamPort;

@InjectMocks ExamService examService;

@Test
void 시험_생성_성공() {
// given
CreateExamCommand command = new CreateExamCommand("3월 모의고사", 30);
given(saveExamPort.saveExam(any())).willAnswer(inv -> inv.getArgument(0));

// when
ExamResponse response = examService.createExam(command);

// then
assertThat(response.name()).isEqualTo("3월 모의고사");
verify(saveExamPort).saveExam(any(Exam.class));
}
}

OpenAPI 스냅샷 갱신

BE 컨트롤러/DTO 변경이 FE 계약에 영향을 준다면, 스냅샷을 재생성하고 diff를 검토한 후 커밋합니다.

./gradlew :app:test --tests '*OpenApiSnapshotTest' -DupdateOpenApiSnapshot=true
# → app/src/test/resources/openapi/api-docs.json 갱신됨
# → 이 파일을 커밋하면 FE orval 코드젠이 새 계약을 반영

자세한 내용은 아키텍처 — OpenAPI / orval 계약을 참고하세요.


새 도메인 모듈 추가

아래 단계에 따라 새 도메인 모듈을 추가합니다. notification 모듈을 예시로 사용합니다.

1단계: 디렉터리 및 빌드 파일 생성

mkdir -p modules/notification/src/main/java/com/lumie/notification/{adapter/{in/{web,internal},out/{persistence,config}},application/{service,port/out,dto},domain/{entity,vo,repository,exception}}
mkdir -p modules/notification/src/test/java/com/lumie/notification
// modules/notification/build.gradle.kts
dependencies {
implementation(project(":libs:common"))
implementation(project(":libs:internal-api"))
implementation("org.springframework.boot:spring-boot-starter-data-jpa")
implementation("org.springframework.boot:spring-boot-starter-web")
}

2단계: settings.gradle.kts에 모듈 등록

// settings.gradle.kts — modules 목록에 추가
include("modules:notification")

3단계: app/build.gradle.kts에 의존성 추가

// app/build.gradle.kts
dependencies {
implementation(project(":modules:notification"))
}

4단계: 헥사고날 패키지 구조 작성

modules/notification/src/main/java/com/lumie/notification/
├── adapter/
│ ├── in/web/
│ │ └── NotificationController.java
│ └── out/persistence/
│ └── NotificationPersistenceAdapter.java
├── application/
│ ├── port/out/SaveNotificationPort.java
│ └── service/
│ └── SmsCommandService.java
└── domain/
├── entity/SmsMessage.java
└── exception/NotificationErrorCode.java

5단계: internal-api에 인터페이스 추가 (선택)

다른 모듈이 이 모듈을 호출해야 한다면 libs/internal-apiXxxService 인터페이스를 추가하고, adapter/in/internal/XxxServiceAdapter.java에 구현체를 작성합니다.

// libs/internal-api/src/main/java/com/lumie/notification/api/NotificationService.java
public interface NotificationService {
void sendSms(Long userId, String message);
}
// modules/notification/adapter/in/internal/NotificationServiceAdapter.java
@Component
@RequiredArgsConstructor
public class NotificationServiceAdapter implements NotificationService {
private final SmsCommandService smsCommandService;

@Override
public void sendSms(Long userId, String message) {
smsCommandService.send(userId, message);
}
}

6단계: Flyway 마이그레이션 추가

-- app/src/main/resources/db/migration/V__create_sms_messages.sql
CREATE TABLE sms_messages (
id BIGSERIAL PRIMARY KEY,
tenant_id BIGINT NOT NULL,
user_id BIGINT NOT NULL,
content TEXT NOT NULL,
sent_at TIMESTAMPTZ,
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

ALTER TABLE sms_messages ENABLE ROW LEVEL SECURITY;
ALTER TABLE sms_messages FORCE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON sms_messages
USING (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::bigint)
WITH CHECK (tenant_id = NULLIF(current_setting('app.tenant_id', true), '')::bigint);

코딩 컨벤션 요약

규칙내용
DTO 타입record 사용, LocalDateTime 금지 → Instant (UTC)
예외모듈별 BusinessException(ErrorCode) 서브클래스, raw RuntimeException 금지
주입@RequiredArgsConstructor 생성자 주입, 필드 @Autowired 금지
엔티티@Setter 금지, @Version (낙관적 잠금) 추가, BaseEntity 상속
정렬 쿼리@AllowedSortFields({...}) 선언 (미선언 필드 → 400)
외부 HTTP@Transactional 바깥에서 호출 (DB 풀 고갈 방지)
스케줄러@Scheduled + @SchedulerLock 필수 (다중 파드)
리스트 쿼리@EntityGraph / @JoinFetch (N+1 방지)

Gradle 팁

# app 모듈의 런타임 의존성 트리 출력
./gradlew :app:dependencies --configuration runtimeClasspath

# 빌드 캐시 활용
./gradlew :app:bootJar --build-cache

# 병렬 빌드
./gradlew build --parallel

IntelliJ IDEA 설정

  1. 프로젝트 열기: File > Open > lumie-backend 루트 디렉터리 선택 (Gradle 프로젝트로 임포트)
  2. JDK 설정: File > Project Structure > SDK > Eclipse Temurin 21
  3. 실행 설정 추가:
    • Run Configuration: Gradle
    • Tasks: :app:bootRun
    • Arguments: --args='--spring.profiles.active=dev'

관련 문서