
필자는 몇몇 회사의 플랫폼 설계자로 일하며, 개발과 운용경함이 20년넘게 있으며 최근에는 비전공자를 대상으로 하는 개발자양성 강사로 일하고 있고 Python, Java, Spring, Bigdata관련 책도 집필하고 있는 가운데 Spring Boot에 대해 소개하고자 합니다.
참고로 오랫만에 Java를 이야기하기 떄문에 Spring Boot의 비교대상이 레거시보다 Java(2010년 이전의 J2EE등)이 된다는 점은 양해바랍니다. 새로운 Java/JavaEE나 프레임워크를 사용하여 개발된 애플리케이션은 여기에서 언급할 만큼 큰 단점이 없습니다.
필자는 학생시절부터 Java나 C#, C++을 중심으로 소프트웨어 개발을 하는 경우가 많았지만, 2010년대 이후, 플랫폼적인 제약이 없는 한 Python과 TypeScript이외에는 거의 사용하지 않게 되었습니다. 규모가 큰 애플리케이션을 작성하는 것이라면, Java등이 적절한 언어이지만, 필자가 개발하고 있던 것중 소규모도 많아 부담없이 작성할 수 있는 Python쪽도 얼맞았다는 배경이 있습니다. 다만, Java의 Spring Boot에 대해서는 오ㅖ외로 "지금까지의 전형적인 Java 애플리케이션 개발보다 압도적으로 편하다"라는 것이 제일 인상이 깊습니다.
그런데 "옛날부터 있는 전형적인 Java애플리케이션 개발의 힘들다"라는 배경을 근거로 한다면, "Spring Boot가 왜 좋은가?"라는 관점에서 이야기를 해봅니다. 자세한 나중에 설명하겠지만 크게 3개로 정리하면 아래와 같습니다.
우선 아래 그림을 참조하십시오.

Java의 웹애플리케이션은 미들웨어인 Tomcat이나 WebLogic등 위에 애플리케이션 본체가 되는 WAR(서블릿등이 포함됨)를 배치하고 동작시킵니다.애플리케이션 본체가 App Library(다양한 Jar)를 참조하여 그것들도 미들웨어의 구성이나 그 아래에서 실행되는 Java Runtime에 의존하는 구성입니다.
이런 구성을 취하는 것으로 애플리케이션이 의존하는 다양한 라이브러리(예로 데이터베이스 드라이버, 프레임워크 등)을 애플리케이션으로부터 분리하여 다른 애플리케이션에서도 재이용이 가능하게 되는 장점이 있습니다.
다만, 이것은 중앙의 클래스 로더에 의해 로드된 스택 그림처럼 있도록 "어떤 라이브러리의 어떤 클래스가 실제로 로드되고 있는지를 알기 어려운, 버전이 다른 동일한 라이브러리가 같이 있다"라는 문제가 일어납니다.
이런 구성으로 "미들웨어에 취약성이 발견되어 버전업그레이드의 필요성이 발생합니다"라고 하면 미들웨어 업그레이드할뿐만 아니라 발밑의 Java Runtime을 대응한 새로운 버전으로 업그레이드하여 위 애플리케이션이 제대로 동작 또는 다시 테스트가 필요합니다. 다시 테스트하지 않으면 런타임 변경과 관련된 문제, 라이브러리 버전 의존 문제등을 찾을 수 없기 때문입니다. 즉, "개발환경에서 작성한 애플리케이션의 War가 프로덕션 환경의 런타임이나 미들웨어 위에서 실행되는지가 궁금하다"라는 문제가 있다는 것입니다.
개발환경이나 스테이징 환경에서 실시한 테스트를 제외하더라도 프로덕션 환경에서 라이브러리 주위의 트러블이 발생하지 않는다고 보증하기는 어렵습니다.
반면에 오른쪽 Spring Boot의 구성을 봅시다.
이것은 미들웨어(Tomcat, Jetty)별로 애플리케이션 본체를 1개의 애플리케이션의 패키지(직접 실행할 수 있는 Jar)로 정리하여 그것을 런타임으로 동작시킨다는 구성을 취하고 있습니다. 이것은 "Fat Jar"라고 말하며 미들웨어의 라이브러리를 재사용할 수 없게 되는 것의 앞서 언급한 변경이나 이식의 난이도를 대폭 낮춰줍니다. 매우 큰 규모의 JavaEE(JakartaEE)로 이를 실현하려고 하면 크기가 커지고 어려워지면서 비교적 심플하고 경량인 Spring Boot이면 이런 구성을 간단하게 취할 수 있습니다.
심플한 올인원 구성이므로 애플리케이션 환경의 구축에 번거로움없이, 배치를 반복하는 것도 간단하게 할 수 있어 통합테스트도 하기 쉽습니다. 이 Spring Boot 애플리케이션은 Maven과 Gradle이 있는 환경이라면 누구나 쉽게 만들 수 있습니다.
아래 Spring 공식 사이트에서 Spring Boot 애플리케이션 템플릿을 필요한 라이브러리를 추가한 구성으로 다운로드할 수 있습니다. 다운로드한 프로젝트 파일에 "Hello World"코드를 작성하고 빌드하는 것만으로 Jar를 만들 수 있고 실행하는 것만으로 애플리케이션이 실행됩니다.
Spring Boot를 이용하는 첫번째 장점인 "애플리케이션의 구조가 간단하고 모든 것이 패키징되어 있기 때문에 구축이 간단하고 이식성이 뛰어납니다"가 됩니다.
개발작업에 있어서의 개발자의 생산성의 정의는 다양하지만, 알기 쉬운 것은 아래와 같습니다.
이 글 시작부분에서도 말했듯이 필자는 Python으로 코드를 작성하는 경우가 많지만, 그것은 레거시 Java에 비해 다른 점이 우수하기 떄문입니다. (이 부분은 어디까지나 소규모 개발에 한정함)
이런 특징이 있다면, 언어나 프레임워크에도 익숙해져 버리면, 보급되고 있는 방법이라면 무엇이라도 좋다고 생각됩니다. 자바의 초기 JSP나 Servlet 및 Structs와 같은 오래된 프레임워크는 종종 "개발 사이클"까지하기 때문에 너무 작은 개발에 적합하지 않습니다.
제대로 애플리케이션의 설계를 해서 비슷한 구조를 대량으로 만들 수 있으면 문제가 없지만, 새로운 구조를 새롭게 구현하는 경우,코드량도 많아지고 다른 코드에 얽혀 있으면 테스트등을 하기 어렵습니다.
한편, Spring Boot는 Python에서 말하면 풀스택 프레임워크인 Django보다 가벼운 프레임워크인 Flask와 비슷합니다.
"어떤 URL에 액세스가 되면 거기에 묶인 처리를 호출합니다"라는 처리를 간단하게 작성할 수 있는 것입니다. 또한, Spring Boot의 프레임워크로서 DI(의존성 주입)을 사용하는 것을 "구조로서 제공하기고 있기"때문에 모듈별로 독립적인 느슨하게 결합된 프로그램 작성이 쉬워집니다. 보다 구체적으로 말하면, "클래스A가 클래스B를 사용하는 상황에 있어서, 클래스A가 클래스B에 의존하기 어려워집니다"라는 것입니다.
느슨하게 결합된 코드가 된다는 것은 클래스마다 유닛테스트를 쓰기 쉽다고 하는 것이므로, 신규 개발시에 테스트를 써 버리면 처음부터 버그가 적고, 코드를 변경해도 문제를 다시 테스트로 체크하기 쉬운 코드를 만들 수 있습니다. DI를 도입하지 않으면 이전 클래스A의 테스트는 클래스B의 구현에 의존합니다.
2개 정도의 클리스 정도라면 문제없겠지만, 보다 대규모가 되면 수십개의 클래스가 밀접하게 결합하고 있는 상황이 됩니다. 그런 상황에서 테스트를 하고 문제를 발견헀다라도 어디에 문제가 있는지 매우 이해하기 어렵습니다.
이런 문제를 DI로 해결함으로써, 새로운 코드를 추가하거나 기존 코드를 변경하는 것도 난이도가 떨어지고 결과적으로 코드를 작성하는 생산성이 높아집니다. 물론 스스로 DI프레임워크를 도입하는 것으로 문제를 해결할 수 있지만, 처음부터 있는 "있는 것의 DI를 사용"하는 쪽이 상급레벨 개발자도 초보자인 개발자에게도 간단할 것입니다.
DI의 기능은 Spring의 전매특허가 아니라, JavaEE에서도 실현할 수 있지만, Spring쪽이 보다 근간에 있어서 강제력이 강한 인상이 있습니다.
덧붙여 개발 프로젝트에 따라서 JavaEE를 이용핮 ㅣ않는 경우가 있을까 생각해 보면, 그런 경우, Spring을 검토하는 것도 좋다고 생각합니다. Spring에서 JavaEE의 일부(예: JPA등)를 이용하는 경우가 많지만 JavaEE API에 의존하지 않습니다.
지금까지의 Spring Boot를 이용하는 2번쨰 장점인 "Spring Boot프레임워크가 애플리케이션에 필요한 기초기능을 심플하게 제공하며, 특히 Spring Framework 본체의 DI(의존성 주입)의 구조가 우수"합니다.
Spring Boot라는 이름을 처음 들은 Java 개발자도 많다고 생각되지만, Spring이라고 하는 프레임워크를 들은 적이 없는 분은 적을지도 모릅니다.
왜냐하면 Spring Framework는 오랜 역사가 있어 사용자수도 많은 프레임워크이기 때문입니다.
이름에서 알 수 있지만 Spring Boot는 이 Spring Framework의 프로젝트 중 하나입니다. 오리지널 Spring Framework는 역사가 오래되어 다양한 기능을 가지는 한편, 구조가 복잡화(예를 들어 다양한 파리미터 파일) 하고 있습니다.
물론 이것이 절충안 어쩔 수 업습니다. 이런 상황에서 Spring Boot프로젝트가 시작되어 새로운 개발자라도 쉽게 Spring 애플리케이션을 개발할 수 있게 되었습니다.
아래 그림은 Spring 생태계의 다이어그램입니다.

세번째 장점은 "성숙한 커뮤티니와 풍부한 에코시스템"입니다.
자바에 한정하지 않고 소규모로 시작한 애플리케이션은 성장함에 따라 거대화가 되어 갑니다. 예를 들어, 전형적인 웹 3계층 애플리케이션입니다. 많은 조직에서 소프트웨어 개발은 역할별로 팀으로 나누이져 있으며 위의 경우, 프론트엔드, 백엔드, 데이터베이스로 나뉩니다. 개발 초기에는 애플리케이션의 크기도 작고 설계도 클리어하기 때문에 3계층의 각 계층 사이에는 밀접하게 연계할 수 있습니다.
예를 들어, 각 계층마다 2명으로 구성된 6명의 팀으로 개발한다고 가정해 봅시다. 프론트엔드에서 표시하는 카테고리 목록을 백엔드로부터 얻기 위해서는,
해당 작업을 다양하게 갖추어서 실시할 필요가 있습니다. 총 6명이라면 개발자간의 거리도 가깝기 때문에 토론이나 설계, 구체적인 변경작업에 그다지 시간이 걸리지 않게 되는 것입니다.
다만, 스타트업은 어쨋든, 많은 엔터프라이즈에서 큰 제품의 개발에는 각 팀이 수십면으로 구성되는 것이 드문일이 아닙니다. 이런 상황에서는 지금까지의 레이어별 팀분할 외에 취급하는 컴포넌트별 팀분할이 발생합니다.이런 상황에서 앞에서와 같이 프론트엔드, 백엔드, 데이터베이스에 걸치는 변경이 필요한 새로운 기능의 개발이 필요하다면 그것은 약간의 프로젝트가 됩니다.
우선, 같은 컴포넌트내의 레이어간에 협의를 통해 변경인트를 정리합니다. 그렇게 하면, 많은 경우, 이웃의 컴포넌트와의 공유 개소등에 영향이 미치는 것을 알고 영향이 있는 레이어의 컴포넌트간에서 조정할 필요가 발생합니다. 그리고 변경 작업을 실행합니다. 소규모화된 옛날이라면 하루안에 끝났던 작업이 팀이 커지고 관계자의 수가 늘어나 몇 주간으로 필요이상으로 늘아나게 됩니다.
마이크로서비스가 주목받은 것은 이런 규모의 문제를 해결하는데 유용한 수단입니다. 예를 들어 방금전 3계층 애플리케이션을 "오리지널 설계를 유지한 채, 모놀리식으로서 서서히 거대화가 됩니다. 아니라, "작은(마이크로)단위로 나뉜 컴포넌트(서비스)를 조합하는 설계"로 했다고 봅시다.
지금ㄲ싸지는 프론트엔드, 백엔드, 데이터베이스와 같은 기술적 관점에서 팀을 나누고 있지만, 이 구성에서는 서비스라는 비즈니스 친화적인 관점에서 기능별로 팀을 분할하고 있습니다. 예를 들어, 프론트에서 데이터베이스까지 담당하는 결제 서비스 팀, 쇼핑몰의 중심적인 상품페이지를 프론트엔드에서 데이터베이스까지 담당하는 쇼핑몰 서비스 팀, 재고관리와 발송시스템의 창고 서비스팀의 상태입니다.
기존과 같은 기술별로 세로 나누어져 있던 팀에서는 상품페이지나 결제페이지의 구성을 변경혀라면 그것은 팀을 넘는 작업이 됩니다. 한편, 기능(서비스)마다 팀을 구성했다고 하면, 각 기능내에 닫는 설계변경은 팀내에서 완결합니다. 변경도 특정 서비스만이기 때문에 범위가 작습니다. 어느 쪽이 빠르고 정확한 변경을 할 수 있는지 생각하면 후자쪽이 유리하다는 것을 알 수 있습니다. 참고로 복수 서비스에 걸치는 변(많지 않을 것으로 기대됨)은 서비스간의 조정이 필요하기 때문에 시간이 걸립니다.
이런 서비스별로 분할하는 기술은 도메인 주도 설계(DDD)등이 뮤명합니다. 단, DDD은 애플리케이션 안에서 도메인을 기반으로 코드를 분할 하는 생각이기 때문에, 그 구체적인 구현방법은 프로젝트나 개발자에게 맡겨지고 있습니다. 마이크로서비스는 이 DDD을 사용한 코드분할을 하나의 애플리케이션 내에 컴포넌트 레벨이 아니라 서비스(큰 앱을 구성하는 작업 앱) 레벨에서 강제적으로 수행합니다. 개발하는 서비스는 다른 서비스와 코드가 분리되어 아티팩트크기도 작아지기 때문에 변경(영향범위가 작음)이나 테스트(항목수를 줄임)의 비용도 낮아진다는 장점이 있습니다. 이와 같이 제대로 나누어진 마이크로서비스는 많은 사람들이 머리를 괴롭혀 온 "소프트웨가 커지면 개발효율이 나빠진다"라는 문제에 대한 유력한 하나의 해결책입니다. 그러나 마이크로서비스를 채택하면 몇 가지 비용이 발생합니다.
이런 보상비용이 마이크로서비스의 이점보다 작은 경우에만 마이크로서비스를 채택해야 합니다. 반대로 말하면, 마이크로 서비스를 채용하는 것이 적합하지 않는 장면도 있습니다.
만약, 향후 마이크로서비스를 개발에 이용해 나가고 싶은 경우, 간단한 애플리케이션으로의 이용으로부터 시험해 보는 것을 추천합니다. 복잡한 애플리케이션일수록 마이크로서비스 설계도 어려워집니다. 익숙하지 않은 동안 간단한 애플리케이션으로 마이크로서비스를 사용한 개발에 익숙해질 것입니다.
개발 프레임워크는 만들고 싶은 애플리케이션에 따라 달라집니다. 예를 들어, 2010년경 큰 Java엔터프라이즈용 서비스와 당시 유행했던 Ruby On Rails를 사용하는 경우와도 다릅니다. 대기업이 수백명의 인재를 1개의 Ruby On Rails애플리케이션 개발 및 유지보수를 하는 것도 아니고 5명정도의 웹계열 스타트업이 Java의 거대한 프레임워크를 선택하는 일도 없을 것입니다.
마이크로서비스를 구성하는 하나의 작은 서비스 개발은 어쨋든 큰 엔터프라이즈 Java보다 Ruby on Rails에 가깝습니다. 수십만줄의 코드로 움직이는 서비스가 아니라, 하나하나는 수천행정도로 움직이는 작은 서비스의 모임입니다. 그런 상황에서 Java를 사용한 개발을 원한다면, 그것에 적압한 프레임워크를 선택해야 합니다. 컴파일 및 배포에 충분하게 사용하려는 프레임워크를 5000행의 서비스에 사용할 이유가 없습니다. 이런 이유로 "보다 가볍고 현대적인 개발기술을 사용하기 쉬운 Spring Boot"는 마이크로서비스 용도에 적합합니다.
참고로 Spring(Spring Boot이외의 프로젝트도 포함)에는 "마이크로서비스로서 사용되는 것을 상정한 기능"의 몇개인가가 처음부터 내장되어 있습니다. 예를 들어, 서비스 간의 통신을 트레이싱하기 위한 구조가 내장되어 있거나, 애플리케이션의 입구로 자주 이용되는 "API 게이트웨이"등도 제공되고 있습니다. Spring Boot이외의 다른 마이크로프레임워크를 이용하는 것으로 Spring Boot와 같이 작은 서비스를 신속하게 개발할 수 있지만, 이 마이크로서비스 독특한 기능이 필요하면 열심히 만들어 넣어야 할 수도 있습니다.
마이크로서비스의 자세한 이야기는 전문서적을 참고하시고 여기에서는 Spring Boot를 사용한 마이크로서비스의 구성예에 대해 소개합니다. 아래 Spring의 공식페이지에 있는 마이크로서비스 샘플 구성도입니다.

그림의 좌측은 애플리케이션의 액세스 기기로 브라우저뿐만 아니라 모바일앱이나 IoT기기로부터의 액세스도 받아들이고 있습니다. 이런 기기로부터의 통신은 애플리에케이션 입구에 있는 "API Gateway"로 규칙에 따라, 백엔드에 있는 Spring Boot로 만들어진 여러개의 서비스로 나뉩니다. 이것은 스스로 전성고칙을 세밀하게 정의할 수 있는 역방향 프록시와 같은 존재이기 때문에 스크래치에서 직접 개발하는 것에 비해 저비용으로 신뢰성이 있고 기성의 역방향 프록시 앱을 사용하는 것보다는 유연하게 프로그램을 실행을 제어할 수 있습니다.
Spring은 이 API Gateway를 Spring Cloud Gateway로 제공합니다. 백엔드에 있는 SPring Boot의 애플리케이션군은 앞의 예제처럼 "결제서비스", "쇼핑페이지 서비스"등과 분리되어 있습니다. 이들은 각각 자신의 데이터베이스등을 가지고 있으며 하나의 서비스로 완결되어 있습니다.
서비스간 연결은
1. 쇼핑서비스로 주문
2. 쇼핑서비스가 주문을 결제서비스에 요청(REST API, 메시지큐 등)
3. 결제 서비스가 요청을 처리
와 같이 필요시에 제휴를 행합니다.
서비스 간의 연계는 애플리케이션에 필수적이지만, 너무 연동되어 서비스간의 결합이 너무 강해지는 것을 방지해야 합니다. 예를 들어, 쇼핑페이지 서비스가 장바구니에 있을 때마다 결제서비스에 정보를 보내고 있으면, 쇼핑서비스는 결제 서비스없이 동작하지 않는 애플리케이션이 되어 버립니다.
참고로 그림 우축에 있는 그림은 마이크로서비스를 구축할 때에 편리한 Spring의 프레임워크군입니다. 예를 들어 Slueth는 방금 전에 설명한 서비스간의 통신을 추적하는 매커니즘입니다.
마지막으로 Spring Boot라는 주제에서 벗어나지만, 마이크로서비스에 컨테이너가 채용되는 경우가 많은 이유를 설명합니다. 마이크로서비스는 철저하게 논해버리면 종래의 모놀리식인 애플리케이션내의 모듈은 작은 애플리케이션으로서 강제적으로 분리하여 작은 결합하는 구조입니다. 따라서, 마이크로서비스의 각 서비스를 가상머신은 확실하게 베어메탈로 구축해도 실수는 아닙니다. 실제로 마이크로서비스만큼은 아니더라도 많은 엔터프라이즈 애플리케이션은 가상머신 레벨에서 기능 분리되어 이쓴ㄴ 경우가 많습니다.
결론을 말하면, 이것은 비용과 속도문제로 인한 것입니다. 예를 들어, 20개의 서비스로 구성된 마이크로서비스를 가상머신(Virtual Machine)으로 구성한다고 가정해 봅시다. 필요한 가상머신의 대수는 각 서비스마다 "프론트엔드(없는 것도 있음), 백엔드, 데이터베이스를 중복성을 갖게 구축한다"라고 생각하면 100VM이 넘어갈 가능성이 있습니다. 개발환경 자체는 마이크로서비스이므로 서비스별로 5VM정도를 구축하면 좋을지 모르지만, 서비스간의 제휴 테스트환경이나 스테이징환경, 프로덕션 환경에 각각 100VM을 움직이는 것이면 구축 및 유지비용은 상당합니다. 그리고 각 서비스의 소프트웨어 업데이트가 매일처럼 실행되는 환경에서 VM에 대해 그 변경을 수동으로 추가하는 것은 현실적이지 않습니다.
컨테이너(Docker등)와 컨테이너 오케스트레이션(Kubernetes등)에는 다음과 같은 특징이 있습니다.
이런 특징을 사ㅣ용하는 것으로 마이크로서비스의 방대한 서비스를 사람의 손을 많이 거치지 않고 빠르게 구축 및 업데이트, 관리가 가능해집니다. 100VM에서는 속도와 비용면에서 마이크로서비스를 채용할 수 없어도 100컨테이너라면 채용할 수 있는 가능성이 있다는 것입니다.