[BACK-004][기초] 라우터·컨트롤러·서비스 역할 나누기 > IT 기술 공유

본문 바로가기
사이트 내 전체검색

IT 기술 공유

[BACK-004][기초] 라우터·컨트롤러·서비스 역할 나누기

페이지 정보

profile_image
작성자 기술팀장
댓글 0건 조회 249회 작성일 26-08-31 06:34

본문

[이번 수업]
백엔드 요청 처리를 한 함수에 모두 넣으면 경로가 늘수록 수정과 테스트가 어려워집니다. 라우터는 요청을 연결하고, 컨트롤러는 HTTP 입력과 응답을 변환하며, 서비스는 업무 규칙을 처리하도록 나눠 봅니다.

[선수지식]
BACK-001의 요청·응답, BACK-002의 HTTP 메서드·상태코드, BACK-003의 REST API 설계를 알면 좋습니다.

[학습목표]
1. 라우터·컨트롤러·서비스의 책임을 구분한다.
2. 경로 매개변수를 검증하고 서비스 입력으로 바꾼다.
3. 업무 오류를 적절한 HTTP 상태로 변환한다.
4. 계층별 함수를 따로 테스트할 수 있게 설계한다.

[핵심개념]
라우팅(routing)은 HTTP 메서드와 경로를 보고 처리할 코드를 선택하는 일입니다.

컨트롤러(controller)는 HTTP 세계와 애플리케이션 세계의 경계입니다. 문자열로 들어온 id를 검증·변환하고, 서비스를 호출한 뒤 성공 결과나 오류를 상태코드와 응답 본문으로 바꿉니다.

서비스(service)는 사용자 조회, 주문 가능 여부 같은 업무 규칙을 담당합니다.

흐름은 `요청 → 라우터 → 컨트롤러 → 서비스 → 컨트롤러 → 응답`입니다. 계층을 무조건 많이 만드는 것이 목표가 아니라 변경 이유가 다른 코드를 분리하는 것이 목표입니다. 작은 기능은 파일을 나누지 않아도 함수 책임부터 구분할 수 있습니다.

[따라하기]
아래 내용을 layers.py로 저장합니다.

```python
USERS = {1: {'id': 1, 'name': '민수'}}


class UserNotFoundError(Exception):
    pass


def get_user_service(user_id: int) -> dict:
    if user_id not in USERS:
        raise UserNotFoundError
    return USERS[user_id].copy()


def get_user_controller(raw_id: str) -> tuple[int, dict]:
    if not raw_id.isdecimal():
        return 400, {'error': 'INVALID_USER_ID'}
    try:
        return 200, get_user_service(int(raw_id))
    except UserNotFoundError:
        return 404, {'error': 'USER_NOT_FOUND'}


def dispatch(method: str, path: str) -> tuple[int, dict]:
    parts = path.strip('/').split('/')
    if method != 'GET':
        return 405, {'error': 'METHOD_NOT_ALLOWED'}
    if len(parts) == 2 and parts[0] == 'users':
        return get_user_controller(parts[1])
    return 404, {'error': 'ROUTE_NOT_FOUND'}


for request in [('GET', '/users/1'), ('GET', '/users/9'), ('POST', '/users/1')]:
    print(request, '->', dispatch(*request))
```

Windows PowerShell은 `python layers.py`, macOS·Linux는 보통 `python3 layers.py`로 실행합니다.

```text
('GET', '/users/1') -> (200, {'id': 1, 'name': '민수'})
('GET', '/users/9') -> (404, {'error': 'USER_NOT_FOUND'})
('POST', '/users/1') -> (405, {'error': 'METHOD_NOT_ALLOWED'})
```

이 코드는 계층 흐름을 보여 주는 모형이며 실제 소켓 서버는 아닙니다.

[흔한 실수]
- 컨트롤러에 조회·가격 계산·메일 발송을 모두 넣어 테스트하기 어렵게 만듭니다.
- 서비스가 HTTP 응답 객체를 직접 만들어 다른 실행 환경에서 재사용하기 어려워집니다.
- 모든 예외를 500 또는 200으로 돌려 클라이언트가 실패 종류를 구분하지 못합니다.
- 경로 매개변수의 형식과 범위를 확인하지 않고 업무 로직으로 넘깁니다.

[보안 주의]
id가 숫자라고 확인하는 것만으로 접근 권한이 생기지 않습니다. 로그인 사용자가 해당 사용자를 조회할 권한이 있는지 컨트롤러의 인증 정보와 서비스의 업무 규칙에서 확인해 IDOR를 막으세요.

[직접 해볼 과제]
1. `GET /users/x`를 호출해 400 응답을 확인하세요.
2. `/health` 경로와 200 응답을 라우터에 추가하세요.
3. 서비스 함수를 직접 호출하는 성공·실패 assert를 작성하세요.

[확인문제]
1. 라우터와 컨트롤러는 각각 무엇을 결정하나요?
2. 서비스가 HTTP 상태코드를 몰라도 되게 만들면 어떤 장점이 있나요?
3. 숫자 id 검증 뒤에도 권한 검사가 필요한 이유는 무엇인가요?

[다음 학습]
번호 우선 순환에 따라 다음 글은 DB-004 데이터 모델링과 정규화입니다. BACK 다음 수업은 BACK-005 입력 검증과 오류 응답입니다.

[공식 참고 자료]
- RFC 9110 HTTP Semantics: https://www.rfc-editor.org/rfc/rfc9110
- FastAPI 경로 매개변수: https://fastapi.tiangolo.com/tutorial/path-params/
- FastAPI 여러 파일 구성: https://fastapi.tiangolo.com/tutorial/bigger-applications/
- NestJS Controllers: https://docs.nestjs.com/controllers
- NestJS Providers: https://docs.nestjs.com/providers

댓글목록

등록된 댓글이 없습니다.

회원로그인

회원가입

사이트 정보

회사명 : 회사명 / 대표 : 대표자명
주소 : OO도 OO시 OO구 OO동 123-45
사업자 등록번호 : 123-45-67890
전화 : 02-123-4567 팩스 : 02-123-4568
통신판매업신고번호 : 제 OO구 - 123호
개인정보관리책임자 : 정보책임자명

접속자집계

오늘
2,007
어제
5,103
최대
16,772
전체
773,061
Copyright © 소유하신 도메인. All rights reserved.