WeniVooks

검색

웹/네트워크/HTTP 베이스캠프

모놀리식과 마이크로서비스 아키텍처

모놀리식과 마이크로서비스 아키텍처

모놀리식(Monolithic) 아키텍처 또는 모노리스(Monolith) 아키텍처와 마이크로서비스(Microservice) 아키텍처에 대한 이해가 없는 경우 협업이나 의사소통에 차질이 생길 수 있습니다. 이번 챕터에서는 이 두개가 어떻게 다른지 가볍게 살펴보도록 하겠습니다.

12.1 모놀리식(Monolithic)

모놀리식 아키텍처는 전체 어플리케이션을 하나의 단일 시스템으로 구성합니다. 이 방식은 개발, 테스팅, 배포 등이 간단하고, 어플리케이션의 다양한 부분들 간의 상호작용도 직관적으로 이해하기 쉽습니다. 하지만, 모놀리식 아키텍처는 시간이 지남에 따라 어플리케이션의 복잡성이 증가하고, 유지보수와 업데이트가 어려워질 수 있습니다.

예를 들어 에어비엔비처럼 숙박 서비스를 제공하는 앱을 모놀리식으로 구현하게 되었을 때, 리뷰를 제공하는 DB서버가 다운되어버리거나 배포에 문제가 생겨 일부 기능에 문제가 발생했을 경우, 사용자 정보를 처리하거나 결제를 하는 다른 모든 시스템에 영향을 미치게되어 전체 서비스가 마비되는 상황에 이를 수 있습니다.

def main_page():
    return """
    <html>
    <head>
        <meta charset="utf-8">
        <title>메인 페이지</title>
    </head>
    <body>
        <h1>메인 페이지</h1>
        <ul>
            <li><a href="/login">로그인</a></li>
            <li><a href="/board">게시판</a></li>
        </ul>
    </body>
    </html>
    """
 
def login_page():
    return """
    <html>
    <head>
        <meta charset="utf-8">
        <title>로그인 페이지</title>
    </head>
    <body>
        <h1>로그인 페이지</h1>
        <form>
            <label for="username">사용자명:</label>
            <input type="text" id="username" name="username"><br><br>
            <label for="password">비밀번호:</label>
            <input type="password" id="password" name="password"><br><br>
            <input type="submit" value="로그인">
        </form>
        <br>
        <a href="/">메인 페이지로 돌아가기</a>
    </body>
    </html>
    """
 
def board_page():
    return """
    <html>
    <head>
        <meta charset="utf-8">
        <title>게시판 페이지</title>
    </head>
    <body>
        <h1>게시판 페이지</h1>
        <ul>
            <li>게시물 1</li>
            <li>게시물 2</li>
            <li>게시물 3</li>
        </ul>
        <br>
        <a href="/">메인 페이지로 돌아가기</a>
    </body>
    </html>
    """
 
def application(environ, start_response):
    path = environ["PATH_INFO"]
    if path == "/":
        response_body = main_page()
    elif path == "/login":
        response_body = login_page()
    elif path == "/board":
        response_body = board_page()
    else:
        response_body = "404 Not Found"
 
    status = "200 OK"
    headers = [("Content-Type", "text/html")]
    start_response(status, headers)
 
    return [response_body.encode("utf-8")]
 
if __name__ == "__main__":
    from wsgiref.simple_server import make_server
 
    server = make_server("localhost", 8000, application)
    server.serve_forever()
 
def main_page():
    return """
    <html>
    <head>
        <meta charset="utf-8">
        <title>메인 페이지</title>
    </head>
    <body>
        <h1>메인 페이지</h1>
        <ul>
            <li><a href="/login">로그인</a></li>
            <li><a href="/board">게시판</a></li>
        </ul>
    </body>
    </html>
    """
 
def login_page():
    return """
    <html>
    <head>
        <meta charset="utf-8">
        <title>로그인 페이지</title>
    </head>
    <body>
        <h1>로그인 페이지</h1>
        <form>
            <label for="username">사용자명:</label>
            <input type="text" id="username" name="username"><br><br>
            <label for="password">비밀번호:</label>
            <input type="password" id="password" name="password"><br><br>
            <input type="submit" value="로그인">
        </form>
        <br>
        <a href="/">메인 페이지로 돌아가기</a>
    </body>
    </html>
    """
 
def board_page():
    return """
    <html>
    <head>
        <meta charset="utf-8">
        <title>게시판 페이지</title>
    </head>
    <body>
        <h1>게시판 페이지</h1>
        <ul>
            <li>게시물 1</li>
            <li>게시물 2</li>
            <li>게시물 3</li>
        </ul>
        <br>
        <a href="/">메인 페이지로 돌아가기</a>
    </body>
    </html>
    """
 
def application(environ, start_response):
    path = environ["PATH_INFO"]
    if path == "/":
        response_body = main_page()
    elif path == "/login":
        response_body = login_page()
    elif path == "/board":
        response_body = board_page()
    else:
        response_body = "404 Not Found"
 
    status = "200 OK"
    headers = [("Content-Type", "text/html")]
    start_response(status, headers)
 
    return [response_body.encode("utf-8")]
 
if __name__ == "__main__":
    from wsgiref.simple_server import make_server
 
    server = make_server("localhost", 8000, application)
    server.serve_forever()
 
12.2 마이크로서비스(Microservice)

반면에, 마이크로서비스 아키텍처는 어플리케이션을 작고, 독립적인 서비스들로 분리합니다. 각 서비스는 자체적으로 개발되고, 배포되며, 다른 서비스와는 네트워크를 통해 통신합니다. 이 방식은 유지보수와 확장성이 용이하며, 개발 팀을 작게 분할하여 각각 독립적으로 작업할 수 있게 해줍니다.

덕분에 앞서 예를 든 숙박 서비스에서 어떤 한 기능에 문제가 생겨도 그것은 일부 서비스의 문제일 뿐 다른 중요한 기능들에 영향을 미치지는 않습니다. 이러한 이점을 통해 마이크로서비스 아키텍처의 신뢰성과 확장성은 모놀리식 아키텍처에 비해 상당히 높은 편 입니다.

하지만, 서비스 간의 통신과 데이터 일관성을 유지하는 것이 복잡할 수 있습니다.

# 서버1
def board_page():
    return """
    <html>
    <head>
        <meta charset="utf-8">
        <title>게시판 페이지</title>
        <script>
            fetch('http://localhost:8080/board_list')
                .then(response => response.json())
                .then(data => {
                    const boardList = document.getElementById('board-list');
                    data.forEach(board => {
                        const listItem = document.createElement('li');
                        listItem.textContent = board.title;
                        boardList.appendChild(listItem);
                    });
                });
        </script>
    </head>
    <body>
        <h1>게시판 페이지</h1>
        <ul id="board-list"></ul>
    </body>
    </html>
    """
 
def application(environ, start_response):
    path = environ['PATH_INFO']
    if path == '/board':
        response_body = board_page()
        status = '200 OK'
        headers = [('Content-Type', 'text/html')]
    else:
        response_body = '404 Not Found'
        status = '404 Not Found'
        headers = [('Content-Type', 'text/plain')]
 
    start_response(status, headers)
 
    return [response_body.encode('utf-8')]
 
if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    server = make_server('localhost', 8000, application)
    server.serve_forever()
# 서버1
def board_page():
    return """
    <html>
    <head>
        <meta charset="utf-8">
        <title>게시판 페이지</title>
        <script>
            fetch('http://localhost:8080/board_list')
                .then(response => response.json())
                .then(data => {
                    const boardList = document.getElementById('board-list');
                    data.forEach(board => {
                        const listItem = document.createElement('li');
                        listItem.textContent = board.title;
                        boardList.appendChild(listItem);
                    });
                });
        </script>
    </head>
    <body>
        <h1>게시판 페이지</h1>
        <ul id="board-list"></ul>
    </body>
    </html>
    """
 
def application(environ, start_response):
    path = environ['PATH_INFO']
    if path == '/board':
        response_body = board_page()
        status = '200 OK'
        headers = [('Content-Type', 'text/html')]
    else:
        response_body = '404 Not Found'
        status = '404 Not Found'
        headers = [('Content-Type', 'text/plain')]
 
    start_response(status, headers)
 
    return [response_body.encode('utf-8')]
 
if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    server = make_server('localhost', 8000, application)
    server.serve_forever()
# 서버2
import json
 
def board_list():
    # 게시물 목록을 반환하는 로직
    board_data = [
        {"id": 1, "title": "게시물 1", "content": "게시물 1의 내용"},
        {"id": 2, "title": "게시물 2", "content": "게시물 2의 내용"},
        {"id": 3, "title": "게시물 3", "content": "게시물 3의 내용"}
    ]
    return json.dumps(board_data)
 
def application(environ, start_response):
    path = environ['PATH_INFO']
    if path == '/board_list':
        response_body = board_list()
        status = '200 OK'
        headers = [('Content-Type', 'application/json'), ('Access-Control-Allow-Origin', '*')]
    else:
        response_body = '404 Not Found'
        status = '404 Not Found'
        headers = [('Content-Type', 'text/plain')]
 
    start_response(status, headers)
 
    return [response_body.encode('utf-8')]
 
if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    server = make_server('localhost', 8080, application)
    server.serve_forever()
# 서버2
import json
 
def board_list():
    # 게시물 목록을 반환하는 로직
    board_data = [
        {"id": 1, "title": "게시물 1", "content": "게시물 1의 내용"},
        {"id": 2, "title": "게시물 2", "content": "게시물 2의 내용"},
        {"id": 3, "title": "게시물 3", "content": "게시물 3의 내용"}
    ]
    return json.dumps(board_data)
 
def application(environ, start_response):
    path = environ['PATH_INFO']
    if path == '/board_list':
        response_body = board_list()
        status = '200 OK'
        headers = [('Content-Type', 'application/json'), ('Access-Control-Allow-Origin', '*')]
    else:
        response_body = '404 Not Found'
        status = '404 Not Found'
        headers = [('Content-Type', 'text/plain')]
 
    start_response(status, headers)
 
    return [response_body.encode('utf-8')]
 
if __name__ == '__main__':
    from wsgiref.simple_server import make_server
    server = make_server('localhost', 8080, application)
    server.serve_forever()
12.3 두 아키텍처 비교

이렇게 두 가지 방법은 각자 장단점을 가지고 있기 때문에 무엇이 더 우월하다고 말할 수는 없습니다. 현재 상황을 고려하여 적절한 방법을 선택하는것이 필요합니다.

Monolithic 아키텍처를 사용하면 좋은 경우

  • 제품과 비즈니스에 속도가 중요한 경우.
  • 팀의 규모가 5명 미만으로 작은 경우.
  • 새로운 제품의 MVP 버전을 구축하는 경우.

Microservice 아키텍처를 사용하면 좋은 경우

  • 긴급한 프로젝트가 아닌 경우.
  • 제품의 확장성과 신뢰성에 대해 많은 고민이 필요한 경우.
  • 대규모 인원의 팀이 있어 각자 세밀한 분업이 가능한 경우.
  • 기존의 모놀리식 앱에서 성능상의 문제가 발생해 애플리케이션의 일부를 마이크로서비스로 분할할 필요가 생긴 경우.
11.1 RESTful API11.3 웹의 역사