개발자 경험 — Dev Mode, DevServices, Continuous Testing
프레임워크를 선택할 때 성능만 보면 될까? 하루 8시간 이상 마주하는 개발 환경의 쾌적함이 생산성에 미치는 영향은 벤치마크 숫자보다 클 수도 있다.
Quarkus 개발자 경험의 핵심
Quarkus는 "Developer Joy"를 공식 목표로 내세웁니다. 빌드 타임 최적화만큼이나 개발 중의 경험 을 중요하게 여긴다는 뜻입니다.
핵심 기능 네 가지를 정리하면 다음과 같습니다.
- Live Reload — 코드 수정 즉시 반영
- DevServices — 외부 서비스(DB, Kafka 등) 자동 프로비저닝
- Dev UI — 브라우저에서 앱 상태 확인
- Continuous Testing — 코드 변경 시 관련 테스트 자동 실행
Live Reload: 서버 재시작 없는 코드 반영
작동 방식
Quarkus의 Dev Mode(quarkus dev)에서는 코드를 수정하면 ** 다음 HTTP 요청이 들어올 때** 변경사항이 자동으로 반영됩니다.
# Dev Mode 시작
quarkus dev
# 또는
./mvnw quarkus:dev
중요한 것은 ** 구현 방식의 차이 **입니다.
[Spring Boot DevTools]
코드 수정 → 전체 ApplicationContext 재시작 → 빈 재생성 → 준비 완료
약 2~5초 (프로젝트 크기에 따라 더 걸림)
[Quarkus Dev Mode]
코드 수정 → 변경된 ClassLoader만 교체 → 변경된 부분만 재처리 → 준비 완료
약 0.5~2초 (대부분 1초 이내)
Spring Boot DevTools와의 차이
| 항목 | Spring Boot DevTools | Quarkus Dev Mode |
|---|---|---|
| 방식 | 전체 컨텍스트 재시작 | ClassLoader 교체 |
| 속도 | 2~5초 | 0.5~2초 |
| 상태 유지 | 세션 등 일부 유지 | 요청 시점에 반영 |
| 정적 리소스 | 별도 처리 | 동일 메커니즘 |
| 활성화 | 의존성 추가 필요 | 기본 내장 |
Spring Boot DevTools는 spring-boot-devtools 의존성을 추가해야 하고, Base ClassLoader와 Restart ClassLoader를 분리하여 Restart ClassLoader만 다시 로드하는 방식입니다. Quarkus는 Dev Mode 자체가 프레임워크에 내장되어 있어서 별도 설정 없이 바로 사용할 수 있습니다.
실제 체감
// UserResource.java를 수정하고 저장
@Path("/users")
public class UserResource {
@GET
@Produces(MediaType.APPLICATION_JSON)
public List<UserDto> listUsers() {
// 이 메서드를 수정하고 저장한 뒤
// curl localhost:8080/users 를 치면 바로 반영됨
return userService.findAll();
}
}
공부하면서 직접 써보니, 수정 후 브라우저를 새로고침하면 바로 반영되는 경험이 프론트엔드의 HMR(Hot Module Replacement)과 비슷했습니다. 자바 백엔드에서 이 정도 속도의 피드백 루프는 꽤 신선합니다.
DevServices: 킬러 기능
DevServices는 Quarkus의 가장 차별화된 기능이라고 생각합니다. 공부하다 보니 이 기능 때문에 Quarkus를 선택하는 개발자도 있다는 걸 알게 되었습니다.
무엇을 해주는가
DB나 메시지 브로커 같은 외부 서비스의 의존성만 추가하면, Docker 컨테이너를 자동으로 시작하고 연결 설정까지 해줍니다.
<!-- pom.xml에 PostgreSQL 의존성만 추가 -->
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-jdbc-postgresql</artifactId>
</dependency>
<dependency>
<groupId>io.quarkus</groupId>
<artifactId>quarkus-hibernate-orm-panache</artifactId>
</dependency>
이것만 추가하고 quarkus dev를 실행하면:
[Dev Mode 시작 로그]
2026-03-28 10:00:01 INFO [io.qua.dev.pos.dep] DevServices for PostgreSQL started
- Container: testcontainers/ryuk:0.5.1
- PostgreSQL: postgres:16
- JDBC URL: jdbc:postgresql://localhost:55432/quarkus
- Username: quarkus
- Password: quarkus
Quarkus application started in 2.134s
**application.properties에 DB 연결 정보를 한 줄도 적지 않았는데 **, PostgreSQL 컨테이너가 자동으로 시작되고 연결까지 완료됩니다.
지원하는 DevServices
| 서비스 | 확장 | 자동 시작 컨테이너 |
|---|---|---|
| PostgreSQL | quarkus-jdbc-postgresql | postgres |
| MySQL | quarkus-jdbc-mysql | mysql |
| MongoDB | quarkus-mongodb-client | mongo |
| Redis | quarkus-redis-client | redis |
| Kafka | quarkus-smallrye-reactive-messaging-kafka | redpanda |
| RabbitMQ | quarkus-smallrye-reactive-messaging-rabbitmq | rabbitmq |
| Elasticsearch | quarkus-elasticsearch-rest-client | elasticsearch |
| Keycloak | quarkus-oidc | keycloak |
| Infinispan | quarkus-infinispan-client | infinispan |
| Apicurio Registry | quarkus-apicurio-registry-avro | apicurio |
기존 방식과의 비교
[기존: 로컬 개발 환경 세팅]
1. docker-compose.yml 작성
2. docker-compose up -d
3. application-local.properties에 연결 정보 작성
4. 프로파일 설정 (-Dspring.profiles.active=local)
5. 애플리케이션 시작
6. 개발 끝나면 docker-compose down
[Quarkus DevServices]
1. 의존성 추가
2. quarkus dev
3. 끝
docker-compose.yml을 작성할 필요도, 연결 정보를 설정할 필요도 없습니다. 팀원이 프로젝트를 클론받고 quarkus dev만 실행하면 모든 외부 서비스가 자동으로 준비됩니다.
DevServices 커스터마이징
기본 설정으로 충분하지 않을 때는 커스터마이징도 가능합니다.
# application.properties
# PostgreSQL 버전 지정
quarkus.datasource.devservices.image-name=postgres:15
# 초기 SQL 실행
quarkus.datasource.devservices.init-script-path=init.sql
# 포트 고정 (기본은 랜덤 포트)
quarkus.datasource.devservices.port=5432
# DevServices 비활성화 (이미 DB가 있을 때)
quarkus.datasource.devservices.enabled=false
# Kafka 설정
quarkus.kafka.devservices.image-name=vectorized/redpanda:latest
quarkus.kafka.devservices.topic-partitions.orders=3
팀 개발에서의 장점
DevServices는 개인 개발 환경의 편의성을 넘어서, ** 팀 개발에서의 "환경 차이" 문제를 해결 **합니다.
- 신규 팀원 온보딩:
git clone→quarkus dev→ 바로 개발 시작 - 환경 일관성: 모든 팀원이 동일한 DB 버전, 동일한 초기 데이터
- CI/CD: 테스트 시에도 동일한 메커니즘 사용
Dev UI: 브라우저에서 앱 상태 확인
Dev Mode에서 http://localhost:8080/q/dev-ui로 접속하면 Dev UI 를 볼 수 있습니다.
제공하는 정보
| 섹션 | 내용 |
|---|---|
| Extensions | 설치된 확장 목록과 상태 |
| ArC Beans | 등록된 빈 목록, 스코프, 의존성 그래프 |
| Endpoints | REST 엔드포인트 목록 |
| Configuration | 모든 설정값과 출처 |
| Health | 헬스 체크 결과 |
| Build Steps | 빌드 파이프라인 시각화 |
| Continuous Testing | 테스트 결과와 실행 상태 |
Spring Actuator와의 비교
[Spring Actuator]
- 프로덕션 모니터링 용도
- /actuator/health, /actuator/metrics 등
- 별도 의존성 추가 필요
- 보안 설정 필요 (프로덕션 노출 주의)
[Quarkus Dev UI]
- 개발 시 디버깅/탐색 용도
- 그래픽 UI로 직관적
- Dev Mode에서만 활성화 (프로덕션에선 자동 비활성화)
- 확장마다 커스텀 페이지 제공
Dev UI는 프로덕션 모니터링이 아니라 ** 개발 중 디버깅 도구 **입니다. 빈이 제대로 등록되었는지, 엔드포인트가 올바르게 매핑되었는지, 설정값이 의도한 대로 적용되었는지를 브라우저에서 바로 확인할 수 있습니다.
유용한 Dev UI 기능들
[ArC Beans 페이지]
- 등록된 빈 수, 제거된 빈 수 확인
- 빈의 의존성 그래프 시각화
- 인터셉터 바인딩 확인
[Configuration 페이지]
- 모든 설정 키와 현재 값
- 설정 출처 (기본값 / application.properties / 환경변수 / 시스템 프로퍼티)
- 설정값 실시간 변경 (일부)
[Endpoints 페이지]
- 모든 REST 엔드포인트 목록
- HTTP 메서드, 경로, 파라미터
- 바로 테스트 호출 가능
Continuous Testing: 코드 변경 시 테스트 자동 실행
Quarkus의 Continuous Testing은 ** 코드를 수정하면 관련된 테스트가 자동으로 실행 **되는 기능입니다.
활성화 방법
Dev Mode에서 콘솔에 다음 메시지가 표시됩니다.
Tests paused
Press [r] to resume testing, [o] Toggle test output, [h] for more options>
r을 누르면 Continuous Testing이 시작됩니다.
동작 방식
[코드 수정 흐름]
UserService.java 수정
│
▼
Quarkus가 변경된 클래스와 관련된 테스트를 식별
│
├── UserServiceTest.java ← UserService를 직접 테스트
├── UserResourceTest.java ← UserService를 간접 사용
└── OrderServiceTest.java ← 변경과 무관, 실행 안 함
│
▼
관련 테스트만 자동 실행, 결과를 콘솔과 Dev UI에 표시
** 모든 테스트를 실행하는 것이 아니라, 변경에 영향받는 테스트만 실행 **합니다. 이 점이 단순히 --watch 모드로 전체 테스트를 돌리는 것과 다릅니다.
실행 결과
All 15 tests are passing (2 disabled), 3 tests were run in 1.2s.
Press [r] to re-run, [o] Toggle test output, [v] Toggle debug, [h] for more options>
Continuous Testing 설정
# application.properties
# 기본 활성화 (Dev Mode에서)
quarkus.test.continuous-testing=enabled
# 테스트 변경 감지 간격 (ms)
quarkus.test.poll-interval=500
# 특정 태그의 테스트만 실행
quarkus.test.include-tags=unit
# 특정 태그의 테스트 제외
quarkus.test.exclude-tags=integration,slow
# 테스트 실패 시 알림 (IDE 연동)
quarkus.test.display-test-output=true
기존 방식과의 비교
[기존: 수동 테스트 실행]
1. 코드 수정
2. IDE에서 해당 테스트 클래스 찾기
3. 테스트 실행
4. 결과 확인
5. 관련된 다른 테스트도 실행해야 하나 고민
6. 전체 테스트 실행 (시간 오래 걸림)
[Quarkus Continuous Testing]
1. 코드 수정
2. 자동으로 관련 테스트 실행 + 결과 표시
Quarkus CLI
Quarkus는 전용 CLI 도구를 제공합니다.
설치
# macOS
brew install quarkusio/tap/quarkus
# Linux (SDKMAN)
sdk install quarkus
# Windows (Chocolatey)
choco install quarkus
주요 명령어
# 프로젝트 생성
quarkus create app com.example:my-app
# 개발 모드 실행
quarkus dev
# 확장 검색
quarkus extension list --installable --search="kafka"
# 확장 추가
quarkus extension add smallrye-reactive-messaging-kafka
# 확장 제거
quarkus extension remove smallrye-reactive-messaging-kafka
# 빌드
quarkus build
# 네이티브 빌드
quarkus build --native
# 정보 확인
quarkus info
Spring Boot CLI와의 비교
# Spring Boot (Initializr 웹에서 생성하는 경우가 많음)
curl https://start.spring.io/starter.tgz \
-d dependencies=web,data-jpa,postgresql | tar -xz
# Quarkus CLI
quarkus create app --extension='resteasy-reactive,hibernate-orm-panache,jdbc-postgresql'
Quarkus CLI의 장점은 ** 개발 모드와 통합 **되어 있다는 점입니다. quarkus dev로 시작하면 Live Reload, DevServices, Dev UI, Continuous Testing이 모두 함께 동작합니다.
테스트 작성
Quarkus의 테스트는 @QuarkusTest로 시작합니다.
통합 테스트
@QuarkusTest
public class UserResourceTest {
@Test
public void testGetUser() {
given()
.when().get("/users/1")
.then()
.statusCode(200)
.body("name", is("홍길동"));
}
@Test
public void testCreateUser() {
given()
.contentType(ContentType.JSON)
.body("""
{
"name": "김철수",
"email": "kim@example.com"
}
""")
.when().post("/users")
.then()
.statusCode(201)
.body("name", is("김철수"));
}
}
@QuarkusTest는 실제 Quarkus 애플리케이션을 시작하고 테스트합니다. DevServices가 테스트에서도 동작하므로, ** 테스트용 DB를 별도로 준비할 필요가 없습니다 **.
네이티브 이미지 테스트
@QuarkusIntegrationTest
public class UserResourceIT {
// @QuarkusTest와 동일한 테스트 코드
// 네이티브 바이너리를 대상으로 실행됨
@Test
public void testGetUser() {
given()
.when().get("/users/1")
.then()
.statusCode(200);
}
}
@QuarkusIntegrationTest는 빌드된 아티팩트(JAR 또는 네이티브 바이너리)를 실행하고 테스트합니다. 네이티브 빌드에서 리플렉션 문제가 있는지 확인할 때 유용합니다.
모킹
@QuarkusTest
public class UserServiceTest {
@InjectMock
UserRepository userRepository; // 모킹된 빈이 주입됨
@Inject
UserService userService; // 실제 빈, 위 모킹된 빈을 사용
@Test
public void testFindById() {
// given
when(userRepository.findById(1L))
.thenReturn(Optional.of(new User("홍길동")));
// when
UserDto result = userService.findById(1L);
// then
assertEquals("홍길동", result.name());
}
}
개발 워크플로 종합
모든 기능을 조합한 실제 개발 흐름을 그려보면 이렇습니다.
[터미널]
$ quarkus dev
[자동으로 일어나는 일]
1. Quarkus 시작 (1~2초)
2. DevServices: PostgreSQL 컨테이너 자동 시작
3. DevServices: Redis 컨테이너 자동 시작
4. Dev UI 활성화 (localhost:8080/q/dev-ui)
5. Continuous Testing 대기
[개발 중]
1. UserResource.java 수정 + 저장
2. Live Reload: 0.5초 후 변경 반영
3. Continuous Testing: UserResourceTest 자동 실행
4. 브라우저: Dev UI에서 결과 확인
5. API 테스트: curl로 바로 확인
[개발 종료]
$ Ctrl+C
→ Quarkus 종료
→ DevServices 컨테이너 자동 정리
전체 과정에서 ** 수동으로 해야 할 일이 거의 없습니다 **. Docker Compose를 관리하거나, 테스트 DB를 세팅하거나, 서버를 재시작하는 번거로움이 사라집니다.
Spring Boot와의 개발 경험 비교 총정리
| 기능 | Spring Boot | Quarkus |
|---|---|---|
| 핫 리로드 | DevTools (재시작) | Dev Mode (ClassLoader 교체) |
| 외부 서비스 | docker-compose 수동 관리 | DevServices 자동 |
| 개발 UI | Actuator (프로덕션 용도) | Dev UI (개발 전용) |
| 연속 테스트 | 없음 (IDE 플러그인 의존) | Continuous Testing 내장 |
| CLI | Spring Initializr 웹 | Quarkus CLI |
| 설정 변경 | 재시작 필요 | 일부 실시간 반영 |
Spring Boot도 개발 경험이 나쁜 것은 아닙니다. 하지만 Quarkus는 개발자 경험을 프레임워크의 핵심 기능으로 설계했다는 점에서 차이가 있습니다.
정리
Quarkus의 개발자 경험 기능을 정리하면 다음과 같습니다.
- Live Reload: ClassLoader 교체 방식으로 Spring DevTools보다 빠른 반영
- DevServices: DB, Kafka, Redis 등을 의존성 추가만으로 자동 프로비저닝 (가장 인상적인 기능)
- Dev UI: 브라우저에서 빈, 엔드포인트, 설정, 테스트 결과를 한눈에 확인
- Continuous Testing: 변경된 코드에 관련된 테스트만 자동 실행
- Quarkus CLI: 프로젝트 생성부터 확장 관리, 빌드까지 통합
기억할 핵심: DevServices 하나만으로도 로컬 개발 환경 세팅에 들이는 시간을 크게 줄일 수 있습니다.
docker-compose.yml없이quarkus dev한 줄이면 모든 외부 서비스가 준비되는 경험은 한 번 써보면 돌아가기 어렵습니다.