Backend 05
Initializr, Gradle, Dependencies로 스프링 프로젝트 시작하기
프로젝트를 만들기도 전에 Group, Artifact, Gradle, Dependencies가 한꺼번에 나와서 무엇이 필수인지 모르겠다.
근거 · 교안 p29-p39
1장: Initializr: 프로젝트의 첫 단추
솔라의 손가락이 노트북 트랙패드 위에서 멈칫했다. 화면에는 ‘start.spring.io’라는 주소와 함께 여러 개의 입력 칸이 가득했다. ‘새 프로젝트 시작하기’라는 안내 문구를 보고 들어온 사이트였지만, 시작은커녕 길을 잃은 기분이었다.
Project, Language, Spring Boot, Group, Artifact, Name, Packaging, Java, 그리고 오른쪽에는 Dependencies라는 거대한 검색창까지. 마치 중요한 계약서에 서명하기 전, 수십 개의 조항을 한꺼번에 검토하라는 요구를 받은 것 같았다.
“언니, 잠깐 이것 좀 봐.”
솔라의 목소리에 옆에서 조용히 책을 읽던 루나가 고개를 들었다.
“그냥 스프링으로 프로젝트 하나 만들어보려고 했는데, 뭘 이렇게 많이 물어보는지 모르겠어. Group? Artifact? Gradle? 이게 다 뭐야? 심지어 오른쪽엔 Dependencies를 추가하라는데, 뭘 알아야 추가를 하지. 이거 다 제대로 입력 안 하면 프로젝트가 안 만들어지는 거야?”
솔라의 말에는 조바심과 막막함이 섞여 있었다. 마치 요리를 시작하기도 전에 레시피에 적힌 낯선 향신료 이름들에 압도당한 사람 같았다. 모든 칸을 완벽하게 채워야만 다음 단계로 나아갈 수 있을 거라는 압박감이 느껴졌다.
루나는 솔라의 노트북 화면을 잠시 들여다보았다. 여러 개의 빈칸들이 마치 각자의 중요성을 주장하듯 나란히 정렬되어 있었다. 루나는 아무 말 없이 화면을 가리켰다.
“저기, 화면 맨 아래쪽에 있는 초록색 버튼 이름이 뭐지?”
“‘GENERATE’라고 쓰여있네. 생성한다는 뜻이잖아.”
“응. 다른 건 아무것도 건드리지 말고, 그냥 그 버튼만 한번 눌러볼래?”
솔라의 얼굴에 의아한 표정이 스쳤다. “아무것도 안 채우고? 그래도 돼?”
“일단 한번 해보자. 무슨 일이 생기는지.”
솔라가 반신반의하며 ‘GENERATE’ 버튼을 클릭하자, 잠시 후 화면 하단에 demo.zip이라는 파일이 다운로드되었다. 너무나 간단하게 벌어진 일이라 솔라는 오히려 당황했다.
“어… 파일이 받아졌어.”
“한번 열어볼까?”
솔라는 다운로드 폴더에서 demo.zip 파일의 압축을 풀었다. demo라는 이름의 폴더가 나타났고, 그 안에는 src, .gradle, gradlew 같은 생소한 이름의 하위 폴더와 파일들이 들어 있었다. src 폴더를 더 열어보니 main, test가 나왔고, 그 안에는 또 java, resources 폴더가 이어졌다. 하지만 정작 코드가 들어있을 법한 파일은 거의 눈에 띄지 않았다.
“이게 뭐야? 그냥 빈 폴더랑 파일 몇 개가 전부인 것 같은데.”
솔라는 폴더 구조를 훑어보며 중얼거렸다. 기대했던 ‘프로젝트’와는 거리가 먼, 텅 빈 구조물에 가까웠다.
“맞아. 지금 솔라 네가 보고 있는 게 이 웹사이트가 우리에게 해주는 가장 기본적인 일이야.”
루나가 차분하게 말했다.
“우리가 앞으로 코드를 채워 넣고, 필요한 그림이나 설정 파일들을 담아둘 집의 ‘뼈대’를 만들어준 거지. 아직 가구도, 벽지도, 아무것도 없지만 방은 나뉘어 있고, 문과 창문은 제자리에 달려있는 상태라고 할까.”
‘뼈대’. 그 단어를 듣는 순간, 솔라의 머릿속을 채우고 있던 복잡한 입력 칸들이 한쪽으로 밀려나는 느낌이 들었다. Group, Artifact, Dependencies 같은 것들을 하나도 이해하지 못했지만, 어쨌든 무언가가 만들어졌다. 비어있지만, 분명한 구조를 가진 무언가가.
“아… 그럼 이 사이트의 원래 역할은 이 ‘틀’을 만들어주는 거구나. 다른 복잡한 것들은 이 뼈대에 뭔가를 추가하기 위한 선택이었던 거고?”
“정확해. 우리는 아무것도 선택하지 않았으니까, 그냥 가장 기본형의 뼈대를 받은 거야.”
솔라는 다시 모니터 화면으로 시선을 돌렸다. 아까와 똑같은 화면이었지만, 더 이상 모든 칸이 똑같은 무게로 느껴지지 않았다. 화면 전체가 ‘프로젝트의 뼈대를 어떻게 만들어 드릴까요?’라는 하나의 큰 질문으로 보이기 시작했다. 수많은 입력 칸들은 그 질문에 대한 세부 옵션일 뿐이었다.
그제야 솔라는 자신이 무엇을 놓치고 있었는지 깨달았다. 시작부터 모든 것을 알아야 한다고 생각했지만, 사실은 가장 핵심적인 기능 하나만으로도 첫 단추를 꿸 수 있었다.
생각이 정리되자 새로운 질문이 자연스럽게 떠올랐다. 솔라는 방금 압축을 푼 ‘demo’ 폴더를 마우스로 가리켰다.
“언니, 이 뼈대가 뭔지는 알겠어. 그런데 왜 하필 폴더 이름이 ‘demo’야? 내가 만약 ‘내 개인 블로그’ 프로젝트를 만들고 싶으면, 이 이름도 바꿀 수 있는 거야? 그리고 다른 사람도 나처럼 아무것도 안 건드리고 버튼을 누르면 똑같이 ‘demo’ 폴더를 받을 텐데, 내 프로젝트랑 다른 사람 프로젝트는 어떻게 구분되는 거지?”
2장: Group & Artifact: 내 프로젝트 이름표 붙이기
솔라의 질문이 끝나자, 루나는 솔라가 가리키던 ‘demo’ 폴더에서 노트북 화면의 웹 브라우저로 시선을 옮겼다. 그리고는 아무 말 없이 손가락으로 화면의 한 부분을 톡톡 두드렸다. 아까는 무심코 지나쳤던 ‘Project Metadata’ 섹션이었다. 루나의 손가락 끝은 Group과 Artifact라고 적힌 두 개의 입력 칸에 머물러 있었다. 희미한 회색 글씨로 com.example과 demo라는 기본값이 보였다.
방금 전까지 ‘프로젝트 뼈대’라는 커다란 개념에 집중하느라 보이지 않았던 세부 사항들이었다. 솔라는 그제야 자신이 다운로드한 폴더 이름 ‘demo’가 바로 저 Artifact 입력 칸에 적혀 있던 단어였음을 깨달았다. 두 개의 점이 하나의 선으로 이어지는 순간이었다.
“아까 네가 했던 질문, 여기에 답이 있어.”
루나가 마침내 입을 열었다.
“내 프로젝트를 다른 사람의 것과 어떻게 구분하냐고 했지? 그리고 ‘demo’라는 이름을 바꿀 수 있냐고도 물었고.”
“응. 저기 Artifact에 있는 ‘demo’가 폴더 이름이 된 거구나. 그럼 저걸 바꾸면 되겠네. 근데 Group은 뭐야? Name 필드도 따로 있는데 왜 이렇게 복잡하게 이름을 정하는 거지?”
솔라의 목소리에는 절반의 깨달음과 절반의 새로운 의문이 섞여 있었다. 그녀에게 Group과 Artifact는 여전히 단순한 이름 입력 칸처럼 보였다. 하나는 회사 이름, 하나는 제품 이름 같은 것일까? 불필요하게 세분화된 것 같다는 생각도 들었다.
“한번 직접 해보자. ‘내 개인 블로그’ 프로젝트에 고유한 주소를 붙여주는 거야.”
루나가 제안했다.
“Group에는 보통 회사나 개인의 도메인 주소를 거꾸로 적어. 고유한 소속을 나타내는 거지. 한번 com.solalog이라고 입력해볼래? ‘솔라의 블로그’라는 뜻으로.”
솔라는 키보드를 두드려 Group 칸에 com.solalog을 입력했다.
“좋아. Artifact는 프로젝트의 결과물, 즉 이 프로젝트 자체의 이름이야. ‘개인 블로그’니까, personal-blog라고 해볼까?”
솔라가 personal-blog라고 입력하자, 바로 아래에 있던 Name 칸의 내용도 똑같이 personal-blog로 바뀌는 것이 보였다. 그리고 Package name 칸은 com.solalog.personalblog로 자동으로 조합되었다.
“어, 밑에 있는 칸들이 저절로 바뀌네.”
“응. 보통은 Artifact가 프로젝트의 대표 이름이 되니까 편의를 위해 자동으로 채워주는 거야. 이제 다시 ‘GENERATE’ 버튼을 눌러봐.”
솔라가 버튼을 누르자, 이번에는 personal-blog.zip이라는 이름의 파일이 다운로드되었다. 솔라는 재빨리 압축을 풀었다. 예상대로 personal-blog라는 이름의 폴더가 나타났다.
“성공! 이제 ‘demo’가 아니야!”
솔라가 만족스러운 듯 외쳤다. 자신의 의도대로 프로젝트의 겉모습을 바꿨다는 작은 성취감이 느껴졌다. 하지만 루나는 거기서 멈추지 않았다.
“그럼 com.solalog은 어디에 쓰였을까? 그냥 이름표의 일부일 뿐일까?”
그 말에 솔라는 고개를 갸웃거렸다. personal-blog 폴더 안으로 들어가 아까와 같이 src/main/java 폴더까지 파고들어 갔다. 그곳에는 아까와 다른 풍경이 펼쳐져 있었다. com이라는 폴더가 있었고, 그 안에는 solalog 폴더, 또 그 안에는 personalblog 폴더가 차례대로 만들어져 있었다.
“아…!”
솔라의 입에서 짧은 탄성이 터져 나왔다. Group과 Artifact에 입력했던 값들이 단순히 파일 이름 하나에만 영향을 준 것이 아니었다. 그것들은 프로젝트의 가장 깊숙한 곳에 디렉터리 구조로 새겨져, 이 프로젝트의 ‘좌표’를 만들고 있었다. 마치 광활한 인터넷 세상에서 com 국가의 solalog 도시에 사는 personalblog라는 집처럼, 이 프로젝트만의 고유한 주소를 부여한 셈이었다.
“이건 그냥 폴더 이름이 아니었구나. 전 세계의 다른 모든 자바 프로젝트들 사이에서 내 프로젝트를 유일하게 식별해주는 ‘고유 식별자’였어. 그래서 이렇게 맨 처음에, 뼈대를 만들 때부터 물어보는 거였구나.”
솔라는 Group과 Artifact 입력 칸을 다시 바라보았다. 더 이상 복잡하고 불필요한 칸이 아니었다. 그것은 수많은 건물들 사이에서 내 집을 구별해 주는 ‘주소판’이자, 수많은 사람들 속에서 나를 증명하는 ‘신분증’과도 같았다. 왜 필수적인지, 왜 뼈대의 일부인지 비로소 명확하게 이해되었다.
생각이 정리되자, 솔라는 자신의 새로운 프로젝트 폴더 personal-blog를 바라보았다. 근사한 이름표가 붙었지만, 여전히 속은 텅 비어 있었다.
“좋아. 이제 내 프로젝트에 아주 멋진 이름표를 붙여줬어. 길 잃을 걱정은 없겠네. 그런데… 이 집은 여전히 텅 비어 있잖아? 여기에 웹사이트를 띄우려면 주방이 필요하고, 글을 저장하려면 창고가 필요할 텐데. 그런 기능들은 어떻게 추가하는 거지?”
3장: Dependencies: 필요한 기능 골라 담기
솔라의 모니터에는 두 개의 창이 나란히 열려 있었다. 왼쪽에는 방금 이름표를 붙여준 personal-blog 프로젝트 폴더가, 오른쪽에는 다시 돌아온 Initializr 웹사이트 화면이 보였다. 왼쪽 폴더는 이름만 근사할 뿐 속은 텅 비어 있었고, 오른쪽 웹사이트는 여전히 복잡한 옵션들로 가득 차 있었다. 이 텅 빈 집에 가구와 살림을 채워 넣을 방법이 분명 저 오른쪽에 있을 터였다.
솔라의 시선은 이제 익숙해진 Group과 Artifact 입력 칸을 지나, 화면의 가장 큰 영역을 차지하고 있는 오른편으로 향했다. ‘ADD DEPENDENCIES…’ 라고 쓰인 거대한 버튼과 그 아래의 텅 빈 공간. 처음 이 사이트를 봤을 때 가장 위압적으로 느껴졌던 곳이었다. 웹사이트를 만들 ‘주방’이나 글을 저장할 ‘창고’가 바로 저기에 있을 것 같다는 막연한 예감은 들었지만, 섣불리 건드릴 수가 없었다.
그때 옆에 있던 루나가 화면의 그 부분을 정확히 손가락으로 가리켰다.
“네가 말한 주방이나 창고는 저기서 고르는 거야.”
루나의 말에 솔라는 미심쩍은 표정으로 그 영역을 쳐다보았다. 검색창과 버튼, 그리고 그 아래로는 텅 빈 공간뿐이었다.
“여기서? ‘의존성 추가’라고 되어 있는데… 그냥 외부 라이브러리 목록 같은 거 아니야? 뭐가 뭔지 알아야 고르지.”
솔라에게 ‘Dependencies’는 그저 개발자들이 쓰는 전문 도구들의 나열처럼 보였다. ‘Spring Web’, ‘Lombok’, ‘JPA’… 이름만 봐서는 ‘주방’인지 ‘창고’인지 도무지 짐작할 수 없는, 그저 생소한 고유명사들의 목록일 뿐이었다. 잘못 골랐다가 집 전체가 무너질지도 모른다는 불안감마저 들었다.
“직접 확인해보는 게 제일 빠르겠네. 우선, 아까 네가 만든 personal-blog 폴더 안으로 한번 들어가 볼래?”
루나가 말했다. 솔라는 화면을 전환해 personal-blog 폴더를 열었다. src 폴더, gradlew 파일 등 낯선 이름들 사이에서 build.gradle이라는 파일이 눈에 띄었다.
“그 파일 한번 열어봐. 지금 우리 프로젝트의 ‘설계도’ 같은 거야.”
솔라가 build.gradle 파일을 열자, 익숙하지 않은 코드들이 나타났다. 하지만 유심히 살펴보니 group = 'com.solalog', version = '0.0.1-SNAPSHOT' 같은 익숙한 내용도 보였다. 조금 아래로 스크롤하자 dependencies라고 적힌 구역이 나왔다. 그 안은 거의 비어 있었다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
“지금 설계도에는 가장 기본적인 내용만 적혀 있는 상태야. 이제 저기에 ‘주방’을 추가해달라고 주문을 넣어보자.”
루나가 다시 Initializr 화면을 가리켰다. 솔라는 반신반의하며 브라우저 창으로 돌아왔다.
“오른쪽 Dependencies 검색창에 ‘Web’이라고만 입력해봐.”
솔라가 키보드로 ‘Web’을 입력하자, ‘Spring Web’이라는 항목이 나타났다. “웹 애플리케이션을 빌드하기 위한 스타터”라는 설명이 붙어 있었다. 솔라는 그 항목을 클릭했다. 그러자 오른편 목록에 ‘Spring Web’이 추가되었다. 마치 온라인 쇼핑몰의 장바구니에 물건이 담기는 것 같았다.
“좋아. 그 상태로 다른 건 건드리지 말고, 다시 ‘GENERATE’ 버튼을 눌러서 프로젝트를 받아볼래?”
솔라는 아까와 똑같이 personal-blog.zip 파일을 다운로드받아 압축을 풀었다. 그리고 새로 만들어진 폴더 안으로 들어가 다시 build.gradle 파일을 열었다.
파일의 내용은 대부분 이전과 같았다. 하지만 솔라의 눈이 dependencies 구역에 멈추는 순간, 미세한 차이점이 발견되었다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
“어…!”
솔라의 입에서 나지막한 탄성이 흘러나왔다.
“여기 이 한 줄, implementation 'org.springframework.boot:spring-boot-starter-web' 이 줄이 아까는 없었는데 새로 생겼어!”
순간 머릿속에서 모든 것이 연결되었다. Initializr 웹사이트에서 ‘Spring Web’ 버튼을 클릭한 행위가, 단순한 선택이 아니었다. 그것은 프로젝트의 설계도인 build.gradle 파일에 ‘웹 기능 부품을 포함시켜 주세요’라는 주문을 한 줄 새겨 넣는 명확한 명령이었던 것이다.
“아… 알겠다. 이 Dependencies라는 건 그냥 복잡한 프로그램 목록이 아니라, 내 프로젝트에 어떤 기능이 필요한지 ‘주문’하는 목록이었구나! 웹사이트를 만들고 싶으면 ‘Web’을 장바구니에 담고, 데이터베이스에 글을 저장하고 싶으면 ‘JPA’나 ‘JDBC’ 같은 걸 찾아서 담으면 되는 거였어.”
솔라는 비로소 ‘Dependencies’ 섹션의 역할을 이해했다. 그것은 텅 빈 뼈대에 실제 기능을 불어넣기 위한 ‘기능 선택 카탈로그’였다. 더 이상 두렵고 막막한 공간이 아니었다. 내가 만들고 싶은 집에 어떤 방을 넣을지, 어떤 가구를 들일지 고르는 즐거운 상점처럼 보이기 시작했다.
생각이 명확해지자, 솔라의 시선은 다시 build.gradle 파일에 추가된 그 한 줄의 코드에 머물렀다. 새로운 의문이 꼬리를 물고 피어올랐다.
“좋아. 이제 내 프로젝트 설계도에 ‘웹 기능 부품을 추가해줘’라고 한 줄 적는 데까지는 성공했어. 그런데… 이건 그냥 글자잖아. 이 영어 한 줄이 어떻게 진짜 웹 서버를 돌리는 복잡한 기능으로 바뀌는 거지? 누가 이 ‘주문서’를 보고 실제로 부품을 가져와서 조립해주는 사람은 누구야? 그리고 이 build.gradle이라는 파일은 대체 정체가 뭐야?”
4장: Gradle: 의존성 관리와 빌드의 핵심
솔라의 손가락이 키보드 위를 맴돌았다. 모니터에는 build.gradle 파일의 내용이 떠 있었다. implementation 'org.springframework.boot:spring-boot-starter-web'. 방금 전, 이 한 줄의 코드가 Initializr 웹사이트에서 ‘Spring Web’을 선택한 행위의 결과물이라는 사실을 깨달았다. 프로젝트의 설계도에 ‘웹 기능 부품을 추가해달라’는 주문을 넣은 셈이었다.
하지만 거기까지였다. 주문서는 손에 쥐었지만, 이 주문을 어디에, 누구에게 전달해야 하는지 알 길이 없었다. 솔라는 마우스 커서를 이리저리 움직여 보았다. 혹시 이 파일을 실행할 수 있는 ‘재생’ 버튼이라도 숨어 있을까? 아니면 이 파일을 어딘가로 복사해서 붙여 넣어야 하는 걸까? 파일은 그저 검은 배경 위의 하얀 텍스트일 뿐, 아무런 반응이 없었다. ‘이건 그냥 글자잖아.’ 라는 생각이 머릿속을 맴돌았다.
“그 주문서, 누가 읽고 처리해주는지 궁금한 거구나.”
어느새 솔라의 옆으로 다가온 루나가 화면을 보며 나지막이 말했다. 그녀의 시선은 build.gradle 파일이 아니라, 프로젝트 폴더(personal-blog) 자체에 머물러 있었다.
“솔라, Initializr 웹사이트 첫 화면 기억나? Project를 선택하는 곳.”
“응. Gradle 프로젝트 아니면 Maven 프로젝트 고르는 거.”
솔라는 대답하면서도 의도를 파악하지 못했다. 그것은 그저 수많은 선택지 중 하나일 뿐이라고 생각했다. 언어는 Java, 스프링 부트 버전은 최신, 그리고 빌드 도구는… 그냥 사람들이 많이 쓴다는 Gradle. 솔라에게 Gradle은 그런 의미였다. 또 다른 설정값.
“우리가 그때 ‘Gradle’을 선택했지. 그건 단순히 프로젝트 타입을 고른 게 아니야. ‘이 프로젝트의 모든 조립과 관리는 이제부터 Gradle이라는 관리자에게 맡기겠다’고 계약한 거나 마찬가지야.”
‘관리자’라는 말에 솔라의 눈이 가늘어졌다.
“관리자? 그럼 이 build.gradle 파일이 그 관리자를 위한 지시서 같은 거야?”
“바로 그거야. 그리고 그 관리자를 호출하는 방법도 이미 네 프로젝트 안에 들어있어.”
루나는 솔라에게 터미널 창을 열어보라고 손짓했다. 솔라는 익숙지 않은 까만 화면을 열고, 루나가 알려주는 대로 personal-blog 프로젝트 폴더로 이동했다.
루나가 말했다.
“거기서 ls -al 명령어를 입력해봐. 폴더 안의 모든 파일 목록이 보일 거야.”
솔라가 명령어를 입력하자, 익숙한 src 폴더나 build.gradle 파일과 함께 gradlew 와 gradlew.bat 라는 파일이 눈에 띄었다. 아까부터 계속 보이던 파일이었지만, 그냥 시스템 파일이겠거니 하고 무시했던 것들이었다.
“저 gradlew가 바로 네 프로젝트 전용 관리자를 호출하는 비서 같은 거야. 이제 그 비서에게 일을 시켜보자.”
루나가 차분한 목소리로 다음 명령어를 알려주었다.
“./gradlew build 라고 입력해봐.”
솔라는 반신반의하며 키보드를 두드렸다. ./gradlew, 띄어쓰고, build. 엔터 키를 누르는 순간, 고요하던 터미널 창이 갑자기 분주하게 움직이기 시작했다.
> Task :compileJava
> Task :processResources
> Task :classes
> Task :bootJar
> Task :build
BUILD SUCCESSFUL in 5s
6 actionable tasks: 6 executed
물론 처음에는 이렇게 간단하지 않았다. 수많은 DOWNLOAD 메시지가 화면을 스쳐 지나갔고, 알 수 없는 주소에서 무언가를 끊임없이 내려받았다. 마치 build.gradle이라는 쇼핑 목록을 받아 든 관리자가 전 세계의 부품 창고를 뒤져 필요한 물건들을 배송받는 모습 같았다. 그리고 잠시 후, 모든 다운로드가 끝나자 화면에는 ‘BUILD SUCCESSFUL’이라는 초록색 메시지가 선명하게 찍혔다.
솔라는 멍하니 화면을 바라보았다. 글자일 뿐이라고 생각했던 implementation 'org.springframework.boot:spring-boot-starter-web' 이 한 줄이, 바로 이 순간 실제 ‘부품’이 되어 자신의 컴퓨터로 다운로드된 것이다. 그리고 Gradle이라는 관리자는 그 부품들과 내가 앞으로 작성할 코드를 합쳐, 실행 가능한 하나의 완성품(build 폴더 안의 .jar 파일)으로 조립해주었다.
“아…!”
탄성과 함께 모든 조각이 맞춰졌다.
“Gradle은 그냥 설정 항목이 아니었어. 진짜로 일을 하는 ‘프로젝트 관리자’였구나. Initializr에서 Gradle을 선택한 건 이 관리자를 고용하는 거였고, Dependencies를 추가한 건 이 관리자에게 줄 작업 지시서(build.gradle)에 내용을 추가하는 거였어. 그리고 터미널에서 ./gradlew build를 실행한 건, 관리자에게 ‘지시서대로 일 시작해!’라고 명령을 내린 거였네.”
솔라는 처음 Initializr 화면을 봤을 때의 막막함을 떠올렸다. 왜 이렇게 많은 것을 한꺼번에 정해야 하는지 불만이었다. 하지만 이제는 아니었다. 집의 뼈대를 만들고(Initializr), 집 주소를 정하고(Group/Artifact), 필요한 가구 목록을 작성한 뒤(Dependencies), 그 목록을 보고 실제로 가구를 배달하고 조립해 줄 유능한 집사(Gradle)를 고용하는 것. 이 모든 것이 프로젝트라는 하나의 집을 짓기 위한 너무나도 당연하고 필수적인 첫 단계들이었다.
생각이 정리되자, 솔라는 성공적으로 빌드된 자신의 프로젝트 폴더를 뿌듯하게 바라보았다. 뼈대, 이름표, 기능 목록, 그리고 관리자까지. 각자의 역할은 이제 명확히 이해됐다. 하지만 여전히 머릿속에 흩어져 있는 느낌이었다.
“좋아, 이제 각각의 부품들이 왜 필요한지는 알겠어. 뼈대, 이름표, 주문서, 관리자… 전부. 그런데 이 모든 과정을 처음부터 끝까지 한 번에 쭉 이어서 보고 싶어. Initializr 페이지를 여는 순간부터, 방금처럼 빌드가 성공하는 것까지. 이 모든 게 어떻게 하나의 흐름으로 맞물려 돌아가는 거지?”
5장: 스프링 프로젝트: 시작부터 실행까지
솔라의 책상 위, 노트북 화면에는 두 개의 창이 떠 있었다. 왼쪽에는 조금 전 성공적으로 빌드된 personal-blog 프로젝트 폴더가, 오른쪽에는 ‘BUILD SUCCESSFUL’이라는 초록색 메시지가 선명한 터미널 창이 보였다. 뼈대, 이름표, 주문서, 그리고 관리자까지. 흩어져 있던 퍼즐 조각들은 제자리를 찾은 듯했다.
하지만 솔라의 표정은 개운치 않았다. 마치 각 부품의 설계도는 완벽하게 이해했지만, 이 부품들을 어떤 순서로 조립해야 하나의 자동차가 완성되는지 전체 조립 과정이 머릿속에 그려지지 않는 기분이었다. 솔라는 키보드 옆에 놓인 메모장을 응시했다.
- Initializr (뼈대)
- Group/Artifact (이름표)
- Dependencies (기능 주문)
- Gradle (관리자)
그녀는 각 항목 옆에 체크 표시를 했지만, 항목들 사이를 연결하는 화살표는 그리지 못했다. 그것들은 여전히 독립된 4개의 섬처럼 느껴졌다.
“알겠어. 각각이 뭔지는 이제 확실히 알겠는데….”
솔라가 혼잣말처럼 중얼거렸다.
“여전히 이상해. 왜 집을 짓기 시작하는 첫 단계에서 집주소, 가구 목록, 그리고 공사 감독관까지 한꺼번에 정해야만 하는 걸까? 이 모든 게 어떻게 하나의 부드러운 흐름이 되는지 모르겠어.”
옆에서 솔라의 혼란을 잠자코 지켜보던 루나가 조용히 입을 열었다.
“우리가 지금까지 한 건, 집을 짓고 가구를 들일 준비를 마친 것까지야. 하지만 아직 그 집에 사람이 살 수 있도록 전기를 연결하고 불을 켜는 일은 하지 않았지.”
루나는 솔라의 메모장을 가리키며 말했다.
“그 네 가지가 왜 처음부터 한 묶음으로 필요한지, 직접 불을 켜보면 알게 될 거야. 처음부터 끝까지, 딱 한 번만 더 해보자.”
루나의 제안에 솔라는 모든 창을 닫고, 깨끗한 웹 브라우저를 열어 start.spring.io 주소를 입력했다. 처음 봤을 때와 같은 화면이었지만, 더 이상 위압감이 느껴지지는 않았다.
“이번엔 아주 간단한 웹페이지를 띄우는 프로젝트를 만들어 볼게.”
솔라는 익숙하게 Group에 com.sola.hello, Artifact에 hello-app이라고 입력했다. 이제 이 이름들이 프로젝트의 고유한 주소와 이름표가 된다는 것을 알고 있었다. 이어서 오른쪽 ‘Dependencies’ 버튼을 누르고, 망설임 없이 검색창에 ‘Web’을 입력한 뒤 ‘Spring Web’을 추가했다. 웹 기능을 하는 ‘주방’을 설치해 달라는 주문이었다.
‘GENERATE’ 버튼을 누르자 hello-app.zip 파일이 다운로드되었다. 솔라는 압축을 풀고, 프로젝트를 개발 도구(IDE)로 열었다. 이전과 달리, 이번에는 단순히 폴더를 구경하는 데서 그치지 않았다.
루나의 안내에 따라 솔라는 src/main/java/com/sola/hello/HelloAppApplication.java 파일을 열고, 그 옆에 새로운 파일을 하나 만들었다. 그리고 웹 브라우저의 요청에 응답할 아주 간단한 코드를 몇 줄 추가했다.
@RestController
public class HelloController {
@GetMapping("/")
public String sayHello() {
return "Hello, World! My project is running!";
}
}
이 코드는 누군가 이 집의 현관문(/)을 두드리면, “Hello, World!”라는 메시지를 보여주라는 간단한 지시였다.
모든 준비가 끝났다. 솔라는 다시 터미널을 열고 프로젝트 폴더로 이동했다. 이전에는 ./gradlew build 명령어로 프로젝트를 ‘건설’만 했다. 하지만 이번에는 루나가 다른 명령어를 알려주었다.
“./gradlew bootRun 이라고 입력해봐. ‘건설하고 바로 입주해서 불까지 켜줘’라는 뜻이야.”
솔라가 엔터키를 누르자, 익숙한 빌드 과정이 시작되었다. 하지만 ‘BUILD SUCCESSFUL’이라는 메시지에서 멈추지 않았다. 터미널은 계속해서 무언가를 실행했고, 잠시 후 다음과 같은 메시지가 나타났다.
Tomcat started on port(s): 8080 (http)
터미널은 명령 프롬프트로 돌아가지 않고 계속 그 상태로 대기하고 있었다. 마치 관리자(Gradle)가 주문서(Dependencies)에 따라 웹 기능(Spring Web) 부품을 가져와 조립한 뒤, 이제 손님을 맞이할 준비를 마치고 현관에서 대기하고 있는 모습 같았다.
“웹 브라우저를 열고, 주소창에 localhost:8080 이라고 입력해봐.”
솔라는 떨리는 손으로 새 탭을 열고 주소를 입력했다. 엔터 키를 누르자, 텅 빈 하얀 화면에 방금 자신이 입력했던 문장이 나타났다.
Hello, World! My project is running!
순간, 솔라의 머릿속에서 모든 것이 연결되었다.
Initializr의 그 복잡해 보였던 첫 화면은, 단지 프로젝트의 ‘뼈대’를 만드는 곳이 아니었다. 그것은 ‘실행 가능한 애플리케이션’이라는 최종 목적지를 향한 모든 정보를 한 번에 입력하는 ‘프로젝트 발사대’였다.
어떤 관리자(Gradle)를 고용해서, 어떤 고유 주소(Group/Artifact)를 가진 집을, 어떤 특별한 기능(Dependencies)을 넣어 지을 것인지. 이 중 하나라도 빠지면 ‘실행’이라는 최종 목표에 도달할 수 없었다. 그래서 이 모든 것을 맨 처음, 한 화면에서 물어봐야만 했던 것이다. 각 단계는 분리된 작업이 아니라, ‘실행’이라는 단 하나의 목표를 향해 유기적으로 연결된 필수 과정이었다.
솔라는 방금 전까지 혼란스러웠던 start.spring.io 페이지를 다시 쳐다보았다. 이제 그 화면은 더 이상 복잡한 설문지가 아니었다. 자신이 원하는 프로그램을 세상에 내놓기 위한, 가장 빠르고 정확한 주문서로 보였다. 솔라는 각 입력 필드를 손가락으로 짚어가며, 이제는 자신의 것이 된 지식을 확인하듯 나지막이 읊조렸다.
“이건 관리자 선택, 이건 우리 집 주소, 이건 필요한 가구 목록… 전부 다 있어야, 이렇게… 불이 켜지는구나.”
노트북 화면에서 환하게 빛나는 ‘Hello, World!’ 메시지가 그 모든 질문에 대한 명쾌한 대답이었다.