본문 바로가기

분류 전체보기

(136)
[Spring Boot] API 문서화의 끝판왕: Swagger(Springdoc)와 REST Docs의 대결 백엔드 개발자가 API 100개를 열심히 다 만들었습니다. 이제 이 API를 가져다 써야 하는 프론트엔드(React, iOS) 개발자와 핏대 높여 싸울 시간이 왔습니다. "회원가입 파라미터가 userName인가요 user_name인가요?", "에러가 났을 때 리턴되는 JSON 응답 양식 좀 엑셀로 정리해서 주세요!" 과거에는 노션(Notion)이나 엑셀에 한 땀 한 땀 API 스펙을 수동으로 적어주었습니다. 하지만 백엔드 코드가 수정될 때 문서 업데이트를 깜빡하는 순간, 코드는 최신인데 문서는 구버전인 '거짓말 문서'가 되어버리고 프론트엔드의 비난을 한 몸에 받게 됩니다. 이 끔찍한 비효율과 커뮤니케이션 비용을 제로(0)로 만들기 위해 등장한 구원자가 바로 "자바 코드를 읽어서 자동으로 멋진 웹페이지 A..
[Spring Boot] Apache Kafka 연동: MSA의 대동맥, 이벤트 주도 비동기 통신 배달 앱에서 고객이 치킨을 '결제'했다고 상상해 봅시다. 백엔드 시스템 내부에서는 어마어마한 일들이 순차적으로 벌어집니다. 1. 주문 시스템에 접수 -> 2. 라이더 배차 시스템 호출 -> 3. 고객에게 카카오톡 알림 발송 -> 4. 쿠폰 소진 처리. 만약 결제 서버가 이 4개의 서버에게 일일이 HTTP(REST API)로 전화를 걸어 "야, 결제됐어! 일해!"라고 동기식(Synchronous)으로 통신하면 어떻게 될까요? 만약 카카오톡 알림 서버가 장애로 3초간 멈춰있다면, 결제 서버도 그 3초 동안 응답을 기다리며 멈춰있게 되고 결국 사용자의 화면은 하얗게 굳어버립니다. 하나의 서버 장애가 전체 시스템의 장애로 퍼지는 '도미노 붕괴(강한 결합)' 현상입니다. 이 끔찍한 의존성을 칼같이 끊어내기 위해 ..
[Spring Boot] Spring Cloud Config: 50개의 서버 설정을 단 한 곳에서 통제하라 회사가 거대해지면 하나의 거대한 스프링 부트(Spring Boot) 애플리케이션(Monolithic)은 한계를 맞이합니다. 결제팀은 결제 로직만, 회원팀은 회원 로직만 따로 떼어내어 각각 50개의 독립적인 서버로 쪼개는 마이크로서비스 아키텍처(MSA, Microservice Architecture)의 시대가 열립니다. 그런데 이 MSA 환경에서 인프라 엔지니어를 가장 고통스럽게 만드는 것은 코드가 아니라 다름 아닌 application.yml 설정 파일입니다. 공통으로 사용하는 'Redis 서버 IP'가 바뀌거나 '결제 연동 API Key'를 변경해야 할 때, 50개의 프로젝트 레포지토리를 열어서 일일이 yml을 수정하고, 50번의 도커(Docker) 이미지를 다시 굽고, 50대의 서버를 재시작해야 하는..
[Spring Boot] Actuator 모니터링: 서버의 심장 박동을 실시간으로 감시하라 열심히 개발한 스프링 부트(Spring Boot) 서버를 무사히 라이브(Live) 환경에 배포했습니다. 그런데 배포 후 3일째 되는 날, 유저 한 명이 "홈페이지가 엄청 느려요!"라고 게시판에 불평을 남겼습니다. 이 말을 들은 개발자는 등골이 오싹해집니다. "서버 메모리가 다 차서 느려진 건가? 아니면 DB 커넥션이 부족한가? 혹시 특정 API가 유독 늦게 응답하는 건가?" 원인을 찾기 위해 리눅스 서버에 SSH로 접속해서 top 명령어를 치거나, 끝도 없이 올라가는 로그 텍스트를 눈알이 빠지도록 쳐다보는 것은 원시 시대의 방식입니다. 서버 스스로가 자신의 건강 상태(Health), 메모리, CPU 사용량, DB 연결 상태, 최근 100개의 HTTP 요청 내역 등을 완벽한 JSON 데이터로 뱉어내 주면 ..
[Spring Boot] 테스트 코드 작성법: JUnit 5와 Mockito로 버그 없는 불사신 서버 만들기 초보 개발자와 실무 프로 개발자를 가르는 가장 거대하고 뚜렷한 경계선은 바로 '테스트 코드(Test Code)'의 존재 여부입니다. 학원 프로젝트 시절에는 기능을 구현하고 서버를 켠 뒤, 포스트맨(Postman)으로 API를 한 번 호출해 보고 성공이 뜨면 "우와! 다 됐다!" 하고 환호하며 코딩을 끝냈습니다. 하지만 6개월 뒤, A 기능을 살짝 수정했는데 전혀 상관없어 보이던 B 기능과 C 기능이 연쇄적으로 펑펑 터지기 시작합니다. 어디서부터 손대야 할지 몰라 밤을 새우는 '레거시 코드의 늪'에 빠지게 되죠. 만약 수백 개의 기능이 "나는 지금 정상적으로 작동하고 있어!"라고 매번 자동 검증해 주는 시스템이 있었다면 어땠을까요? 코드가 코드를 검증하고, 내 로직이 100% 옳다는 것을 수학적으로 증명해..
[Spring Boot] 파일 업로드와 AWS S3 연동: 서버 디스크의 한계를 클라우드로 돌파하라 게시판에 사용자가 10MB짜리 고화질 사진을 첨부하여 글을 작성합니다. 초보 시절에는 MultipartFile 객체를 받아 스프링 부트(Spring Boot)가 구동되고 있는 로컬 서버의 C:\upload 디렉토리나 리눅스 /var/www/images 폴더에 file.transferTo() 코드로 저장(Save)하는 방식을 썼습니다. 서버가 딱 1대일 때는 이 방식이 기가 막히게 잘 돌아갑니다. 하지만 대박이 터져서 서버를 3대(A, B, C)로 스케일 아웃(Scale-out)하는 순간 지옥문이 열립니다. 사용자가 A서버에 접속해 프로필 사진을 올렸는데, 내일 다시 접속할 땐 로드밸런서가 B서버로 연결해 주면? B서버 디스크에는 사진이 없으므로 화면에 끔찍한 '엑스박스(이미지 깨짐)'가 뜹니다. 게다가 ..
[Spring Boot] 비동기(@Async)와 스케줄링(@Scheduled): 무거운 작업을 뒤로 빼라 여러분이 야심 차게 런칭한 서비스의 회원가입 로직을 짜고 있습니다. 1. 유저 정보 DB 저장 -> 2. 포인트 지급 -> 3. 환영 이메일 발송 순서로 코드를 짰습니다. DB 저장은 0.1초면 끝나지만, 외부 메일 서버를 거치는 '이메일 발송'은 무려 3초가 걸립니다. 사용자가 [가입하기] 버튼을 누르면 이 모든 작업이 '직렬(동기)'로 실행되어 브라우저 화면이 3초 넘게 하얗게 멈춰있게 됩니다. 성질 급한 한국 유저들은 서버가 죽은 줄 알고 새로고침을 연타하다가 결국 이탈해 버릴 것입니다. 이럴 때 백엔드 개발자의 센스가 발휘되어야 합니다. "이메일 발송 같은 건 메인 응답에 영향을 주지 않도록 다른 쓰레드(알바생)에게 짬처리(?) 시키고, 나는 유저에게 0.1초 만에 '가입 성공!'을 외쳐주자!" ..
[Spring Boot] @Transactional의 함정: 롤백(Rollback)과 전파 속성의 모든 것 은행 앱에서 A가 B에게 10만 원을 송금하는 상황을 상상해 봅시다. 서버는 두 가지 쿼리를 실행합니다. 1. A의 통장에서 10만 원 차감 (UPDATE), 2. B의 통장에 10만 원 증가 (UPDATE). 그런데 1번 쿼리가 성공한 직후, 갑자기 데이터베이스 서버의 전원이 나갔습니다! A의 돈은 10만 원이 증발했는데, B는 돈을 받지 못했습니다. 뉴스에 대서특필될 만한 엄청난 사고입니다. 이런 대형 사고를 막기 위해 데이터베이스는 "1번과 2번 쿼리를 하나로 묶어라. 둘 다 성공할 때만 최종 반영(Commit)하고, 하나라도 실패하면 1번 쿼리도 아예 없었던 일로 롤백(Rollback) 시켜라!"라는 논리적 작업 단위를 제공하는데, 이것이 바로 트랜잭션(Transaction)입니다. 스프링 부트(..
[Spring Boot] OAuth 2.0 소셜 로그인: 카카오, 네이버, 구글 연동의 정석 인터넷 쇼핑몰이나 새로운 서비스에 접속했을 때, 여러분은 긴 회원가입 폼을 성실하게 채우시나요, 아니면 "카카오로 1초 만에 시작하기" 버튼을 누르시나요? 바쁘고 귀찮은 것을 극도로 싫어하는 현대의 유저들에게 자사몰의 독자적인 회원가입(ID/PW)을 강요하는 것은 곧 '고객 이탈'을 의미합니다. 이러한 이탈을 막기 위해 구글, 카카오, 네이버 같은 거대 IT 기업들이 유저의 신원을 대신 확인해 주고 우리 서버에 "이 사람 진짜 맞아!"라고 보증을 서주는 기술이 등장했습니다. 바로 OAuth 2.0 (Open Authorization) 프로토콜입니다. 그리고 스프링 부트(Spring Boot) 진영에는 이 복잡한 프로토콜 통신을 아주 우아하게 캡슐화해 놓은 spring-boot-starter-oauth2-..
[Spring Boot] Spring Security 2편: 현대 API 인증의 표준, JWT(JSON Web Token) 완벽 구현 앞선 14단계에서 우리는 스프링 시큐리티(Spring Security)의 위대한 필터 체인(Filter Chain)과 전통적인 폼 로그인(Session/Cookie 방식)을 마스터했습니다. 세션 기반 로그인은 보안성이 높고 구현이 편하지만, 현대의 트렌드인 '모바일 앱(iOS/Android) 통신'과 서버 수십 대를 띄우는 '마이크로서비스(MSA) 스케일 아웃' 환경을 만나면 치명적인 약점을 드러냅니다. 사용자가 서버 A에 로그인하여 세션을 만들었는데, 다음 요청이 서버 B로 향하면 서버 B는 "너 누구야?" 라며 로그아웃 시켜버리기 때문입니다(세션 불일치). 이 골치 아픈 '상태 유지(Stateful)'의 사슬을 끊어내고, 서버가 사용자 정보를 기억할 필요 없이 "네가 가진 암호화된 방문증(Token)..
[Spring Boot] Spring Security 1편: 거대한 철문, 폼 로그인(Form Login)과 아키텍처의 이해 웹 서비스를 운영하다 보면 "로그인한 회원만 글을 쓸 수 있게 해라", "관리자(Admin) 권한이 있는 사람만 이 페이지에 접근하게 해라"와 같은 요구사항이 쏟아집니다. 초보 개발자들은 컨트롤러(Controller)의 모든 메서드 첫 줄에 if (session.getAttribute("user") == null) throw new Error(); 같은 코드를 복붙하며 지옥을 맛보곤 합니다. 이처럼 중복되는 보안(Security) 검사 로직을 애플리케이션의 핵심 비즈니스 로직과 완벽하게 분리하여, 개발자가 비즈니스에만 집중할 수 있게 해주는 전 세계에서 가장 강력하고 튼튼한 방패가 바로 Spring Security(스프링 시큐리티)입니다. 하지만 그 강력함만큼이나 초기 진입 장벽이 악명 높게 높은 것으로..
[Spring Boot] Redis 캐싱(Caching): DB 부하를 박살내고 응답 속도 10배 끌어올리기 스타트업이 처음 런칭했을 때는 유저가 적어 데이터베이스(RDB) 하나만으로도 충분히 서비스가 잘 돌아갑니다. 하지만 마케팅이 대박 나서 '초당 1만 건'의 요청이 쏟아지기 시작하면 상황은 달라집니다. 여러분이 앞선 12단계에서 HikariCP 커넥션 풀을 아무리 완벽하게 튜닝해 놓았더라도, 물리적인 디스크(Disk)를 긁어서 데이터를 읽어오는 관계형 데이터베이스(MySQL, Oracle)는 태생적으로 속도의 한계가 있습니다. 특히 '쇼핑몰 메인 페이지의 배너 이미지 목록'이나 '오늘의 인기 검색어'처럼 수많은 유저가 똑같이 조회하지만, 데이터는 하루에 한두 번만 변하는 정보를 매번 느린 디스크 DB까지 내려가서 읽어오는 것은 엄청난 리소스 낭비입니다. 이를 해결하기 위해 백엔드 진영이 도입한 궁극의 마법..
[Spring Boot] 커넥션 풀(HikariCP) 튜닝: 병목을 뚫고 장애를 예방하는 인프라의 심장 여러분이 인기 아이돌 콘서트 티켓팅 사이트의 백엔드 개발자라고 상상해 봅시다. 밤 8시 정각, 1만 명의 유저가 동시에 '결제하기' 버튼을 누릅니다. 스프링 부트(Spring Boot) 서버는 무사히 요청을 받았지만, 데이터베이스(DB)에 접속하려다 보니 심각한 문제가 발생합니다. 애플리케이션이 DB와 TCP/IP 통신을 맺고 로그인하는 과정은 엄청나게 무겁고 긴 시간이 소요됩니다. 1만 명이 접속할 때마다 매번 이 연결(Connection)을 새로 맺는다면 DB 서버는 10초도 안 돼서 뻗어버릴 것입니다. 이 끔찍한 병목(Bottleneck) 현상을 해결하기 위해 "미리 DB와의 연결(통로) 100개를 맺어놓고, 유저가 올 때마다 빌려주고 다시 반납받는 대여소"를 만들었는데, 이것이 바로 커넥션 풀(C..
[Spring Boot] QueryDSL 입문: 타입 세이프(Type-safe)한 동적 쿼리의 끝판왕 10단계에서 배운 Spring Data JPA의 Query Method와 @Query는 훌륭했지만, 실무의 거친 요구사항을 만나면 한계가 명확하게 드러납니다. 쇼핑몰의 상품 검색창을 생각해 봅시다. 사용자는 '상품명'만 검색할 수도 있고, '가격 범위'만 필터링할 수도 있으며, '카테고리'와 '브랜드'를 동시에 선택할 수도 있습니다. 즉, 조건이 있을 수도 있고 없을 수도 있는 '동적 쿼리(Dynamic Query)'가 필요합니다. 과거 MyBatis 시절에는 XML 파일에 같은 태그를 수십 개 도배하여 쿼리 문자열(String)을 조립했습니다. JPA @Query에서 문자열로 동적 쿼리를 짜다 보면 띄어쓰기 하나 잘못해서 서버가 켜진 뒤 고객이 검색 버튼을 누르는 순간 에러가 터집니다(런타임 에러)...
[Spring Boot] Spring Data JPA의 마법: QueryMethod로 검색과 페이징을 박살내다 과거 JPA(Hibernate)만 순수하게 쓰던 시절에는 개발자가 직접 EntityManager를 불러와서 em.persist(), em.find() 같은 코드를 짰습니다. 이 정도만 해도 SQL을 안 짜서 행복했지만, 스프링 진영은 여기서 만족하지 않았습니다. "아니, 저장하고 찾고 삭제하는 CRUD 코드는 전 세계 모든 프로젝트가 다 똑같은데, 개발자가 왜 굳이 그 코드를 또 짜야 해? 우리가 다 만들어 줄게!"라는 미친 발상에서 탄생한 괴물이 바로 Spring Data JPA입니다. 이제 우리는 그저 JpaRepository라는 인터페이스 하나를 상속받는 것만으로 수십 개의 기본 쿼리 기능이 하늘에서 뚝 떨어지는 기적을 맛봅니다. 하지만 진짜 마법은 지금부터입니다. 만약 "나이가 20살 이상이고 이..
[Spring Boot] JPA 연관관계 매핑 실전: 1:N 설계와 공포의 N+1 문제 정복 8단계에서 JPA의 신세계를 맛본 개발자들은 흥분에 차서 이렇게 외칩니다. "와! SQL 안 짜고 @Entity 하나만 달면 끝이네! 이제 JPA 마스터다!" 그러나 그 오만함은 실무의 복잡한 비즈니스 모델을 만나는 순간 처참히 박살 납니다. 현실의 데이터베이스는 단일 테이블로 존재하지 않습니다. 하나의 회원(Member)은 여러 개의 주문(Order)을 할 수 있고, 하나의 게시글(Post)에는 수많은 댓글(Comment)이 달립니다. RDB는 '외래 키(Foreign Key)' 하나로 이 관계를 멋지게 풀지만, 자바(Java) 객체는 서로의 '참조(Reference)'를 가져야만 관계가 맺어집니다. 여기서 발생하는 패러다임의 충돌을 해결하는 것이 바로 JPA의 꽃이라 불리는 '연관관계 매핑(Assoc..
[Spring Boot] JPA(Hibernate) 기초: 객체와 관계형 DB의 패러다임 불일치를 부수다 과거의 자바(Java) 백엔드 개발자들은 하루 종일 무엇을 했을까요? "SQL 작성 기계"였습니다. 게시판 하나를 만들려 해도 INSERT INTO board VALUES..., SELECT * FROM board WHERE... 등 길고 지루한 SQL 쿼리문을 수백 줄씩 타이핑해야 했습니다. 만약 중간에 DB 테이블에 컬럼이 하나라도 추가되면, 그 수백 줄의 쿼리문을 일일이 다 찾아다니며 수정하는 끔찍한 '노가다'의 연속이었죠. 게다가 객체 지향 언어인 자바(Java)와 관계형 데이터베이스(RDB)는 태생적으로 사상(Paradigm)이 너무나도 달랐습니다. 자바는 '상속'과 '참조'를 사랑하지만, DB는 그냥 2차원 표(Table)일 뿐이니까요. 이 극악무도한 '패러다임의 불일치'를 박살내고, 개발자가..
[Spring Boot] 전역 예외 처리(Global Exception Handling): 에러의 혼돈을 우아하게 제압하라 우리가 정성스럽게 만든 REST API 서버가 실제 세상에 배포되는 순간, 상상을 초월하는 사용자(Client)들을 만나게 됩니다. 숫자가 들어가야 할 나이 필드에 '스물다섯'이라는 문자를 던지거나, 존재하지 않는 user_id=999를 집요하게 요청하는 등 무수한 공격과 실수가 쏟아집니다. 이때 서버 내부에서 NullPointerException이나 IllegalArgumentException 같은 자바 에러가 터지면 어떻게 될까요? 스프링 부트(Spring Boot)는 아무 설정을 안 하면 시뻘건 스택 트레이스(Stack Trace) 로그와 함께 아주 끔찍하고 못생긴 "Whitelabel Error Page"나 알 수 없는 HTML 덩어리를 응답으로 뱉어냅니다. 만약 프론트엔드(React, Vue) ..
[Spring Boot] 의존성 주입(DI)과 IoC 컨테이너: 객체의 생과 사를 스프링에게 맡기다 우리가 자바(Java) 학원에서 처음 프로그래밍을 배울 때, 어떤 클래스의 기능을 사용하려면 무조건 new 키워드를 사용해 메모리에 객체를 찍어내야 한다고 배웠습니다. 예를 들어 UserService에서 UserRepository가 필요하면 UserRepository repository = new UserRepository(); 처럼 직접 생성했습니다. 하지만 실무 스프링 부트(Spring Boot) 프로젝트 코드를 아무리 뒤져봐도 new 키워드를 찾아보기는 하늘의 별 따기입니다. 개발자가 객체를 직접 만들지 않는데 도대체 프로그램이 어떻게 굴러가는 걸까요? 이 거대한 미스터리를 푸는 열쇠이자, 전 세계 수많은 자바 개발자들을 스프링의 노예(?)로 만들어버린 가장 위대하고 핵심적인 철학이 바로 IoC(I..
[Spring Boot] Logback 완벽 가이드: 서버의 유일한 목격자, 로깅(Logging) 마스터하기 초보 개발자와 실무 개발자를 구분하는 가장 확실한 기준 하나를 꼽으라면 바로 '로깅(Logging)을 어떻게 다루는가?'입니다. 대학생이나 학원 시절 프로젝트에서는 에러가 나거나 변수 값을 확인하고 싶을 때 무조건 System.out.println("여기로 들어옴!!")을 난사했습니다. 하지만 실제 운영(Production) 서버에서 System.out.println을 쓰면, 치명적인 성능 저하(블로킹 I/O)가 발생할 뿐만 아니라, 서버가 재시작되는 순간 콘솔에 찍힌 글자들이 흔적도 없이 증발해 버립니다. 어젯밤 11시에 결제가 실패해서 고객센터로 항의가 들어왔는데, 원인을 찾으려고 보니 로그 기록이 완전히 날아가 있다면 개발자는 등골이 오싹해질 수밖에 없습니다.이번 [Spring Boot 실무 완벽 ..
[Spring Boot] application.yml 완벽 가이드: Profile을 활용한 무결점 환경 분리 전략 스프링 부트(Spring Boot)로 개발을 진행하다 보면 필연적으로 데이터베이스 접속 IP, 비밀번호, 외부 결제 API 연동 키(Key) 등 수많은 '설정(Configuration) 데이터'를 다루게 됩니다. 초보 개발자들은 흔히 자바(Java) 코드 안에 String dbUrl = "jdbc:mysql://localhost:3306..." 처럼 값을 하드코딩(Hard-coding)하는 실수를 저지릅니다. 이렇게 코드를 짜면, 내 노트북(로컬)에서는 잘 돌던 프로그램이, IP가 완전히 다른 실제 라이브 운영(Production) 서버에 배포되는 순간 데이터베이스를 찾지 못하고 펑 터져버립니다. 배포할 때마다 소스코드를 열어서 주석 처리하고 IP를 고쳐서 빌드하실 건가요? 이런 원시적인 작업을 우아하게..
[Spring Boot] Spring Boot MVC 패턴 완벽 해부: Controller, Service, Repository의 분업 만약 여러분이 햄버거 매장의 점장이라고 상상해 봅시다. 카운터에서 손님의 주문(Request)을 받고 결제하는 직원이 있고, 뒤쪽 주방에서 패티를 굽고 햄버거를 조립(비즈니스 로직)하는 요리사가 있으며, 냉장고(Database)에서 신선한 식재료를 꺼내오는 창고 관리자가 있습니다. 이 세 명의 직원이 완벽하게 역할(Role)을 분담하여 일할 때 매장은 가장 효율적으로 돌아갑니다. 만약 카운터 직원이 주문을 받다 말고 주방에 들어가서 패티를 굽고 냉장고 문을 열어젖힌다면 매장은 금세 난장판이 될 것입니다. 스프링 부트(Spring Boot) 백엔드 애플리케이션도 이와 똑같습니다. 하나의 클래스 안에 모든 코드를 때려 넣는 '스파게티 코드'를 방지하고, 유지보수와 테스트를 극대화하기 위해 탄생한 아키텍처가 ..
[Spring Boot] Spring Initializr: 완벽한 프로젝트 뼈대와 실무 의존성 설계 가이드 집을 지을 때 가장 중요한 것은 화려한 인테리어가 아니라 탄탄한 설계도와 뼈대(Foundation)입니다. 스프링 부트(Spring Boot) 개발 역시 마찬가지입니다. 처음에 프로젝트 뼈대를 어떻게 잡고, 빌드 도구를 무엇으로 선택하며, 어떤 라이브러리(Dependencies)들을 담고 시작하느냐에 따라 1년 뒤 프로젝트의 유지보수 난이도가 천차만별로 갈라집니다. 다행히도 스프링 진영은 개발자들이 손쉽게 뼈대를 짤 수 있도록 Spring Initializr(start.spring.io)라는 강력한 웹 기반의 제너레이터를 제공합니다.이번 시리즈의 두 번째 포스팅에서는 수많은 옵션들 앞에서 헤매는 주니어 개발자들을 위해, 실무에서 100% 통용되는 프로젝트 구조 설계와 필수 의존성(Dependencies)..
[Spring Boot] 3.x 시작하기: 레거시 Spring과의 차이점과 완벽한 첫 세팅 자바(Java) 백엔드 생태계에서 Spring Framework는 절대적인 표준입니다. 수많은 엔터프라이즈 기업과 스타트업이 모두 스프링을 기반으로 거대한 서비스를 구축하고 있습니다. 그러나 과거의 레거시(Legacy) Spring은 초기 세팅이 너무나도 고통스러웠습니다. XML 파일에 빈(Bean)을 등록하고, 톰캣(Tomcat) 서버를 별도로 설치해서 .war 파일을 배포하고, 수십 개의 의존성 버전이 꼬이는 이른바 'XML 지옥(XML Hell)'과 '의존성 지옥(Dependency Hell)'을 겪어야만 했습니다. 이러한 고통을 완벽하게 해결하고 개발자가 오직 '비즈니스 로직(Business Logic)' 작성에만 집중할 수 있도록 혁신을 일으킨 구원자가 바로 Spring Boot(스프링 부트)입..