아키텍처에 대한 이해를 높여보자!
앞 장에서는 Data Flow 스타일 중 Pipe and Filter와 Uniform Pipe and Filter 스타일을 살펴보았다. 이번 장에서는 다양한 아키텍처 스타일을 추가로 탐구하여 네트워크 기반 소프트웨어 아키텍처에 대한 이해를 심화하는 것을 목표로 한다. 이 장을 끝으로 REST 아키텍처 스타일을 이해할 준비는 끝나게 된다.
Replication Styles에서는 Replicated Repository와 Cache 스타일에 대해 설명한다. Replication Styles는 아키텍처의 구성요소를 복제(replication)하는 방식을 다룬다.
Replicated Repository 스타일은 동일한 서비스를 여러 개 복제함으로써 데이터 접근성(accessibility)과 확장성(scalability)을 개선하는 방식이다. 예를 들어, Git과 같은 버전 관리 시스템이나 데이터베이스, Redis의 액티브-스탠바이(active-standby) 구성이 대표적이다. 이러한 방식은 여러 복제본을 통해 장애 상황에서의 복원력과 서비스의 가용성을 높일 수 있다.
Cache 스타일은 요청을 처리한 결과를 캐시에 저장하고, 이후 동일한 요청이 들어올 때 서버에 다시 요청하는 대신 캐시된 값을 응답하는 방식이다. 따라서 캐시는 부분적 복제 전략이라고 할 수 있다. 캐시에 너무 오래 남아있으면 데이터의 최신성을 보장할 수 없기에 만료(expired)된 것으로 취급하여 삭제해야 한다.
Cache 스타일은 캐시된 데이터에 대해서만 성능 개선 효과를 발휘하기 때문에, 모든 요청에 대해 동일한 성능 개선을 기대하기 어렵다. 따라서 Replicated Repository와 비교했을 때 전반적인 고객 체감 성능(user perceived performance)은 상황에 따라 달라질 수 있다. 만약 클라이언트가 새로운 요청을 하거나 캐시에 없는 데이터를 요청하는 경우, 캐시 노드를 거치는 추가적인 비용(overhead)이 발생하면서도 결국 서버로부터 직접 데이터를 가져와야 하기 때문이다.
네트워크 기반 소프트웨어 아키텍처에서 캐시의 역할은 매우 중요한데, REST 스타일에서도 다양한 지점에서 캐시를 적극 활용하고 있다. 캐시는 서버의 부하를 줄이고, 응답 시간을 단축하여 사용자 경험을 향상시키는 중요한 요소로 작용한다.
Replicated Repository 스타일은 아래의 속성들을 강화한다.
고객 체감 성능(user perceived performance)
확장성(scalability)
신뢰성(reliability)
Cache 스타일은 아래의 속성들을 강화한다.
확률적 고객 체감 성능(user perceived performance)
효율성(efficiency)
확장성(scalability)
단순성(simplicity)
Hierarchical Styles은 계층화 구조를 만드는 스타일로, Client-Server, Layered Client-Server, Client-Stateless Server 등의 다양한 아키텍처를 포함한다.
Client-Server는 가장 널리 사용되는 아키텍처 스타일 중 하나이다. 이 스타일이 추구하는 핵심 원칙은 관심사의 분리(Separation of Concerns)이다. 관심사의 분리는 시스템의 기능을 컴포넌트들에게 어떻게 적절하게 분배할 것인지를 다루며, 이를 통해 시스템의 복잡성을 줄이고 효율성을 높이는 것을 목표로 한다.
일반적으로, 클라이언트는 사용자 인터페이스와 관련된 기능을 담당한다. 반면에 서버는 데이터의 저장과 관련된 역할을 수행한다. 이러한 역할의 분배를 통해, 서버는 단순한 구조를 유지할 수 있게 되고, 단순한 구조의 서버는 자연스럽게 확장성(scalability)에 집중할 수 있는 기회를 얻게 된다.
또한 서버와 클라이언트가 서로 다른 관심사를 가질 때, 각 컴포넌트들은 자신만의 독립적인 역할을 가지기 때문에, 각자의 변경에 따른 다른 부분에 대한 영향력 또한 최소화된다. 이러한 낮은 의존성 덕분에, 서버와 클라이언트 각각은 독립적으로 진화할 수 있는 가능성(independent evolvability)을 얻는다.
Client-Server 아키텍처는 이러한 관심사의 분리를 통해 단순성, 확장성, 그리고 독립적 진화 가능성을 확보하는 것을 목표로 하는 강력한 설계 방식이라고 할 수 있다.
Layered System은 여러 개의 층(layer)으로 구성된 시스템을 의미한다. 이 구조에서는 각 계층이 상호작용할 때, 바로 옆의 인접한 계층과만 통신할 수 있으며, 다른 계층의 내부 구조에 대해서는 알 수 없어야 한다. 이러한 제한은 시스템의 결합도(coupling)를 낮추어 각 계층의 독립성을 높여준다.
각 계층이 독립적이기 때문에 다른 계층을 변경할 때 영향을 최소화할 수 있으며, 특정 계층의 컴포넌트는 다른 시스템에서도 재사용될 수 있다. 이러한 구조의 단점으로는 여러 계층을 거쳐야 하는 오버헤드(overhead)가 추가되고, 응답 시간(latency)이 지연될 수 있다는 점이 있다.
Layered System이 Client-Server 스타일과 결합되면, 클라이언트와 서버는 각각 여러 계층의 구조로 진화하게 된다. 이때, 클라이언트가 서버에게 요청을 보내는 구조에서는 클라이언트가 최상위 계층의 역할을 하게 된다. 또한 클라이언트 레이어에서는 서버 레이어의 내부 구조에 대해서는 알 수 없다. 이러한 은닉성(hiding)은 시스템의 모듈성을 높여주고 유지보수가 쉽게 만들어준다.
클라이언트 레이어에는 프락시(proxy) 컴포넌트가 추가될 수 있으며, 기존의 클라이언트 입장에서 프락시는 서버처럼 보이지만 실제로는 클라이언트의 요청을 받아서 서버로 대신 전달하는 역할을 한다. 이러한 프락시는 보안상의 이유로 위험한 사이트에 대한 접근을 차단하거나, 클라이언트 사이드 로드 밸런서의 역할을 수행할 수 있다.
서버 레이어에는 게이트웨이(gateway) 컴포넌트가 추가될 수 있다. 게이트웨이는 클라이언트나 프락시 입장에서는 그저 평범한 서버처럼 보이지만, 실제로는 중간 역할을 수행하는 중개자 컴포넌트이다. 게이트웨이는 서버 사이드 로드 밸런싱을 하거나, 인증 및 보안과 같은 모든 서버의 공통 관심사를 처리하는 역할을 한다. 이러한 중개자 컴포넌트들은 시스템의 성능과 보안을 향상시키는 데 중요한 역할을 한다.
Layered System과 Client-Server를 결합하여 Layered Client-Server 스타일을 만들 수 있다는 것을 확인했으며, 그 구조는 아래와 같이 표현할 수 있다:
Client ⇒ Proxy(클라이언트 중개자) ⇒ Gateway(서버 중개자) ⇒ Server
Layered Client-Server 스타일은 아래의 속성이 개선된다.
진화 가능성(evolvability) → 큰 폭으로 개선된다.
재사용성(reusability)
단순성(simplicity)
확장성(scalability) → 큰 폭으로 개선된다.
Stateless Server는 서버 컴포넌트가 세션 상태를 유지해서는 안 된다는 제약을 추가한다. 여기서 세션 상태란 사용자의 상태 정보를 유지하는 것을 의미한다. 따라서 Stateless Server가 되기 위해서는 클라이언트가 매 요청마다 필요한 모든 정보를 서버에 전달해야 하며, 세션 상태는 온전히 클라이언트 측에서만 유지된다.
로이 필딩이 생각하는 상태(state)는 애플리케이션의 상태를 의미하는데, 이때 애플리케이션은 웹 페이지를 의미한다(단, 브라우저 기반 애플리케이션이라고 가정한다). 로이 필딩은 브라우저에 표시되는 화면 그 자체가 애플리케이션의 현재 상태여야 가장 이상적인 상태관리가 가능하다고 생각했다.
Client-Stateless Server 제약은 여러 가지 장점을 가져다준다.
가시성(Visibility): 서버가 세션 상태를 유지하지 않기 때문에, 모니터링 시스템은 각 요청과 응답에 담겨 있는 정보만으로 요청의 의미를 충분히 파악할 수 있다. 이러한 특성 덕분에 가시성이 높아지며, 시스템의 동작을 이해하고 관리하는 데 도움을 준다.
신뢰성(Reliability): 서버가 상태를 유지하지 않기 때문에, 시스템의 일부 서버에 장애가 발생하더라도(partial failures), 복구 과정이 더 간단해진다. 서버가 상태를 기억하지 않으므로 상태 유실로 인한 피해가 발생하지 않으며, 복구 시에도 별다른 상태 정보를 복원할 필요가 없다. 그저 여분의 서버를 준비해 놓는 것만으로 쉽게 신뢰성을 높일 수 있다.
확장성(Scalability): 서버가 상태를 유지하지 않기 때문에, 서버의 구현이 단순해지고 서버의 추가나 제외가 쉬워지므로, 수평적 확장이 쉬워진다.
Client-Stateless Server 스타일의 단점은 아래와 같다.
네트워크 성능(Network Performance): 서버가 상태를 저장하지 않기 때문에, 클라이언트는 매번 모든 필요한 정보를 요청에 포함해 서버에 전달해야 한다. 특히, 상태 정보가 많거나 데이터를 자주 전송해야 할 경우 네트워크 부하가 증가하게 된다.
Client-Stateless Server에 Layered System의 특성과 Cache 스타일을 결합하면, 매우 강력한 아키텍처 스타일인 Layered Client-Cache-Stateless Server를 만들어낼 수 있다. 이 구조는 확장성과 유지보수성, 그리고 성능을 동시에 고려한 아키텍처로서, 네트워크 기반 애플리케이션에 유용하게 활용될 수 있다. Layered System과 Client-Stateless Server 스타일이 본질적으로 가져야만 하는 오버헤드는 Cache 스타일에 의해 상쇄되거나 오히려 더 빨라질 수도 있다.
Layered Client-Cache-Stateless Server 스타일은 아래와 같이 표현할 수 있다:
Client ⇒ Cache ⇒ Proxy ⇒ Gateway ⇒ Stateless Server
Layered Client-Cache-Stateless Server 스타일은 아래의 속성이 강화된다.
확장성(scalability): 막강한 확장성을 가진다.
단순성(Simplicity)
진화 가능성(Evolvability)
재사용성(Reusability)
가시성(Visibility)
신뢰성(Reliability)
Mobile Code 스타일은 실행해야 할 명령어 혹은 소스코드를 이동시키는 방식에 대한 것이다. 로이 필딩은 데이터 혹은 코드를 굉장히 중요한 아키텍처의 컴포넌트로 보았다. 예를 들어, HTML 혹은 자바스크립트 소스코드는 HTTP 요청과 응답에서는 그저 데이터일 뿐이지만, 일단 브라우저에 전달되면 사용자와 상호작용하고 동적인 기능을 제공하는 데 핵심적인 역할을 한다.
Mobile Code Style에서는 Virtual Machine과 Code on Demand를 살펴보겠다.
모바일 코드 스타일에서는 가상 머신(VM) 또는 인터프리터의 개념이 필요하다. 이동된 코드(mobile code)가 실행될 때, 안전하고 신뢰할 수 있는 방식으로 실행되도록 보장하기 위해 통제된 환경이 필요하기 때문이다.
Virtual Machine 스타일은 각 플랫폼에게 VM 구현의 책임을 전가한다. 일단 특정 플랫폼을 위한 VM이 구현되었다면, 그 플랫폼에서는 작성된 모든 코드를 실행할 수 있다. 즉, 코드가 작성된 만큼 기능이 추가되는 효과가 발생한다. 이러한 관점에서 Virtual Machine 스타일은 이식성(portability)과 기능 확장성(extensibility)이 좋다고 할 수 있다.
다만 모니터링 시스템에서는 코드만 봐서는 그 역할을 파악하기 어렵기 때문에 가시성(visibility)이 낮아지는 단점이 있다. 또한, VM(Virtual Machine)이 실행되는 환경을 관리해야 하기 때문에 전체 시스템의 단순성(simplicity)이 떨어질 수 있다. 하지만 VM을 통해 기능을 동적으로 확장할 수 있으므로, 플랫폼 자체의 기능은 단순하게 유지될 수 있다. 따라서, 단순성 측면에서 이러한 단점은 어느 정도 상쇄된다고 볼 수 있다.
Code on Demand 스타일은 클라이언트가 데이터나 리소스를 어떻게 처리해야 할지 모를 때, 서버로부터 처리 방식을 제공받아 적용할 수 있는 유연한 방법이다.
예를 들어, 주민등록번호가 올바른 형식을 갖추었는지 어떻게 확인할 수 있을까? 클라이언트는 서버에 저장된 주민등록번호 처리용 자바스크립트를 다운로드하여 이 문제를 해결할 수 있다.
Code on Demand 스타일을 통해 클라이언트는 기능을 쉽게 확장할 수 있으며(extensibility, configurability), 서버에서 다운로드한 코드를 실행해 추가적인 네트워크 요청을 줄일 수 있다. 따라서 고객 체감 성능(user perceived performance)과 효율성(efficiency)이 향상된다. 또한 작업을 클라이언트 쪽으로 내릴 수 있으므로(off loading) 서버의 확장성(scalability)도 개선될 수 있다.
하지만, VM 스타일의 도입으로 인해 아키텍처의 단순성(simplicity)이 저해될 수 있으며, 코드가 이동하면서 가시성(visibility)이 낮아지고, 이로 인해 신뢰성(reliability) 문제도 발생할 수 있다.
Layered Client-Cache-Stateless Server 스타일에 Code on Demand 스타일에 더해지면, 성능 확장성(scalability)과 기능 확장성(extensibility)이 더욱 개선된다.
이를 도식화하면 아래와 같은 형태가 된다:
Layered Client-Code on Demand-Cache-Stateless Server
이번 장에서는 다양한 아키텍처 스타일과 조합 방법을 살펴보았다. 사실 로이 필딩은 더 많은 스타일들이 소개했으나 이 글에서는 REST 스타일을 이해하는데 필수적인 스타일들만을 소개했다.
다음 장에서는 HTTP 1.0이 직면했던 문제와 요구사항이 무엇인지 살펴보고, 이를 해결하기 위해 로이 필딩이 아키텍처 스타일을 REST로 진화시키는 과정을 살펴보겠다.
4편 끝.