알아두면 좋은 웹어플리케이션 - Web Server, WAS, django, MTV

9 minute read

열심히 데이터 분석 모델을 만들었다고 생각해보자. 이 모델을 이런저런 방식으로 다른 사람들도 사용할 수 있도록 만들고 싶은데 그러려면 뭔가 모델과 함께 작동할 화면 프로그램이 필요해보인다. 웹 어플리케이션은 화면을 바탕으로 개발한 기능을 원하는 시나리오대로 사용할 수 있는 하나의 프로그램이다. 그럼 웹어플리케이션에 대해 알아보자.

웹어플리케이션 구조 예시

django를 활용한 웹어플리케이션 구조 예시

/assets/images//web-application-architecture.png

먼저 웹어플리케이션 구조를 살펴보자. 파이썬 django를 사용해 만드는 웹어플리케이션 구조는 위 그림과 같다. 이 구조의 웹어플리케이션이 돌아가는 방식은 다음과 같다.

  • 사용자가 PC 웹브라우저를 통해 웹어플리케이션에 특정 행위에 대한 요청을 보냄
  • 웹서버에서 사용자의 요청을 받음
  • 웹서버가 WAS에 사용자들의 요청을 전달함
  • WAS가 어플리케이션(별도 프로세스)에 들어온 요청들을 분산해 로직을 실행시킴
  • 어플리케이션에서 로직을 실행시키고 WAS에 응답을 보냄(필요시 DB 참조)
  • WAS가 어플리케이션으로부터 온 응답을 웹서버가 이해할 수 있는 형태로 변환해 전달
  • 웹서버가 정적데이터와 함께 사용자 웹브라우저 상에 최종 응답결과를 제공함

아무 배경지식이 없는 상태에서 구조나 돌아가는 방식을 이해해보려 하면 잘 이해가 되지 않는다. 구성 요소를 하나씩 살펴보자.

웹어플리케이션을 사용하지 않는다면?

잠시 웹어플리케이션을 사용하지 않고 모델을 사용할 방법을 만들어본다 생각해보자.

sys.argv를 활용한 모델 작동 프로그램

$ python model.py data.csv parameter.csv

sys 모듈의 sys.argv를 활용해 모델에 돌아가는데 필요한 데이터와 파라미터를 넘기는 방식으로 프로그램을 구성할 수 있다. 만약 아웃풋 파일도 csv 형식이라면 엑셀을 통해 모델의 실행 결과를 확인 가능한 프로그램이 된다.

커맨드를 사용하는 것이 익숙한 개발자라면 이런 방식으로도 계속 모델을 실행시키고 결과를 확인해볼 수 있을 것이다. 다만 이런 방식은 일반 사용자 입장에서는 쓰기 어렵다. 개발자라 할 지라도 매번 커맨드를 통해 실행하고 파일을 따로 관리하며 엑셀을 통해 확인하는 방식은 많이 번거로울 것이다.

또한 엑셀은 훌륭한 프로그램이지만 원하는 방식으로 모든 것을 표현하기에는 표 베이스 프로그램이라는 한계가 있다. 이런 경우도 파이썬에서 PyQt 같은 라이브러리를 통해 GUI를 만들 수 있지만 UI 디자인이 그렇게 깔끔하지 않다.

파일 관리나 UI 디자인이 상관 없고 결과만 단순히 확인할 목적의 프로그램이라면 위 방식처럼 개발해도 충분하다. 하지만 이렇게 만든 프로그램은 내 컴퓨터가 아닌 다른 컴퓨터에서 모델에 접근할 방법이 없다. 다른 컴퓨터에서도 모델에 접근하기 위해서는 서버와 클라이언트 구조가 필요하다.

서버와 클라이언트

서버는 요청을 받아 서비스나 데이터를 제공하는 컴퓨터, 클라이언트는 요청을 보내는 컴퓨터 또는 어플리케이션이다. 서버와 클라이언트 구조를 갖춘 프로그램을 만들면 개발 컴퓨터가 아닌 타 컴퓨터에서도 모델을 실행시키고 결과를 볼 수 있다.

여기서 클라이언트가 서버에게 무언가를 요청하는 것을 request라고 하고 서버가 클라이언트에게 결과를 제공하는 것을 response라고 한다. 그렇다고 아무렇게나 요청한다고 원하는 결과가 나오지는 않는다. 웹어플리케이션의 서버와 클라이언트 통신에는 정해진 프로토콜이 있다. 이에 대해 자세히 알아보자.

HTTP, REST API

웹어플리케이션에서 서버와 클라이언트 간 통신은 HTTP(HyperText Transfer Protocol)을 따른다. HTTP 프로토콜을 따라 데이터를 어떤 방식으로 요청할지 정할 수 있고 이에 따라 응답이 생성된다.

HTTP Request

HTTP 프로토콜에서는 URI(Uniform Resource Identifier)라는 주소에 메소드를 통해 데이터를 요청한다. 하나의 자원에 대해 단순 조회를 해본다거나 그 자원을 사용해 다른 결과를 만드는 등 다양한 행위를 하고 싶을 수 있기 때문에 HTTP에서는 여러 메소드를 두어 요청을 할 수 있게 만들어놓았다.

HTTP 메소드의 종류는 다음과 같다.

  • GET: URI에 데이터를 조회하는 메소드
  • POST: URI에 데이터를 생성하기 위한 메소드. 또는, URI에 데이터를 보내고 새로운 결과를 얻기 위한 메소드.
  • PUT: URI 데이터 수정을 위한 메소드
  • DELETE: URI 데이터 삭제를 위한 메소드

이 외에도 HEAD, CONNECT, OPTIONS, TRACE, PATCH와 같은 메소드가 존재한다. 가장 많이 사용하는 메소드는 GET과 POST로 두 가지 메소드만 잘 알아둬도 많은 문제 상황을 해결할 수 있다.

HTTP Response

서버에 request를 보내면 response로 결과가 나온다. 크게 응답코드로 요청에 대한 성공/실패 등 수행 결과를 제공한다. 가끔 우리가 보는 404 에러 같은 메세지는 응답코드로 실패를 나타낸다. 400번대는 실패 200번대는 성공이라고 일단 알아두면 이해에 도움이 될 것이다. 요청에 대한 결과는 body에 json이나 정해진 형식으로 응답코드와 함께 제공된다.

REST (Representational State Transfer) API

REST API를 간단히 말하면 URI 주소에 HTTP 메소드를 바탕으로 원하는 데이터를 얻을 수 있게 만든 인터페이스이다. 대부분 웹 API의 설계는 REST API 방식으로 이루어지므로 웹에서 데이터를 주고 받을 수 있는 수단 정도로 이해해주면 좋을 것 같다.

데이터 분석 모델의 경우 POST 메소드로 데이터를 넘겨주고 예측 결과값을 받아오는 방식으로 많이 활용된다. 웹 어플리케이션을 화면과 같이 통채로 만들지 않더라도 파이썬 REST API 설계만으로 모델을 다른 시스템에서 호출할 수 있게 만드는 형태로 개발을 많이 진행한다.

기타

웹어플리케이션에서 데이터의 교환은 주로 json을 이용해 이루어진다. csv 같은 파일 형식보다 키 값 형태로 이루어져 있어 사용이 편하고 javascript와 연동이 좋아 표준으로 사용된다. 요청을 보낼 때 json 형식으로 보내겠다는 식으로 데이터 형식 지정이 가능하다.

HTTP에 대해 더 알고 싶다면 인프런 김영한님의 모든 개발자를 위한 HTTP 웹 기본 지식을 들어보는 것을 추천한다. 블로그나 유튜브에서 조각조각 내용을 보다가 한 번 정리하고 싶어서 들어봤는데 정말 잘 설명해주셔서 도움이 많이 됐다.

어플리케이션

서버와 클라이언트, HTTP 통신에 대해 알아보았다. 그럼 실제 어플리케이션은 어떻게 만들어지는지 알아보자.

MTV (Model-Template-View) 패턴

django MTV 패턴

/assets/images//django-mtv-pattern.png

웹어플리케이션은 여러 화면과 이에 연관된 로직, 데이터를 바탕으로 구성된다. 현대 웹 어플리케이션에서는 이런 화면, 로직, 데이터를 효과적으로 관리하기 위해 소프트웨어 패턴을 적용해 개발한다. 파이썬 django는 MTV 패턴을 사용한다.

간단히 MTV 패턴에 대해 알아보자.

  • Model: 데이터를 다루는 부분. 데이터가 정의되고 DB와 연결됨
  • Template: 화면을 다루는 부분. 화면에 표시될 html을 다룸
  • View: 로직을 다루는 부분. HTTP request와 연관된 로직을 관리하고 처리

MTV 패턴에서 사용자의 요청은 View에서 로직으로 구현된다. View는 Model로부터 요청을 처리하기 위한 데이터를 받아오고 Template의 html에 로직의 결과를 포함해 응답한다.

머신러닝 모델을 구현했다면 구현한 함수나 클래스가 View에서 호출된다. Model로부터 필요한 설정값이나 데이터가 있다면 받아오고 정의된 Template을 바탕으로 화면에 표시해준다.

MTV 패턴을 이용하면 다수의 DB에 연결된 복잡한 로직들을 효과적으로 구현할 수 있다. 좀 더 널리 알려진 패턴은 MVC(Model-View-Controller) 패턴으로 자바 스프링 프레임워크에서 사용되며 Template 역할이 View, View 역할이 Controller로 바뀐 것을 제외하면 큰 틀에서는 유사한 개념이다.

django

그럼 MTV 패턴을 이용해 만든 django 프로젝트를 살펴보자.

django 프로젝트 구조

.
├── config                      # 설정
│   ├── __init__.py
│   ├── settings.py
│   ├── urls.py
│   └── wsgi.py         
├── core                        # 공통 모듈 (비슷한 기능을 다른 앱에서 참조하기 위해 사용)
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── reviews                     # 개별 앱 (리뷰)
│   ├── __init__.py
│   ├── admin.py
│   ├── apps.py
│   ├── migrations
│   ├── models.py
│   ├── tests.py
│   ├── urls.py
│   └── views.py
├── rooms 
│   ...
├── templates                   # html
│   ├── rooms
│   │   ├── room_detail.html
│   │   ├── ...
│   │   └── room_edit.html
│   │   ...
|   ├── 404.html
│   └── base.html
├── static                      # 정적데이터 (nginx 웹서버 이용 시 nginx가 참조하는 다른 위치에 존재)
│   ├── css       
│   └── image
├── db.sqlite3                  # 데이터베이스 (mysql, postgresql 등 사용하면 별도로 연결)
├── manage.py
└── requirements.txt

django 프로젝트는 설정 관련 디렉토리(settings.py, urls.py, wsgi.py 포함), 개별 앱 디렉토리(reviews, rooms 등), templates 디렉토리(화면에 표시하기 위한 html 파일 포함), static 디렉토리(정적데이터 포함), database 등으로 구성된다.

django에서는 reviews, rooms 등의 논리적인 단위인 앱(app)에 MTV 패턴을 적용해 어플리케이션을 개발한다. reviews 디렉토리를 보면 리뷰 관련 데이터를 다루기 위한 models.py 파일, 데이터와 요청에서 받은 정보를 이용해 로직을 만드는 views.py 파일을 볼 수 있다. Template 부분은 별도 디렉토리에 관리되고 로직의 결과를 담아 보내기 위한 html 파일들을 포함한다. 이외에도 여러 파일들이 존재하는데 중요 요소를 바탕으로 작동 방식을 도식화해보면 아래 그림과 같다.

django 작동방식

/assets/images//django-flow.png

  • HTTP Request을 보냄
  • HTTP Request의 URL을 보고 어떤 View로 찾아가야하는지 urls.py에서 찾아 넘겨줌
  • View에 작성된 로직에 따라 Model 데이터를 조작하고 필요한 값을 읽어옴
  • Model에서 읽어온 값과 Template의 html 형식을 조합해 완성된 HTML HTTP Response 생성

작동 방식에 나온 구성 요소들의 코드 예시를 살펴보자.

urls.py

from django.urls import path
from . import views

app_name = "reviews"

urlpatterns = [
    path("create/<int:room>", views.create_review, name="create")
]

어떤 URL이 어떤 View에 맵핑되어있는 지를 작성한다. path("create/<int:room>", views.create_review, name="create") 에서 "create/<int:room>" 부분이 URL, views.create_review 부분이 View를 의미한다.

models.py

from django.db import models
from django.core.validators import MinValueValidator, MaxValueValidator
from core import models as core_models

class Review(core_models.TimeStampedModel):

    """ Review Model Definition"""

    review = models.TextField()
    accuracy = models.IntegerField(
        validators=[MinValueValidator(1), MaxValueValidator(5)]
    )
    communication = models.IntegerField(
        validators=[MinValueValidator(1), MaxValueValidator(5)]
    )
    cleanliness = models.IntegerField(
        validators=[MinValueValidator(1), MaxValueValidator(5)]
    )
    location = models.IntegerField(
        validators=[MinValueValidator(1), MaxValueValidator(5)]
    )
    check_in = models.IntegerField(
        validators=[MinValueValidator(1), MaxValueValidator(5)]
    )
    value = models.IntegerField(
        validators=[MinValueValidator(1), MaxValueValidator(5)]
    )
    user = models.ForeignKey("users.User", related_name="reviews", on_delete=models.CASCADE)
    room = models.ForeignKey("rooms.Room", related_name="reviews", on_delete=models.CASCADE)

    def __str__(self):
        return f"{self.review} - {self.room}"

    def rating_average(self):
        avg = (
            self.accuracy
            + self.communication
            + self.cleanliness
            + self.location
            + self.check_in
            + self.value
        ) / 6

        return round(avg, 2)

    rating_average.short_description = "Avg."

    class Meta:
        ordering = ("-created",)

어떤 객체가 어떤 방식으로 만들어져야 하는지를 규정한다. 예시에서 Review라는 객체는 텍스트로 된 리뷰 필드, accuracy 등 항목에 대한 개별 점수 필드로 정의되고 foreign key를 통해 리뷰 작성자와 리뷰가 작성되는 방에 연결된다. 파이썬 코드로 위와 같이 작성하면 ORM(Object Relational Mapping)이라는 기술을 통해 DB와 연동되어 쿼리를 사용할 필요 없이 파이썬 코드로 데이터를 조작할 수 있게 된다. Model로 만들어놓은 객체들은 추후 View에서 import해 사용한다.

views.py

from django.contrib import messages
from django.shortcuts import redirect, reverse
from rooms import models as room_models
from . import forms

def create_review(request, room):
    if request.method == "POST":
        form = forms.CreateReviewForm(request.POST)
        room = room_models.Room.objects.get_or_none(pk=room)

        if not room:
            return redirect(reverse("core:home"))

        if form.is_valid():
            review = form.save()
            review.room = room
            review.user = request.user
            review.save()
            messages.success(request, "Room reviewed")
            return redirect(reverse("rooms:detail", kwargs={"pk": room.pk}))

View에서는 로직을 정의한다. def create_review(request, room) 으로 리뷰를 생성하기 위한 로직을 정의하고 return redirect(reverse("rooms:detail", kwargs={"pk": room.pk})) 으로 원하는 Template으로 이동시킨다.

room_detail.html


{% extends "base.html" %}
{% load is_booked on_favs %}
{% load i18n %}

{% block page_title %}
    {{ room.name }}
{% endblock page_title %}

{% block content %}
...

    <div class="-mt-5 container max-w-full h-75vh flex mb-20">
        <div class="h-full w-1/2 bg-center bg-cover" style="background-image:url({{room.first_photo}})"></div>
        <div class="h-full w-1/2 flex flex-wrap">
            # {% for photo in room.get_next_four_photos %}
                <div style="background-image:url({{photo.file.url}})" class="w-1/2 h-auto bg-cover bg-center border-gray-500 border"></div>
            # {% endfor %}
        </div>
    </div>

    <div class="container mx-auto flex justify-around pb-56">
        <div class="w-1/2">
            <div class="flex justify-between">
                <div class="mb-5">
                    <h4 class="text-3xl font-medium mb-px">{{ room.name }}</h4>
                    <span class="text-gray-700 font-light">{{ room.city }}</span>
                </div>
                <a href="{{ room.host.get_absolute_url }}" class="flex flex-col items-center">
                    {% include "mixins/user_avatar_info.html" with user=room.host %}
                    <span class="mt-3 text-gray-500">{{ room.host.first_name }}</span>
                </a>
            </div>
            ...

{% endblock content %}

Template은 django 객체를 사용해 만든 html이다. django template만의 특수문법과 View에서 나온 값들을 포함해 사용자가 볼 html 페이지를 만든다.

django도 하나의 거대한 프레임워크라 직접 사용하려면 배워야할 개념들이 많다. 처음 접한다면 Django Girls Tutorial을 따라해보자. 조금 더 실제 프로젝트에 가까운 케이스를 보고 싶다면 노마드코더 풀스택 에어비앤비 클론코딩를 보는 것을 추천한다.

데이터 분석에서 django를 주로 사용했을 때는 모델을 API 형식으로 만들 때였던 것 같다. 전체적인 어플리케이션 만드는 것보다 API 만드는 법만 알고 싶다면 Django REST Framework Full Course For Beginners을 참조해보자.

웹서버, 웹어플리케이션서버(WAS)

어플리케이션이 만들어졌으면 HTTP 요청을 주고 받는 것을 관리해줄 부분이 필요하다. 웹서버와 웹어플리케이션서버(WAS)가 이 역할을 수행하는데 각자의 역할을 통해 시스템으로 들어오는 사용자 요청을 분배하고 관리한다.

Web Server

이미지나 css, 단순 html 등의 정적 데이터를 처리하는 서버이다. Nginx와 Tomcat 등이 웹서버에 속한다. 클라이언트가 요청을 하면 웹서버에서 제일 먼저 HTTP 요청을 처리하며 정적 데이터는 웹서버에서 제공하고 동적으로 처리가 필요한 데이터는 WAS에 넘긴다.

WAS (Web Application Server)

웹서버로 온 HTTP 요청을 여러 어플리케이션에 분배해 부하를 관리하는 서버이다. WAS는 웹서버로부터 받은 요청을 중간에 해석해 django 어플리케이션으로 전달한다. 설정한 프로세스 갯수만큼 어플리케이션 객체를 띄우고 이들에게 요청을 분배해 많은 요청이 들어왔을 때 장애가 일어나지 않고 응답을 보낸다.

WAS도 정적 데이터 처리가 가능해서 python [manage.py](http://manage.py) runserver 로 실행하는 django 개발 서버만으로도 웹어플리케이션을 구동시킬 수 있다. 다만, 정적 데이터는 Nginx 같은 웹서버에서 동적으로 처리가 필요한 데이터를 WAS를 통해 분배하는 구조가 더 효율적이다.

django의 WAS에는 gunicorn과 uwsgi 등이 있다. django 개발 서버를 사용하면 개발 변경 사항을 반영하기 위한 부분을 제외하면 하나의 어플리케이션이 돌아가 많은 요청을 보내면 버벅거린다. gunicorn이나 uwsgi 설정에서 프로세스 갯수를 늘리면 대기하고 있는 어플리케이션들이 각각 요청을 수행해 효과적으로 요청을 처리할 수 있다.

django의 WAS 부분이 이해하기 어려웠었다. 여러 프로세스로 django 어플리케이션을 띄우고 Nginx로부터 받은 요청을 어플리케이션에 나눠 부하를 관리하는 서버라고 생각하면 좋을 것 같다.

마치며

파이썬 Django를 기준으로 웹어플리케이션의 구조에 대해 알아보았다. 프레임워크마다 조금씩 구조나 세부요소가 다르긴 하지만 큰 틀에서는 비슷한 구조를 취한다. 포스트 내용을 바탕으로 파이썬으로 만든 웹 어플리케이션 환경 이해에 도움이 되었길 바란다.

References

Leave a comment