프론트엔드 페이로드 누락으로 인한 백엔드 독트린 미적용 문제 해결

3 min read · 668 words

#541

이 글은 프론트엔드 페이로드 누락으로 인해 백엔드 독트린이 제대로 적용되지 않아 어려움을 겪는 분들을 위한 기록입니다. 내가 겪었던 문제를 해결한 과정을 통해 유사한 상황에 처한 분들이 즉시 해결책을 찾을 수 있도록 돕고자 합니다.

마치며 — 핵심 정리와 생각할 거리

핵심 결론

이번 문제의 근본 원인은 프론트엔드에서 백엔드로 전송되는 페이로드에 핵심적인 'category' 필드가 누락되어 발생한 데이터 무결성(Data Integrity) 위반이었습니다. 백엔드는 이 정보의 부재로 인해 동적인 독트린 적용 로직을 실행할 수 없었고, 결과적으로 시스템의 결정론적 동작(Deterministic Behavior)이 훼손되었습니다. 해결책은 프론트엔드에서 누락된 필드를 정확히 포함시키고, 백엔드에서 이를 명시적으로 파싱하여 비즈니스 로직에 반영하는 것으로, 이는 시스템의 예측 가능성과 신뢰성을 회복하는 가장 기본적인 접근 방식입니다.

더 생각해볼 것들

이 문제를 해결하고 나면 자연스럽게 떠오르는 질문들이 있습니다.

  • 데이터 유효성 검증(Data Validation)의 중요성 — 현재는 프론트엔드에서 'category' 필드를 추가하고 백엔드에서 이를 활용하는 방식으로 문제를 해결했습니다. 하지만 만약 프론트엔드에서 여전히 잘못된 카테고리 값을 보내거나, 백엔드에서 예상치 못한 형식의 데이터를 받는다면 어떻게 될까요? 백엔드 단에서 들어오는 모든 페이로드에 대해 스키마 유효성 검증(Schema Validation)을 강화하고, 유효하지 않은 데이터에 대한 적절한 에러 처리 메커니즘을 구축하는 것이 시스템 안정성에 얼마나 기여할지 고민해 볼 필요가 있습니다.
  • 프론트엔드-백엔드 계약(Contract) 관리 — 이번 문제는 프론트엔드와 백엔드 간의 데이터 전송 계약이 명확하게 정의되지 않았거나, 정의되었더라도 제대로 준수되지 않아 발생했습니다. 이러한 문제를 사전에 방지하기 위해 OpenAPI(Swagger)와 같은 도구를 활용하여 API 명세를 자동화하고, 이를 기반으로 프론트엔드와 백엔드 개발자가 동일한 계약을 공유하도록 강제하는 방법은 없는지 탐색해 볼 수 있습니다. 이는 개발 초기 단계에서부터 잠재적인 통합 오류를 줄이는 데 큰 도움이 될 것입니다.
  • 기본값(Default Value)의 현명한 활용 — 현재 백엔드는 'category' 필드가 없을 때 '활용 팁'을 기본값으로 사용했습니다. 기본값은 시스템의 견고성을 높이는 중요한 요소이지만, 이번 사례처럼 핵심 정보가 누락되었을 때 잘못된 기본값이 적용되면 오히려 문제를 은폐하고 디버깅을 어렵게 만들 수 있습니다. 어떤 상황에서 기본값을 허용하고, 어떤 상황에서는 명시적인 에러를 발생시켜야 하는지에 대한 정책을 수립하는 것이 중요합니다. 예를 들어, 필수 필드 누락 시에는 400 Bad Request와 같은 명확한 에러를 반환하는 것이 장기적으로는 더 나은 설계일 수 있습니다.

응용 가능한 상황

이 해결책은 단순히 카테고리 정보 누락 문제에 국한되지 않고, 프론트엔드와 백엔드 간의 데이터 불일치로 인해 발생하는 다양한 문제에 응용될 수 있습니다. 다음은 몇 가지 시나리오입니다.


# 1. 사용자 설정(User Preferences) 미적용 문제
# 문제: 사용자가 테마를 '다크 모드'로 설정했음에도 불구하고, 페이지 새로고침 시 '라이트 모드'로 돌아옴.
# 원인: 프론트엔드에서 사용자 설정 저장 시 'theme' 필드를 백엔드 페이로드에 포함시키지 않음.
# 해결:
# 프론트엔드 (JavaScript)
const saveUserSettings = async (userId, theme) => {
    const response = await fetch(`/api/users/${userId}/settings`, {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ theme: theme }) // 'theme' 필드 추가
    });
    return response.json();
};

# 백엔드 (Python/FastAPI 예시)
from pydantic import BaseModel
from fastapi import FastAPI

app = FastAPI()

class UserSettings(BaseModel):
    theme: str # 'theme' 필드 정의

@app.post("/api/users/{user_id}/settings")
async def update_user_settings(user_id: int, settings: UserSettings):
    # 데이터베이스에 settings.theme 저장 로직
    print(f"User {user_id} updated theme to: {settings.theme}")
    return {"message": "Settings updated successfully"}

# 2. 다국어 지원(Internationalization) 언어 설정 오류
# 문제: 사용자가 웹사이트 언어를 '영어'로 변경했으나, 일부 동적 콘텐츠는 여전히 '한국어'로 표시됨.
# 원인: 콘텐츠 요청 시 'Accept-Language' 헤더 또는 'lang' 필드가 백엔드로 전달되지 않음.
# 해결:
# 프론트엔드 (JavaScript)
const fetchLocalizedContent = async (contentId, lang) => {
    const response = await fetch(`/api/content/${contentId}`, {
        method: 'GET',
        headers: { 
            'Content-Type': 'application/json',
            'Accept-Language': lang // 'Accept-Language' 헤더 추가
        }
    });
    return response.json();
};

# 백엔드 (Python/Flask 예시)
from flask import Flask, request, jsonify

app = Flask(__name__)

@app.route("/api/content/", methods=["GET"])
def get_localized_content(content_id):
    lang = request.headers.get('Accept-Language', 'ko') # 헤더에서 언어 정보 추출
    # content_id와 lang에 따라 적절한 콘텐츠 반환 로직
    if lang == 'en':
        return jsonify({"id": content_id, "text": "Hello World!", "lang": lang})
    else:
        return jsonify({"id": content_id, "text": "안녕하세요!", "lang": lang})

# 3. 검색 필터(Search Filter) 미적용 문제
# 문제: 사용자가 검색 결과에서 '가격 범위' 필터를 적용했으나, 백엔드에서 반환된 결과에 필터가 반영되지 않음.
# 원인: 검색 API 호출 시 'min_price' 또는 'max_price' 필드가 누락됨.
# 해결:
# 프론트엔드 (JavaScript)
const searchProducts = async (query, minPrice, maxPrice) => {
    const params = new URLSearchParams({ query });
    if (minPrice) params.append('min_price', minPrice);
    if (maxPrice) params.append('max_price', maxPrice);

    const response = await fetch(`/api/products?${params.toString()}`); // 쿼리 파라미터로 필터 전달
    return response.json();
};

# 백엔드 (Python/Django REST Framework 예시)
from rest_framework.views import APIView
from rest_framework.response import Response
from rest_framework import status

class ProductSearchView(APIView):
    def get(self, request):
        query = request.query_params.get('query', '')
        min_price = request.query_params.get('min_price')
        max_price = request.query_params.get('max_price')

        # 데이터베이스에서 필터링된 상품 검색 로직
        products = [] # 실제 검색 로직 대체
        if min_price:
            products.append(f"Filtered by min_price: {min_price}")
        if max_price:
            products.append(f"Filtered by max_price: {max_price}")
        
        return Response({"query": query, "results": products}, status=status.HTTP_200_OK)

경우의 수로 보는 이 버그

이 버그는 프론트엔드와 백엔드 간의 통신 과정에서 발생하는 전형적인 데이터 불일치 문제입니다. 이를 경우의 수로 정리하면 다음과 같습니다.

이 문제는 크게 프론트엔드(F), 백엔드(B), 그리고 데이터 계약(C)이라는 세 가지 축에서 발생할 수 있습니다. 각 축에서 '정상(O)' 또는 '오류(X)' 상태를 가정하면 총 23 = 8가지의 조합이 가능합니다.

  • F(O) × B(O) × C(O): 프론트엔드가 올바른 데이터를 보내고, 백엔드가 이를 올바르게 처리하며, 데이터 계약이 명확한 경우. 문제 없음.
  • F(X) × B(O) × C(O): 프론트엔드가 데이터를 누락하거나 잘못 보내지만, 백엔드와 계약은 올바른 경우. 이번 글의 문제 상황에 해당합니다. 백엔드는 계약에 따라 데이터를 기다리지만 받지 못해 기본값을 사용하거나 오류를 발생시킵니다.
  • F(O) × B(X) × C(O): 프론트엔드는 올바른 데이터를 보내지만, 백엔드가 이를 잘못 파싱하거나 처리하는 경우. (예: 백엔드 스키마에 필드 누락)
  • F(O) × B(O) × C(X): 프론트엔드와 백엔드 모두 각자의 로직은 올바르지만, 둘 사이의 데이터 계약 자체가 잘못 정의된 경우. (예: 프론트엔드는 'category'로 보내고 백엔드는 'type'으로 기대)
  • F(X) × B(X) × C(O): 프론트엔드도 잘못 보내고 백엔드도 잘못 처리하지만, 계약은 올바른 경우. 디버깅이 매우 복잡해집니다.
  • F(X) × B(O) × C(X): 프론트엔드가 잘못 보내고, 백엔드는 올바르게 처리하지만, 계약 자체가 잘못된 경우. (예: 프론트엔드가 'category'를 보내지 않는데, 백엔드는 'type'을 기대하고 계약서에는 'category'로 명시되어 있음)
  • F(O) × B(X) × C(X): 프론트엔드는 올바르게 보내지만, 백엔드가 잘못 처리하고, 계약도 잘못된 경우.
  • F(X) × B(X) × C(X): 모든 것이 잘못된 최악의 경우. 시스템 전체의 재설계가 필요할 수 있습니다.

이번 사례는 F(X) × B(O) × C(O) 조합에 해당하며, 프론트엔드에서 필수 필드 누락이라는 비교적 명확한 원인이었습니다. 이러한 문제는 시스템의 규모가 커지고 개발자가 많아질수록 발생할 확률이 기하급수적으로 증가합니다. 따라서 명확한 데이터 계약 정의, 철저한 유효성 검증, 그리고 통합 테스트를 통해 이러한 경우의 수를 최소화하는 것이 견고한 시스템을 구축하는 핵심입니다.

문제 상황

내가 개발 중이던 시스템에서는 사용자가 카테고리 선택기(selector)를 통해 글의 카테고리를 지정하면, 백엔드 프롬프트에 해당 카테고리가 반영되어 글의 구조나 내용에 대한 독트린(doctrine)이 동적으로 적용되도록 설계했습니다. 예를 들어, '뉴스' 카테고리를 선택하면 날짜 기반 요약과 출처 명시가 필수로 요구되는 독트린이 적용되고, '활용 팁' 카테고리에는 8개 섹션으로 구성된 특정 구조가 적용되는 식입니다. 하지만 실제 운영 환경에서 사용자가 '뉴스'를 선택해도 시스템은 여전히 '활용 팁'의 8-섹션 구조를 따르는 현상이 발생했습니다. 내가 확인해 보니, 프론트엔드에서 백엔드로 전송하는 페이로드에 'category' 필드가 누락되어 있었고, 백엔드는 기본값인 '활용 팁' 독트린만을 모든 글에 일괄 적용하고 있었습니다. 이는 정보 시스템의 결정론적 동작(deterministic behavior)을 저해하는 심각한 문제였습니다.

에러 증상

가장 명확한 증상은 'Writing' 탭에서 사용자가 '뉴스' 카테고리를 선택했음에도 불구하고, 생성된 글이 '활용 팁' 카테고리에 할당된 8-섹션 구조를 그대로 사용한다는 점이었습니다. '뉴스' 글에 필수적인 '날짜 기반 요약'이나 '외부 출처 명시'와 같은 독트린이 전혀 적용되지 않았습니다. 백엔드 로그를 확인했을 때, 카테고리 정보가 제대로 전달되지 않아 항상 기본 카테고리 독트린이 활성화되는 것을 확인할 수 있었습니다.

환경

내가 이 문제를 겪은 환경은 다음과 같습니다. 프론트엔드는 JavaScript 기반의 웹 애플리케이션(writer.js)으로 구성되어 있었고, 백엔드는 여러 엔드포인트(generate-title, outline, publish)를 통해 프론트엔드와 통신하는 구조였습니다. 특히, 글의 제목 생성, 개요 생성, 그리고 최종 발행을 담당하는 세 가지 주요 엔드포인트에서 이 문제가 발생했습니다. 이들 엔드포인트는 모두 프론트엔드에서 전송하는 JSON 페이로드를 기반으로 동작했습니다.

시도했지만 실패한 방법

처음에는 백엔드의 기본 카테고리 설정을 변경해 보았습니다. 기본값을 '뉴스'로 변경하자, '뉴스' 글에는 정상적으로 날짜 기반 요약과 출처 명시 독트린이 적용되었습니다. 그러나 이 경우 '활용 팁' 글이 '뉴스' 독트린을 따르는 불일치(mismatch) 문제가 발생했습니다. 이는 문제의 근본 원인이 백엔드의 기본값 설정이 아니라, 프론트엔드에서 카테고리 정보 자체가 백엔드로 전달되지 않는다는 것을 명확히 보여주었습니다. 결국, 백엔드의 기본값 변경은 임시방편일 뿐, 시스템 전체의 일관성을 해치는 해결책이었습니다.

최종 해결

문제의 근본 원인은 프론트엔드(writer.js)에서 백엔드로 전송하는 페이로드에 'category' 필드가 누락되어 있었다는 점이었습니다. 이를 해결하기 위해 나는 writer.js 파일의 generate-title, outline, publish 세 가지 엔드포인트로 데이터를 전송하는 부분에 category: window.TSP_WRITER_CATEGORY 필드를 추가했습니다. window.TSP_WRITER_CATEGORY는 프론트엔드에서 현재 선택된 카테고리 값을 저장하는 전역 변수였습니다. 또한, 백엔드에서는 OutlineBody와 GenerateTitleBody 스키마에 category 필드를 추가하고, 이 필드 값을 활용하여 _category_doctrine() 헬퍼 함수를 호출하도록 로직을 수정했습니다. 이로써 백엔드는 프론트엔드로부터 정확한 카테고리 정보를 받아 해당 카테고리에 맞는 독트린을 동적으로 적용할 수 있게 되었습니다. 이는 정보의 무결성(data integrity)을 확보하고 시스템의 예측 가능성(predictability)을 높이는 중요한 조치였습니다.

사용한 코드 또는 프롬프트

다음은 writer.js에서 백엔드 페이로드에 카테고리 필드를 추가하기 위해 내가 수정한 코드입니다. 이 변경을 통해 프론트엔드에서 선택된 카테고리 정보가 백엔드로 정확히 전달될 수 있었습니다.

# writer.js
# 이전 (말썽난 코드)
const body = JSON.stringify({ topic, keywords, ... });

# 수정 (바로잡은 후)
const category = window.TSP_WRITER_CATEGORY || "활용 팁";
const body = JSON.stringify({ topic, keywords, ..., category });

검증 결과 및 현재 상태

수정 후, 나는 /api/writer/outline 엔드포인트를 호출하여 백엔드가 카테고리별 프롬프트 접두사를 정확하게 주입하는지 확인했습니다. '뉴스' 카테고리를 선택했을 때, 백엔드에서 외부 출처 필수 독트린이 올바르게 적용되는 것을 확인했습니다. 현재 이 문제는 완전히 해결(fixed)되었으며, 시스템은 사용자가 선택한 카테고리에 따라 정확한 독트린을 적용하고 있습니다.

같은 문제 겪는 분들에게

만약 여러분의 시스템에서도 프론트엔드에서 전달하는 데이터가 백엔드 로직에 제대로 반영되지 않는다면, 가장 먼저 프론트엔드 페이로드에 필요한 필드가 모두 포함되어 있는지, 그리고 백엔드에서 해당 필드를 올바르게 파싱하고 활용하는지를 확인해 보십시오. 특히, 동적으로 변경되어야 할 중요한 정보(예: 카테고리, 사용자 설정 등)가 기본값으로만 처리되고 있다면, 데이터 흐름의 시작점인 프론트엔드 페이로드 구성을 면밀히 검토하는 것이 중요합니다. 나의 경험상, 이러한 문제는 종종 작은 필드 누락에서 시작되는 경우가 많습니다. 데이터 무결성(Data Integrity)은 시스템의 신뢰성을 결정하는 핵심 요소임을 잊지 마십시오.

ToolSignal Pro Editorial

Claude · GPT · Antigravity · Cursor 실전 오류와 해결을 5개 언어로 정리한 AI debugging archive.

이전 글 다음 글