인터페이스

클라이언트와 서버 관점에서 생각한다면, 마이크로서비스 아키텍처는 많은 서버가 API를 각각 제공하는 구조라고 할 수 있습니다. 클라이언트가 모든 API를 호출해야 한다면 어떤 일이 벌어질까요?

독립된 각 API를 호출하려면 클라이언트는 모든 서버의 접속 정보와 패킷 구조를 알아야 합니다. 모든 API에서 일일이 코드를 작성해야 한다면, 이 작업은 생각만으로도 끔찍합니다.

통일된 인터페이스의 필요성

통일된 인터페이스의 필요성

이러한 문제를 해결하려면 통일된 패킷 구조와 인터페이스 하나로 모든 API를 호출할 수 있는 구조를 만들어야 합니다.

레이서의 필요성

클라이언트가 인터페이스 하나로 모든 마이크로서비스를 호출할 수 있으려면 레이어 개념이 필요합니다. 이는 마이크로서비스들과 통신할 수 있는 인터페이스 레이어를 하나 두는 개념입니다. 마이크로서비스보다 상위 계층의 레이어를 두고, 클라이언트의 접속을 처리하게 하면 통일된 인터페이스를 이용해 모든 API를 호출할 수 있습니다.

시스템 레이어

시스템 레이어

게이트웨이와 마이크로서비스를 분리된 레이어로 설계했습니다. 클라이언트는 게이트웨이로 접속합니다. 이때 게이트웨이는 HTTP, TCP, PROTOBUF 등 다양한 형태의 서버가 될 수 있으며, 게이트웨이도 여러 개 존재할 수 있습니다. 단 게이트웨이 간에는 통신을 하지 않습니다.

게이트웨이는 망형 구조로 연결된 마이크로서비스들과 연결됩니다. 앞에서 만든 Distributor를 이용해 모든 마이크로서비스와 접속하거나 필요한 마이크로서비스와 제한적으로 접속할 수도 있습니다. 보안을 위해 접속 IP를 제한할 수도 있고 요청 가능한 API를 필터링할 수도 있습니다.

마이크로서비스는 클라이언트가 접속했을 때와 동일하게 게이트웨이의 요청을 처리합니다. 클라이언트가 접속하는 위치에 따라 게이트웨이와 마이크로서비스가 서로 다른 네트워크에 있기도 합니다. 게이트웨이는 Public 망에 위치하고, 마이크로서비스는 Private 망에 위치해 보안을 강화할 수 있습니다. 보안과 관련된 내용은 13장에서 자세히 알아봅니다.

HTTP 게이트웨이 만들기

인터페이스를 통일해야 하는 이유와 레이어 개념을 알아보았으니, 이제 우리가 만드는 마이크로서비스 아키텍처에 게이트웨이를 추가하겠습니다. 다양한 프로토콜의 게이트웨이를 만들 수 있지만, 가장 널리 사용하는 HTTP 요청을 처리하도록 HTTP 게이트웨이를 만들겠습니다.

HTTP 게이트웨이는 기본적으로 HTTP 서버입니다. HTTP에 대한 요청을 받아 메모리에 저장한 후 해당 API에 대한 마이크로서비스를 호출합니다. 마이크로서비스에서 응답이 오면 조금 전 메모리에 저장한 HTTP 요청 객체를 찾아 응답하고는 메모리에서 지웁니다.

HTTP 게이트웨이 동작 순서

HTTP 게이트웨이 동작 순서

먼저 HTTP 서버 기능을 만듭니다.

'use strict'

const http = require('http');
const url = require('url');
const querystring = require('querystring');

// HTTP 서버를 만듦
var server = http.createServer((req, res) => { // ➊ HTTP 서버 생성
    var method = req.method;
    var uri = url.parse(req.url, true);
    var pathname = uri.pathname;

    if (method === "POST" || method === "PUT") { // ➋ POST, PUT 메서드 처리
        var body = "";

        req.on('data', function(data) {
            body += data;
        });
        req.on('end', function() {
            var params;
            if (req.headers['content-type'] == "application/json") { // ➌ 헤더가 application/json일 때는 JSON으로 파싱
                params = JSON.parse(body); 
            } else { // ➍ 헤더가 JSON이 아니면 querystring으로 파싱
                params = querystring.parse(body);
            }

            onRequest(res, method, pathname, params);
        });
    } else {
        onRequest(res, method, pathname, uri.query);
    }
}).listen(8000, () => {
    console.log('listen', server.address());
});

function onRequest(res, method, pathname, params) {  // ➎ 요청 정보 처리
}

http 모듈을 참조해 서버를 만듭니다(➊). HTTP는 메서드에 따라 파라미터를 읽어 들이는 방식이 다르기 때문에 각각 처리합니다.

POST와 PUT에서 data와 end 이벤트를 이용해 파라미터를 읽습니다(➋). 이때 콘텐츠 타입이 application/json이라면 파라미터가 JSON 형식의 스트링이므로 JSON.parse 함수를 이용해 파라미터를 처리합니다(➌). 콘텐츠 타입이 application/json이 아니라면 querystring 모듈의 parse 함수를 이용해 파라미터를 읽습니다(➍).