진화하는 백엔드 패턴 - 모놀리식 MVC부터 현대적인 API 아키텍처까지
Wenhao Wang
Dev Intern · Leapcell

소개
지난 10년 동안 백엔드 개발 환경은 큰 변화를 겪었습니다. 한때 Model-View-Controller (MVC) 패턴을 따르는 모놀리식 웹 프레임워크에 의해 주로 제공되었던 것이 점차 API를 중심으로 하는 더 다양하고 종종 분산된 생태계로 발전했습니다. 이러한 변화는 단순한 기술적 유행이 아니라, 확장성, 유연성에 대한 증가하는 요구와 웹 브라우저부터 모바일 장치 및 IoT 센서에 이르기까지 다양한 클라이언트 애플리케이션의 확산에 대한 대응입니다. 견고하고 유지보수 가능하며 미래 지향적인 시스템을 구축하고자 하는 모든 백엔드 개발자에게 이러한 진화를 이해하는 것이 중요합니다. 이 글에서는 전통적인 MVC에서 현대적인 API 기반 아키텍처로의 여정을 살펴보고, 이 패러다임 전환의 핵심 개념과 실제적인 영향을 탐구합니다.
백엔드 개발에서의 아키텍처 진화
백엔드 개발의 현재 상태를 완전히 파악하려면 기초적인 개념과 시간이 지남에 따라 어떻게 적응되었는지 이해하는 것이 필수적입니다.
핵심 용어 설명
아키텍처 패턴에 대해 자세히 알아보기 전에, 논의에서 자주 등장할 몇 가지 주요 용어를 정의해 보겠습니다.
- MVC (Model-View-Controller): 애플리케이션을 세 가지 상호 연결된 구성 요소로 분리하는 아키텍처 패턴입니다. Model은 데이터와 비즈니스 로직을 관리합니다. View는 사용자에게 데이터를 표시합니다. Controller는 사용자 입력을 처리하고 Model과 View를 적절하게 업데이트합니다. Django 및 Ruby on Rails와 같은 프레임워크는 MVC 패턴의 고전적인 예입니다.
- 모놀리식 아키텍처: 애플리케이션의 모든 구성 요소가 긴밀하게 결합되어 단일 서비스로 실행되는 소프트웨어 아키텍처입니다. 처음에는 개발하기 쉽지만, 복잡성이 증가함에 따라 확장하고 유지 관리하기 어려워질 수 있습니다.
- API (Application Programming Interface): 서로 다른 소프트웨어 구성 요소가 서로 통신할 수 있도록 하는 정의된 규칙 집합입니다. 웹 개발 맥락에서 API는 일반적으로 데이터를 노출하는 HTTP 기반 인터페이스(REST, GraphQL)를 의미합니다.
- REST (Representational State Transfer): 네트워킹 애플리케이션을 설계하기 위한 아키텍처 스타일입니다. RESTful API는 무상태이며 클라이언트-서버 기반이며 리소스를 조작하기 위해 표준 HTTP 메서드(GET, POST, PUT, DELETE)를 사용합니다.
- GraphQL: API를 위한 쿼리 언어이며 기존 데이터로 해당 쿼리를 이행하기 위한 런타임입니다. 클라이언트가 필요한 데이터를 정확히 요청할 수 있어 REST에서 흔히 발생하는 과잉 제공 및 과소 제공 문제를 줄입니다.
- 마이크로서비스: 애플리케이션을 느슨하게 결합된 독립적으로 배포 가능한 서비스 컬렉션으로 구성하는 아키텍처 스타일입니다. 각 서비스는 일반적으로 단일 비즈니스 기능에 중점을 둡니다.
- Backend-for-Frontend (BFF): 각 특정 프론트엔드 애플리케이션(예: 웹용, 모바일용)에 대해 별도의 백엔드 서비스가 생성되는 패턴입니다. 이를 통해 프론트엔드 팀은 다른 클라이언트에 영향을 주지 않고 API 요구 사항을 맞춤 설정할 수 있습니다.
- 서버리스/FaaS (Function-as-a-Service): 클라우드 제공업체가 기본 인프라를 관리하고 개발자가 서버를 관리하지 않고도 이벤트에 응답하여 개별 함수 또는 코드 조각을 배포 및 실행하는 클라우드 실행 모델입니다.
MVC 프레임워크의 부상과 진화
웹 개발 초기에는 Django 및 Ruby on Rails와 같은 프레임워크가 웹 애플리케이션 구축 방식을 혁신했습니다. 이들은 MVC 패턴을 채택하여 라우팅, ORM(Object-Relational Mapping), 템플릿팅 및 인증을 위한 통합 도구 모음을 제공했습니다.
예시 (전통적인 Django MVC):
간단한 블로그 애플리케이션을 생각해 봅시다.
# blog/models.py from django.db import models class Post(models.Model): title = models.CharField(max_length=200) content = models.TextField() published_date = models.DateTimeField(auto_now_add=True) def __str__(self): return self.title # blog/views.py from django.shortcuts import render, get_object_or_404 from .models import Post def post_list(request): posts = Post.objects.all().order_by('-published_date') return render(request, 'blog/post_list.html', {'posts': posts}) def post_detail(request, pk): post = get_object_or_404(Post, pk=pk) return render(request, 'blog/post_detail.html', {'post': post}) # blog/urls.py from django.urls import path from . import views urlpatterns = [ path('', views.post_list, name='post_list'), path('post/<int:pk>/', views.post_detail, name='post_detail'), ] # blog/templates/blog/post_list.html (simplified) <!DOCTYPE html> <html> <head> <title>My Blog</title> </head> <body> <h1>Blog Posts</h1> {% for post in posts %} <h2><a href="{% url 'post_detail' pk=post.pk %}">{{ post.title }}</a></h2> <p>{{ post.content|truncatechars:100 }}</p> {% endfor %} </body> </html>
이 설정에서:
Post
모델은 데이터를 처리합니다.post_list
및post_detail
뷰는 애플리케이션 로직과 HTML 템플릿(post_list.html
,post_detail.html
) 렌더링을 처리합니다.urlpatterns
는 URL을 뷰에 매핑하는 컨트롤러 역할을 합니다.
이 모놀리식 접근 방식은 서버 렌더링 웹 애플리케이션에 매우 효과적이었습니다. 그러나 React, Angular 및 Vue.js와 같은 JavaScript 프레임워크가 인기를 얻으면서 "View" 컴포넌트는 점점 클라이언트 측으로 이동했으며, 이는 순수 데이터를 제공하는 종류의 백엔드를 요구했고 렌더링된 HTML을 제공하는 방식과는 달랐습니다.
API 중심 아키텍처의 등장
단일 페이지 애플리케이션(SPA)과 모바일 앱의 부상은 프론트엔드와 백엔드 간의 명확한 분리를 필요로 했습니다. 백엔드는 HTML 제공에서 API를 통한 순수 데이터 제공자로 진화했습니다. 이로 인해 백엔드 구성 요소의 구조와 상호 작용 방식에 상당한 변화가 생겼습니다.
RESTful API: 사실상의 표준
REST는 단순성, 무상태성 및 표준 HTTP 메소드 활용 덕분에 웹 API 구축을 위한 지배적인 아키텍처 스타일이 되었습니다. Django REST Framework (DRF
) 또는 Rails의 Active Model Serializers와 같은 프레임워크는 RESTful 엔드포인트 생성을 용이하게 하기 위해 등장했습니다.
예시 (Django REST Framework API):
이전 블로그 예제를 기반으로 DRF를 사용하여 게시물에 대한 RESTful API가 어떻게 보일지 보여주는 예입니다.
# blog/serializers.py from rest_framework import serializers from .models import Post class PostSerializer(serializers.ModelSerializer): class Meta: model = Post fields = ['id', 'title', 'content', 'published_date'] # blog/views.py (API View) from rest_framework import generics from .models import Post from .serializers import PostSerializer class PostListAPIView(generics.ListCreateAPIView): queryset = Post.objects.all().order_by('-published_date') serializer_class = PostSerializer class PostDetailAPIView(generics.RetrieveUpdateDestroyAPIView): queryset = Post.objects.all() serializer_class = PostSerializer # blog/urls.py (API URLs) from django.urls import path from .views import PostListAPIView, PostDetailAPIView urlpatterns = [ path('api/posts/', PostListAPIView.as_view(), name='api_post_list'), path('api/posts/<int:pk>/', PostDetailAPIView.as_view(), name='api_post_detail'), ]
여기서 백엔드의 책임은 이제 특정 URL을 통해 데이터를(이 경우 JSON) 노출하는 것입니다. 프론트엔드 애플리케이션(웹, 모바일)은 이러한 API를 사용하여 콘텐츠를 가져오고 표시합니다. MVC의 "View" 부분은 전체적으로 클라이언트에서 처리됩니다.
REST를 넘어서: GraphQL 및 Backend-for-Frontend (BFF) 패턴
REST는 강력하지만, 과잉 제공(필요한 것보다 더 많은 데이터를 가져오는 것) 또는 과소 제공(관련 데이터를 얻기 위해 여러 요청이 필요한 것)의 문제를 겪을 수 있습니다. GraphQL은 클라이언트가 정확히 필요한 데이터를 지정할 수 있도록 하여 이러한 문제를 해결합니다.
또한 다양한 클라이언트 유형이 존재함에 따라 단일 '범용' API가 모든 클라이언트에게 최적이 아닐 수 있습니다. 이것이 Backend-for-Frontend (BFF) 패턴이 유용한 곳입니다. 단일 모놀리식 API 대신, 각별한 클라이언트 유형(예: 웹 앱, iOS 앱, Android 앱)마다 별도의 백엔드 서비스가 있습니다. 이 서비스는 하위 마이크로서비스에서 데이터를 집계하고, 변환하고, 특정 클라이언트에게 최적화된 형식으로 서비스를 제공할 수 있습니다.
예시 (개념적 BFF 및 GraphQL):
쇼핑 애플리케이션을 상상해 봅시다.
Product Microservice
는 제품 데이터를 관리합니다.User Microservice
는 사용자 프로필을 관리합니다.Order Microservice
는 구매를 관리합니다.
GET /products
, GET /users
, GET /orders
를 노출할 수 있는 단일 REST API 대신, 웹 클라이언트를 위한 BFF는 GraphQL 엔드포인트를 가질 수 있습니다.
query ProductAndUserDetails($productId: ID!, $userId: ID!) { product(id: $productId) { name price description reviews { author rating } } user(id: $userId) { username email latestOrders(limit: 3) { id totalAmount } } }
BFF 서비스는 다음과 같은 작업을 수행합니다:
- 웹 클라이언트로부터 이 GraphQL 쿼리를 받습니다.
- 내부적으로
Product Microservice
및User Microservice
에 호출(REST 또는 gRPC 사용 가능)을 수행합니다. - GraphQL 스키마에 따라 데이터를 집계하고 변환합니다.
- 웹 클라이언트에게 단일의 맞춤형 JSON 응답을 반환합니다.
이 디자인은 다음과 같은 상당한 이점을 제공합니다:
- 클라이언트 특수성: 각 BFF는 클라이언트의 고유한 요구 사항에 맞게 최적화될 수 있습니다.
- 네트워크 호출 감소: 클라이언트는 종종 단일 요청으로 모든 필요한 데이터를 얻습니다.
- 분리: 프론트엔드 팀은 다른 클라이언트나 핵심 마이크로서비스에 영향을 주지 않고 BFF를 반복할 수 있습니다.
마이크로서비스 및 서버리스: 확장 및 모듈성
모놀리스에서 벗어난 궁극적인 결과는 종종 마이크로서비스 아키텍처의 채택이었습니다. 애플리케이션을 작고 독립적인 서비스로 분해함으로써 팀은 다음과 같은 작업을 수행할 수 있습니다:
- 독립적으로 확장: 과부하 상태의 서비스만 업스케일링하면 됩니다.
- 다양한 기술 선택: 다른 서비스에서 특정 작업에 가장 적합한 언어나 프레임워크를 사용할 수 있습니다.
- 결함 격리 개선: 한 서비스의 실패가 전체 애플리케이션을 다운시키는 경우는 드뭅니다.
서버리스 컴퓨팅(FaaS
)은 서버 관리를 완전히 추상화함으로써 이를 한 단계 더 나아갑니다. 개발자는 HTTP 요청, 데이터베이스 변경, 파일 업로드와 같은 이벤트에 의해 트리거되는 함수(예: AWS Lambda, Google Cloud Functions)를 배포합니다. 이는 이벤트 기반 아키텍처, 간헐적인 워크로드 및 개별적이고 무상태 함수로 분해될 수 있는 작업에 이상적입니다.
이러한 패턴은 엄청난 이점을 제공하지만, 서비스 검색, 서비스 간 통신, 모니터링 및 분산 추적을 위한 강력한 도구를 요구하며 운영상의 복잡성을 야기합니다.
애플리케이션 시나리오
- 전통적인 MVC (Django/Rails): 전체 스택, 서버 렌더링 웹 애플리케이션에 가장 적합하며, 특히 소규모에서 중규모 팀 또는 전체 스택의 빠른 개발이 중요한 프로젝트에서 프론트엔드와 백엔드 로직 간에 긴밀하게 결합되어 있습니다. 예: SEO가 중요하고 생성된 정적 HTML이 도움이 되는 내부 도구, 콘텐츠 관리 시스템.
- RESTful API (Django REST Framework): 데이터 구조가 비교적 안정적이고 클라이언트가 데이터 집계를 처리할 수 있는 여러 클라이언트 애플리케이션(웹 SPA, 모바일 앱, 기타 서비스)에 데이터를 제공하는 데 이상적입니다. 대부분의 현대 웹 서비스에 대한 일반적인 패턴입니다.
- GraphQL/BFF: 클라이언트가 데이터 가져오기를 세밀하게 제어해야 하는 다양한 클라이언트 유형이나 복잡한 데이터 요구 사항이 있는 애플리케이션에 탁월합니다. 특히 마이크로서비스 환경에서 집계된 보기를 제공하여 클라이언트 측 데이터 소비를 단순화하는 데 유용합니다. 예: 전자상거래 플랫폼, 소셜 미디어 앱.
- 마이크로서비스: 높은 확장성, 독립적인 팀 개발 및 기술적 다양성을 요구하는 대규모의 복잡한 애플리케이션에 필수적입니다. 기업 및 고도로 발전하는 플랫폼에서 선호됩니다.
- 서버리스/FaaS: 이벤트 기반 워크로드, 백그라운드 작업, 실시간 데이터 스트림 처리 및 특정, 더 작은 기능에 대한 고도로 확장 가능한 API 구축에 적합합니다. 예: 업로드 시 이미지 처리, 웹훅 핸들러, IoT 데이터 수집.
결론
모놀리식 프레임워크의 전통적인 MVC에서 현대적인 API 중심 개발의 다양한 패턴으로의 여정은 더 큰 확장성, 유연성 및 유지보수성에 대한 지속적인 추구를 반영합니다. MVC 프레임워크는 여전히 특정 사용 사례에 유용하지만, 연결된 다중 클라이언트 애플리케이션의 요구 사항으로 인해 백엔드를 독립적인 프론트엔드 및 백엔드 진화를 가능하게 하는 순수 데이터 및 로직 제공자의 역할로 밀어붙였습니다. 현대 백엔드 개발은 모듈성과 강력한 API 계약을 통해 발전하며, 점점 확장되는 장치 생태계 전반에 걸쳐 정교한 애플리케이션을 지원합니다. 미래는 수많은 클라이언트를 효율적이고 안정적으로 지원할 수 있는 적응 가능한 시스템을 설계하는 데 있습니다.