# 서버 아키텍처 설계

TIP

구 블로그에 남겨둔 기록을 옮겨왔다.

한 컴포넌트는 독립적으로 실행이 가능하게 설계해야 한다. 앱은 여러 컴포넌트를 조립하는 형태로 만들어야 한다.

보통 쓰이는 3-tier(presentation, business logic, data access)구조는 쓰다보니 너무 뭉뚱그려져있는 경우가 많다. rest 서버만 구현할경우 크게 문제가 없을 수 있으나 개인적인 경험으로는 좀 더 역할을 나누는게 좋아 보였다. 모든 컴포넌트가 이렇게 세분화할 필요는 없고, 규모가 커짐에 따라 필요에 따라 계층을 나누면 될 듯 하다.

핵심은 서버 내에서도 서비스라는 개념을 두어 목적에 따라 독립된 컴포넌트처럼 서비스를 제공하게 만들자는 것이다. 서비스는 자체적으로 돌아가는 스레드가 있을 수도 있고, 상위 레이어로부터 호출될 수도 있고, 서비스끼리 호출하는 경우도 있을 것이다. 프론트엔드이긴 하지만 Angular의 service, React의 flux/redux. Vue의 vuex의 개념과도 매우 유사하다. flux 아키텍쳐가 백엔드 서버쪽에도 이미 있는 개념인지는 모르겠지만, 의도는 동일하다. 데이타의 이동이 인접(상/하) 레이어간에 전달이 아니라, 어느 레이어든지 접근이 가능한 독립적인 흐름처럼 다루자는 것이다. 구조 기반이 아닌, 목적 기반의 설계이다. 요즘 유행하는 MSA(Micro Service Architecture)와도 일맥상통한다. 원래 SOA(Service Oriented Architecture)라고 언급하려 했지만 이미 10년전 철지난 용어라서 MSA로 변경하였다. 따져보면 SOA나 MSA나 추구하는 바는 같다. 이는 다른 글에서 다루어보도록 하자.

컴포넌트를 이루는 구성요소를 정리해보면...

  • 인터페이스를 정의, 입력값 검증, 결과값 가공, 비즈니스 로직(active/passive), 라이브러리, 데이타 입출력, 모델 정도로 역할을 나눌 수 있겠다. 외부에서 호출당하는게 아닌, 능동적으로 동작하고 외부를 호출하는 경우는 어떻게 정의해야 할지 조금 더 생각해봐야 겠다.

자세히 정의해 보자면... (용어는 참 정하기가 어렵다.)

  • 0_interface(export): 컴포넌트 외부로 노출하는 인터페이스를 위치시킨다. 인터페이스가 없는 언어에서는 단순히 validation 티어의 동일한 이름의 함수를 호출하도록 만든다.
  • 1_middleware(filter): 실제 동작에 들어가기전에 preprocessor처럼 선가공하는 계층. 세션 검증, 필요 정보 인젝션 등이 이루어질 것이다.
  • 2_validation: interface 레이어에 의해 호출. 입력값 검증만 수행하고 presentation 레이어를 호출한다. 반환된 결과는 가공 없이 interface 레이어로 전달.
  • 3_presentation: validation 레이어에 의해 호출됨. 신뢰할 수 있는 입력값을 바탕으로 필요한 서비스나 핸들러, 라이브러리, 엔티티를 호출하고, 결과를 가공하여 다시 validation 레이어로 반환한다.
  • 4_service: 서비스 레이어는 주로 상태를 가지거나 자체 스레드를 가지는 계층이다. stateful. 인스턴스화 필요. start/stop이 존재할 것이다. 상태나 스레드가 필요 없다면 투명하게 통과해도 될 계층. db pool, logging 등도 여기에 존재할 것이다.
  • 5_handler: 핸들러 레이어는 실제 대부분의 로직을 처리한다. 주로 이벤트 핸들러들이 위치하게 된다. 상태등은 상위 계층인 서비스 레이어로 위임. stateless, static으로 충분. 함수형 프로그래밍에서 말하는 순수 함수와 거의 유사할 것이다. 테스트에도 용이한 계층이겠다. 너무 복잡한 상황이 아니라면 굳이 service 레이어와 구분되지 않을 것이다.
  • 6_library: 컴포넌트와 독립적으로 동작 가능한 inner-component 정도로 여기면 되겠다.
  • 7_entity: control에서 사용하기 쉽도록 설계된 데이터 객체들. 여기서 model을 가공하고 db 연결을 다루어 데이터를 처리한다. 기본적으로 CRUD, 필요에 따라 간편 함수나 최적화된 쿼리를 수행하도록 만들 수 있겠다. 예를 들어, value=value+1 같은 쿼리는 굳이 select/update 할 필요 없을 것이다. 이런 최적화 쿼리를 수행하기 위해서는 ORM이 적절하지 않을 것이다.
  • 8_model: ORM에 대응되는 모듈. 객체 정의와 검증, 테이블 생성, 테이블 변경 등이 존재할 것이다. 모델 버전별 처리를 하는 마이그레이션 코드도 존재할 수 있겠다. 역시 복잡하지 않은 경우라면 굳이 entity와 분리할 필요 없을것 같다.

폴더 구조는 컴포넌트(관심영역) 기준으로 나눌 수도 있고, 티어(역할) 기준으로 나눌 수도 있을텐데 예를들어 결제와 환불 컴포넌트를 만들 때,

  • payment.service
  • payment.control
  • refund.service
  • refund.control

이렇게 컴포넌트 기준으로 나누면 시각적으로도 분리되어 있고 컴포넌트를 분리해내기가 용이하다.

  • service.payment
  • service.refund
  • control.payment
  • control.refund

이렇게 티어 기준으로 나누면 유사한 형태의 코드들이 모이게 되므로 리팩토링이 수월해질 것이다.

아마 두가지 방법이 혼용되어 쓰여질것 같다. 프로젝트 내부에서만 쓰일거라면 후자의 형태가 낫고, 정말 재사용이 가능한 컴포넌트로 간다면 전자의 방향이 나을것 같다. 결국 외부에 공개가 된다면 이러한 서브 컴포넌트를 모아서 슈퍼 컴포넌트 네이밍 하나로 공개가 되어야 할것이라 크게 관계는 없겠다.

일단 서버를 구성할때는 위와 같은 모양새면 부족함은 없을듯 하다. 클라이언트 쪽에선 View가 있기 때문에 그대로 적용하긴 어려울듯. View가 있는 프로젝트를 작업할 때 다시 한번 정리해보자.