AI가 생성한 블록을 작업 시스템에 적용하기 3
요약
본 글은 'Functional Block Design (FBD)' 방법론의 세 번째 단계로, AI가 생성한 여러 독립적인 코드 블록들을 통합하여 완전하고 실행 가능한 URL 단축기 시스템을 구축하는 과정을 다룹니다. 이 프로젝트는 `url_validator`, `key_generator`, `storage_manager` 등 6개의 핵심 모듈과 이를 통합하는 `main.py`로 구성되어 있습니다. 각 블록은 설명(Description)과 AI 프롬프트(Prompt)를 거쳐 생성되었으며, 각 파일에는 공개 함수(public function) 형태로 완성된 코드가 포함되어 있어 독자들이 즉시 복사하여 실행할 수 있도록 설계되었습니다.
핵심 포인트
- FBD (Functional Block Design) 방법론을 통해 분리된 모듈들을 통합하는 실습 예시를 제공합니다.
- 완전한 URL 단축기 시스템이 6개의 독립적인 Python 파일(블록)로 구성되어 있습니다.
- 각 블록은 명확한 설명, AI 프롬프트, 그리고 완성된 코드로 제시되어 학습 자료로서의 가치가 높습니다.
- 시스템을 실행하기 위해서는 `main.py` 파일을 통해 모든 모듈들이 통합적으로 작동하는 것을 확인해야 합니다.
Part 3: 완전한 URL 단축기 구축
Part 1에서는 문제를 살펴보았습니다: vibe coding은 그 자체의 무게를 견디지 못하고 무너집니다. Part 2에서는 방법론을 소개했습니다: Functional Block Design (FBD) — 분해 (Decomposition), 블록 사양 (Block Specs), 생성 (Generation), 통합 (Integration). 이제 이 모든 것을 하나로 합칩니다. 이 파트에는 FBD로 구축된 완전하고 실행 가능한 URL 단축기가 포함되어 있습니다. 모든 블록이 여기에 있습니다. 모든 .py 파일이 완성되어 있습니다. 이 파일들을 복사하여 실행하고 작동하는 시스템을 확인할 수 있습니다. 이 포스트의 끝에 도달하면, 여러분의 프로젝트에 직접 FBD를 시도하는 데 필요한 모든 것을 갖게 될 것입니다.
3.1 완전한 파일 구조
프로젝트의 모든 파일은 다음과 같습니다:
text project/
├── url_validator.py # 완성됨
├── key_generator.py # 완성됨
├── storage_manager.py # 완성됨
├── redirect_handler.py # 완성됨
├── analytics_recorder.py # 완성됨
├── api_router.py # 완성됨
└── main.py # 통합(Integration) - 이것을 실행하세요
모든 블록은 동일한 패턴을 따릅니다:
- 설명(Description, 사람용)과 프롬프트(Prompt, AI용)가 포함된 헤더
- 헤더 아래에 생성된 코드
- 블록당 하나의 공개 함수 (public function)
3.2 6개의 모든 블록 (완성본)
프로젝트의 모든 .py 파일입니다. 보이는 그대로 복사하여 사용할 수 있습니다.
url_validator.py
DESCRIPTION
이 블록은 원시 URL 문자열을 검증하고 정규화된 버전을 반환합니다. 누락된 프로토콜, 잘못된 문자, 형식이 잘못된 도메인을 처리합니다.
PROMPT
URL Validator 블록을 위한 Python 함수를 생성하세요.
함수 시그니처 (Function signature):
def validate_url(raw_url: str) -> str:
입력값 (Inputs):
raw_url: 문자열, 프로토콜이 누락되었거나 형식이 잘못되었을 수 있음
출력값 (Outputs):
validated_url: 프로토콜이 누락된 경우 https:// 접두사가 붙은 정규화된 URL 문자열
에러 처리 (Error handling):
- raw_url이 비어 있으면 ValueError를 발생시킴
- URL 형식이 유효하지 않으면 ValueError를 발생시킴
요구사항 (Requirements):
- 프로토콜이 없는 경우 https:// 접두사를 추가할 것.
urllib.parse의 urlparse를 사용하여 검증할 것. 3. http 및 https 프로토콜만 허용할 것. 구현 참고 사항: - 입력값에서 공백을 제거할 것. - 정규화된 URL을 반환할 것. - docstring과 타입 힌트 (type hints)를 포함할 것. 예시: url = validate_url("example.com") # "https://example.com" 반환
from urllib.parse import urlparse
def validate_url(raw_url: str) -> str:
"""URL 문자열을 검증하고 정규화합니다.
Args:
raw_url: 프로토콜이 누락되었을 수 있는 URL 문자열.
Returns:
https:// 접두사가 붙은 정규화된 URL 문자열.
Raises:
ValueError: raw_url이 비어 있거나 유효하지 않은 경우.
"""
if not raw_url:
raise ValueError("URL cannot be empty")
raw_url = raw_url.strip() # 프로토콜이 없는 경우 https:// 추가
if not urlparse(raw_url).scheme:
raw_url = "https://" + raw_url
parsed = urlparse(raw_url)
if parsed.scheme not in ("http", "https"):
raise ValueError(f"Invalid protocol: {parsed.scheme}. Only http and https are allowed.")
if not parsed.netloc:
raise ValueError(f"Invalid URL: missing domain. {raw_url}")
return raw_url
key_generator.py
DESCRIPTION ============================================================
이 블록은 주어진 URL에 대해 고유한 6-8자리의 영숫자(alphanumeric) 키를 생성합니다. 최대 10번까지 재시도하여 키 충돌 (key collisions)을 처리합니다.
PROMPT ============================================================
Key Generator 블록을 위한 Python 함수를 생성하세요.
함수 시그니처 (Function signature):
def generate_key(original_url: str) -> str:
입력값 (Inputs):
original_url: 이미 검증된 문자열
출력값 (Outputs):
key: 6~8자리의 영숫자(digits 0-9, uppercase A-Z, lowercase a-z) 문자열
에러 처리 (Error handling):
- original_url이 비어 있는 경우, ValueError를 발생시킴
- 10번의 시도 후에도 고유한 키를 생성할 수 없는 경우, RuntimeError를 발생시킴
요구사항 (Requirements):
- 키는 반드시 고유해야 합니다. 두 개의 URL이 동일한 키를 가질 수 없습니다.
base62 인코딩(숫자 + 대문자 + 소문자)을 사용합니다. 3. 키를 반환하기 전에, storage_key_exists(key)를 호출하여 이미 존재하는지 확인합니다. 이 헬퍼(helper) 함수는 Storage Manager 블록에서 제공됩니다. 4. 충돌(collision)이 발생하면, 새로운 키를 생성하여 다시 시도합니다. 5. 고유한 키를 찾거나 10번의 시도가 완료될 때까지 계속 시도합니다. 구현 참고 사항: - random 모듈을 사용하여 후보 키를 생성합니다. - 독스트링(docstring)과 타입 힌트(type hints)를 포함합니다. - 함수를 순수 함수(pure function)로 유지합니다 (storage_key_exists 이외의 부작용(side effects)이 없어야 함). 예시: key = generate_key("https://example.com") # "aB3xY9" 반환
============================================================
import random
import string
이는 런타임 시 storage_manager에서 임포트됩니다.
단독 테스트를 위해 모킹(mock)할 수 있습니다.
def storage_key_exists(key: str) -> bool:
"""Mock 함수 - 실제 storage_manager로 교체될 예정입니다."""
# 이것은 플레이스홀더(placeholder)입니다. 실제 storage_manager가 이를 제공할 것입니다.
return False
def generate_key(original_url: str) -> str:
"""주어진 URL에 대해 고유한 6-8자리의 영문 숫자 키를 생성합니다.
Args:
original_url: 검증된 URL 문자열.
Returns:
고유한 6-8자리의 영문 숫자 키.
Raises:
ValueError: original_url이 비어 있는 경우.
RuntimeError: 10번의 시도 후에도 고유한 키를 생성할 수 없는 경우.
"""
if not original_url:
raise ValueError("URL은 비어 있을 수 없습니다")
chars = string.digits + string.ascii_letters
for attempt in range(10):
length = random.randint(6, 8)
key = ''.join(random.choices(chars, k=length))
if not storage_key_exists(key):
return key
raise RuntimeError("10번의 시도 후에도 고유한 키를 생성하는 데 실패했습니다")
storage_manager.py
DESCRIPTION
이 블록은 (key, URL) 쌍을 인메모리(in-memory) 딕셔너리에 저장하고 키를 통해 URL을 검색합니다. 프로덕션(production) 환경에서는 데이터베이스로 교체하십시오.
============================================================ PROMPT ============================================================
Storage Manager 블록을 위한 Python 모듈을 생성하세요.
함수:
def save_url(key: str, url: str) -> None:
def get_url(key: str) -> str | None:
def key_exists(key: str) -> bool:
입력값:
key: 문자열 키 (string key)
url: 문자열 URL (string URL)
출력값:
save_url: None
get_url: URL 문자열 또는 찾지 못한 경우 None
key_exists: 키가 존재하면 True, 그렇지 않으면 False
에러 처리:
- key 또는 url이 비어 있는 경우, ValueError를 발생시킵니다.
요구사항:
- 저장을 위해 인메모리 (in-memory) 딕셔너리를 사용하세요.
- 이 예제에서는 스레드 안전 (Thread-safe)이 요구되지 않습니다.
- key_exists는 충돌 (collision)을 확인하기 위해 key_generator에 의해 사용됩니다.
구현 참고 사항:
- 딕셔너리를 모듈 수준 변수 (module-level variable)로 유지하세요.
- 독스트링 (docstrings)과 타입 힌트 (type hints)를 포함하세요.
예시:
save_url("abc123", "https://example.com")
url = get_url("abc123") # "https://example.com" 반환
exists = key_exists("abc123") # True 반환
_store: dict[str, str] = {}
def save_url(key: str, url: str) -> None:
"""(key, URL) 쌍을 저장합니다.
Args:
key: 짧은 키 (short key).
url: 원본 URL (original URL).
Raises:
ValueError: key 또는 url이 비어 있는 경우.
"""
if not key:
raise ValueError("Key cannot be empty")
if not url:
raise ValueError("URL cannot be empty")
_store[key] = url
def get_url(key: str) -> str | None:
"""키를 통해 URL을 검색합니다.
Args:
key: 짧은 키 (short key).
Returns:
찾은 경우 원본 URL을, 그렇지 않으면 None을 반환합니다.
"""
return _store.get(key)
def key_exists(key: str) -> bool:
"""키가 이미 존재하는지 확인합니다.
Args:
key: 짧은 키 (short key).
Returns:
키가 존재하면 True, 그렇지 않으면 False를 반환합니다.
"""
return key in _store
redirect_handler.py
=========================================================== DESCRIPTION ============================================================
이 블록은 짧은 키를 받아 HTTP 리다이렉트 (redirect) 응답을 반환하거나, 키를 찾을 수 없는 경우 404 응답을 반환합니다.
============================================================ PROMPT ============================================================
Redirect Handler 블록을 위한 Python 함수를 생성하세요.
함수 시그니처 (Function signature): def create_redirect_response(original_url: str) -> tuple[str, int]:
입력값 (Inputs):
original_url: 문자열, 원래의 긴 URL
출력값 (Outputs): (message, status_code) 튜플
- 성공 시: (redirect_url, 302)
- 찾을 수 없는 경우: ("Not found", 404)
요구사항 (Requirements):
- 302 Found 리다이렉트 (redirect) 응답을 반환할 것.
- original_url이 None이거나 비어 있다면, 404를 반환할 것.
구현 참고 사항 (Implementation notes):
- 이것은 단순화된 응답입니다. 실제 웹 프레임워크 (web framework)에서는 적절한 Response 객체를 반환해야 합니다.
- 독스트링 (docstring)과 타입 힌트 (type hints)를 포함하세요.
예시 (Example):
response = create_redirect_response(" https://example.com" ) # (" https://example.com ", 302)를 반환함
============================================================
def create_redirect_response(original_url: str | None) -> tuple[str, int]:
"""HTTP 리다이렉트 (redirect) 또는 404 응답을 생성합니다.
Args:
original_url: 원래의 긴 URL, 또는 찾을 수 없는 경우 None.
Returns:
(message, status_code) 튜플.
- 리다이렉트의 경우 (redirect_url, 302)
- 키가 없는 경우 ("Not found", 404)
"""
if not original_url:
return "Not found", 404
return original_url, 302
analytics_recorder.py
DESCRIPTION ============================================================
이 블록은 분석 (analytics) 목적으로 클릭 이벤트 (timestamp, key, referrer, IP)를 기록합니다.
운영 환경 (production)에서는 데이터베이스 (database)에 기록하세요.
PROMPT ============================================================
Analytics Recorder 블록을 위한 Python 함수를 생성하세요.
함수 시그니처 (Function signature):
def record_click(key: str, request_context: dict) -> None:
입력값 (Inputs):
key: 문자열, 클릭된 짧은 키
request_context: "ip"와 "referrer"를 최소한 포함하는 딕셔너리 (dict)
출력값 (Outputs): None
요구사항 (Requirements):
1.
타임스탬프(timestamp), 키(key), IP, 그리고 리퍼러(referrer)와 함께 클릭을 기록합니다. 2. 이 예제에서는 콘솔에 출력합니다 (실제 운영 환경에서는 데이터베이스를 사용합니다). 3. 분석(analytics) 실패로 인해 예외(exception)를 발생시키지 마세요 (non-blocking). 구현 참고 사항: - 타임스탬프를 위해 datetime.now()를 사용하세요. - 독스트링(docstring)과 타입 힌트(type hints)를 포함하세요. - 분석 실패가 리다이렉트(redirect)를 중단시키지 않도록 try/except를 사용하세요. 예시: context = {"ip": "192.168.1.1", "referrer": " https://google.com" } record_click("abc123", context) ============================================================ from datetime import datetime from typing import Any
def record_click(key: str, request_context: dict[str, Any]) -> None:
"""분석을 위한 클릭 이벤트를 기록합니다.
Args:
key: 클릭된 짧은 키(short key).
request_context: 'ip'와 'referrer'를 포함하는 딕셔너리 (dict).
"""
try:
timestamp = datetime.now().isoformat()
ip = request_context.get("ip", "unknown")
referrer = request_context.get("referrer", "unknown")
# 실제 운영 환경에서는 데이터베이스나 메시지 큐(message queue)에 기록합니다.
print(f"[ANALYTICS] {timestamp} | key={key} | ip={ip} | referrer={referrer}")
except Exception as e:
# Non-blocking: 분석 실패가 리다이렉트를 중단시켜서는 안 됩니다.
print(f"[ANALYTICS ERROR] Failed to record click: {e}")
api_router.py ============================================================ DESCRIPTION ============================================================ 이 블록은 HTTP 요청을 적절한 핸들러(handler)로 라우팅합니다. POST /shorten을 생성(create) 핸들러로, GET /{key}를 리다이렉트(redirect) 핸들러로 매핑합니다. ============================================================ PROMPT ============================================================ API 라우터(API Router) 블록을 위한 Python 함수를 생성하세요. 함수 시그니처(Function signature): def route_request(routes: dict) -> object: 입력값 (Inputs): routes: 경로 패턴(path patterns)을 핸들러 함수로 매핑하는 딕셔너리 (dict) 출력값 (Outputs): app 객체 (이 예제를 위해 단순화됨) 요구사항 (Requirements): 1. 이것은 데모를 위한 단순화된 라우터입니다. 2. 실제 운영 환경에서는 FastAPI, Flask 또는 유사한 프레임워크를 사용하세요. 3.
반환된 app에는 run() 메서드가 있습니다. 구현 참고 사항: - 이것은 예제를 독립적으로 유지하기 위한 모의(mock) 라우터입니다. - Docstring(문서화 문자열)과 Type hints(타입 힌트)를 포함하세요. 예시: app = route_request({ "POST /shorten": handle_create, "GET /{key}": handle_redirect }) app.run() ============================================================ from typing import Callable
class SimpleApp:
"""데모를 위한 모의(mock) 웹 앱입니다."""
def init(self, routes: dict[str, Callable]):
self.routes = routes
def run(self):
"""모의(mock) 앱을 실행합니다."""
print("\n" + "=" * 50)
print("URL Shortener is ready!")
print("=" * 50)
print("\nThis is a mock router for demonstration.")
print("In production, replace with FastAPI, Flask, etc.")
print("\nAvailable routes:")
for path, handler in self.routes.items():
print(f" {path} -> {handler.__name__}")
AI 자동 생성 콘텐츠
본 콘텐츠는 Dev.to AI tag의 원문을 AI가 자동으로 요약·번역·분석한 것입니다. 원 저작권은 원저작자에게 있으며, 정확한 내용은 반드시 원문을 확인해 주세요.
원문 바로가기