Armeria와 Spring Boot 연동하기
Armeria와 Spring
Armeria는 라인에서 개발하여 오픈 소스로 운영되는 프레임워크입니다. Armeria를 이용하면 우리가 잘 아는 REST 서비스 부터 gRPC, Thrift 등의 서비스까지 일관된 인터페이스로 편리하게 개발할 수 있습니다. 하지만 Armeria의 장점을 말로만 듣고 실제 서비스에 곧바로 적용하기란 쉽지 않습니다. 다행히 Armeria에서 직접 기존 Spring 레거시 코드에서 Armeria로 점진적으로 마이그레이션 하는 방법을 소개하고 있습니다. 이 글은 Armeria와 Spring을 함께 사용할 때 장점을 간단히 소개하고, LINE 개발자들이 Spring 대신 Armeria를 사용하는 이유에서 소개한 Spring 마이그레이션 과정을 직접 시도하고 그 과정을 정리한 글입니다. (영상)
왜 Armeria를 써야 할까?
실제 연동 과정을 설명하기 앞서 그 이점을 먼저 이야기하겠습니다. Armeria의 장점은 뭘까요? 어떤 상황에서 Armeria와 Spring을 함께 쓰면 좋을까요?
Armeria의 주요한 특징이자 장점은 멀티플렉싱 기반 논블로킹 서버라는 것입니다. 톰캣 기반의 Spring에서는 요청 하나마다 이를 처리하는 스레드를 생성해야 합니다. 이 때 요청이 많아지만 스레드풀이 모두 고갈되면서 새로운 요청을 받지 못해 응답 불능 상태에 빠집니다. 반면, 멀티플렉싱 기반 서버는 요청마다 스레드를 생성하지 않고 이벤트 루프 스레드가 모든 요청을 처리합니다. 이것이 가능한 이유는 I/O 발생 시 응답을 기다리지 않고 비동기로 다른 요청을 처리하기 때문입니다. 또한 Armeria는 I/O 비중이 적은 CPU 중심(CPU-intensive) 작업이거나 스레드를 블록해야 하는 경우 이를 처리하는 스레드를 따로 만들어 작업을 위임할 수 있습니다. (BlockingTaskExecutor
참고) 따라서 멀티스레딩 기반의 Spring보다 적은 수의 스레드로 유연하게 부하를 처리할 수 있습니다.
두 번째 장점으로 Reactive Streams 표준을 지원하여 스트림 기반 서버를 구성할 수 있습니다. 예를 들어 매우 큰 파일 전달하는 서버나 영화 등의 긴 러닝 타임의 영상을 스트리밍하는 서버를 만들 때 전통적인 서버의 경우 모든 데이터를 메모리에 적재한 후 처리를 거쳐 응답합니다. 반면, 리액티브 서버에서는 데이터를 작은 단위로 쪼개 메모리에 적재한 후 처리하기 때문에 메모리 고갈 걱정없이 어려운 요구사항을 해결할 수 있습니다. 이미 유명한 Spring WebFlux 또한 같은 문제를 해결할 수 있는 솔루션입니다. Spring WebFlux 역시 간단한 설정으로 Armeria와 함께 사용할 수 있습니다.
마지막으로 Armeria를 Spring과 함께 사용하면 좋은 이유는 무엇일까요? 저는 Armeria가 모든 면에서 Spring보다 뛰어나다고 생각하지 않습니다. (Armeria를 그렇게 잘 알지 않거든요. 😅) 따라서 무작정 전체를 교체하기보다 레거시 서버에서 잘 돌아가는 부분은 그대로 두고 Armeria의 도움으로 발전시킬 수 있는 부분을 먼저 바꿔나가면 적은 노력으로 큰 이득을 볼 수 있습니다. Armeria는 위에서 언급한 장점을 포함해서 gRPC, Thrift 프로토콜 기반 서비스를 만들 수 있고 이들을 HTTP/1, HTTP/2로 지원하는 유연한 구성을 가져갈 수 있습니다. 이런 장점을 갖고 있음에도 레거시 코드와 통합하기 어렵다면 그림의 떡이겠지만 Armeria가 제공하는 통합 기능은 매우 간편한 마이그레이션을 지원합니다. 뒤에서 설명하겠지만 기존 서비스의 엔드포인트(도메인 네임, IP, port)를 변경하지 않고 gRPC, Thrift 등의 서비스들을 함께 사용할 수 있습니다.
Spring Boot와 연동하기
그럼 이제 실제 연동 과정을 하나씩 짚어보겠습니다. 연동 과정은 armeria-examples 리포지토리의 spring-boot-minimal, spring-boot-tomcat 예제를 참고하여 작성하였습니다.
1. Armeria 의존성 추가하기
먼저 사용 중인 maven이나 gradle 등 빌드 툴에 Armeria 의존성을 추가합니다. 저는 gradle을 사용하기 때문에 이를 기준으로 코드를 작성하였습니다.
2. ArmeriaServerConfigurator
빈 생성하기
그 다음 ArmeriaServerConfigurator 빈을 생성하여 레거시 톰캣 서비스를 그대로 유지하면서 Armeria를 도입합니다.
마지막으로 스프링 프로퍼티 설정을 통해 레거시 톰캣 서비스 포트를 막고 해당 포트로 아르메리아 서비스에 접속할 수 있도록 설정합니다.
벌써 모든 설정이 끝났습니다. 몇 줄 안되는 코드로 레거시 Spring 서비스를 그대로 사용하면서 Armeria가 제공하는 gRPC, Thrift 서비스들을 같은 포트에서 제공할 수 있습니다.
3. Armeria로 REST 서비스 만들어서 추가하기
그럼 이제 Armeria를 이용하여 간단한 RESTful 서비스를 만들어 추가해보겠습니다. 아래 코드는 간단한 TODO 관리 어플리케이션의 등록과 조회 API를 Armeria가 제공하는 어노테이션 기반으로 제작한 코드입니다. 언뜻 봤을 때 Spring의 컨트롤러 코드와 큰 차이가 없습니다. Spring이 제공하는 의존성 주입 또한 똑같이 사용할 수 있습니다. 따라서 기존 레거시 코드에서 Armeria로 이전하는 과정에서 익숙하지 않은 패턴 때문에 겪는 어려움은 없을 것입니다. (Armeria REST service tutorial에서 더 자세한 내용을 보실 수 있습니다.)(@ConsumesJson
, @ProducesJson
어노테이션을 사용하여 JSON 변환도 분리할 수 있습니다.)
이제 TodoAnnotatedService를 Armeria의 서비스로 등록하면 사용할 수 있습니다. 이 과정에서 역시 Spring의 의존성 주입의 도움을 받기 때문에 간단하게 설정할 수 있습니다. 위에서 설정했던 ArmeriaServerConfigurator 빈 설정에서 2번째 파라미터로 TodoAnnotatedService를 추가하면 자동으로 주입되어 Armeria의 ServerBuilder에 등록할 수 있습니다. 이제 TodoAnnotatedService
는 같은 엔드포인트 (도메인 네임, IP, port) 상의 /armeria/todos 경로로 도착하는 요청들을 처리할 수 있습니다.
gRPC, Thrift 서비스 역시 같은 방식으로 쉽게 추가할 수 있습니다. (gRPC 튜토리얼, 글을 쓰는 시점 기준으로 Thrift 튜토리얼은 다른 훌륭한 분께서 만들어주고 계십니다.)
Armeria를 써보자!
멀티플렉싱 기반 비동기 서버 Armeria는 멀티 스레딩 기반의 Spring보다 유연하게 요청 급증으로 인한 부하를 처리할 수 있습니다. 또한 매우 큰 파일 전달, 길이가 긴 동영상 스트리밍 등의 특수한 요구사항을 처리할 때도 스트림 기반 서비스를 통해 문제를 해결할 수 있습니다.
이런 장점을 가진 Armeria를 간단한 설정만으로 Spring과 함께 사용할 수 있습니다. 특히 기존 서비스의 엔드포인트를 그대로 사용하면서 gRPC, Thrift와 같은 RPC 서비스를 추가할 수 있다는 장점이 있습니다. 또한 Armeria가 제공하는 어노테이션 기반 서비스의 코드는 Spring에서 컨트롤러를 작성할 때와 유사한 점이 많아 적은 학습만으로도 충분히 적용 가능합니다. gRPC와 Thrift 서비스 역시 일관성 있는 인터페이스를 제공하기 때문에 간단히 추가할 수 있습니다.
Armeria는 오픈소스로 운영되지만 라인에서 주도적으로 개발과 관리를 맡고 있으며 라인 내부에서도 많이 사용되고 있다고 합니다. 따라서 안정성이나 꾸준한 관리 측면에서 장점이 많다고 생각합니다. 만약 Spring으로 해결하기 어려운 요구 사항을 맞닥뜨렸거나 RPC 서비스 사용을 고려한다면 Armeria 도입을 충분히 고려해보는 것은 어떨까요?