백엔드 프레임워크가 폴더 구조, 경계, 테스트, 팀 워크플로에 어떤 영향을 미치는지 알아보고—일관성 있고 유지보수 가능한 코드로 팀이 더 빠르게 배포할 수 있도록 합니다.

백엔드 프레임워크는 단순한 라이브러리 묶음 그 이상입니다. 라이브러리는 특정 작업(라우팅, 검증, ORM, 로깅)을 돕지만, 프레임워크는 의견이 담긴 “작업 방식”을 제공합니다: 기본 프로젝트 구조, 공통 패턴, 내장 도구, 그리고 조각들이 어떻게 연결되는지에 대한 규칙입니다.
프레임워크가 도입되면 수백 가지의 작은 선택을 안내합니다:
이 때문에 같은 언어와 DB를 사용해도 두 팀이 동일한 API를 만든다고 해도 코드베이스는 매우 달라질 수 있습니다. 프레임워크의 관례가 “여기서는 어떻게 하지?”에 대한 기본 답이 되기 때문입니다.
프레임워크는 종종 예측 가능한 구조를 위해 유연성을 일부 포기합니다. 장점은 온보딩이 빠르고 논쟁이 줄며, 재사용 가능한 패턴이 우발적 복잡성을 줄인다는 점입니다. 반면 단점은 제품이 특이한 워크플로, 성능 튜닝, 비표준 아키텍처를 필요로 할 때 프레임워크 관례가 제약으로 느껴질 수 있다는 점입니다.
현명한 결정은 “프레임워크냐 아니냐”가 아니라 얼마나 많은 관례를 채택할지와 팀이 시간이 지남에 따라 커스터마이징 비용을 계속 감내할지 여부입니다.
대부분의 팀은 빈 폴더에서 시작하지 않고 프레임워크의 “권장” 레이아웃으로 시작합니다. 그 기본값이 코드의 위치, 이름 규칙, 리뷰에서 무엇이 ‘정상’인지 결정합니다.
어떤 프레임워크는 고전적인 계층형 구조를 밀어줍니다: controllers / services / models. 배우기 쉽고 요청 처리 모델에 깔끔하게 맞습니다:
/src
/controllers
/services
/models
/repositories
다른 프레임워크는 기능 모듈 중심을 선호합니다: 하나의 기능에 필요한 모든 것을 함께 그룹화(HTTP 핸들러, 도메인 규칙, 영속성)합니다. 이렇게 하면 ‘로컬 추론’이 쉬워져 “Billing” 작업을 할 때 하나의 폴더만 열면 됩니다:
/src
/modules
/billing
/http
/domain
/data
어느 쪽이 자동으로 더 낫다는 것은 없지만, 각 방식은 습관을 형성합니다. 계층형 구조는 로깅, 검증, 오류 처리 같은 횡단 관심사를 중앙화하기 쉬운 반면, 모듈 우선 구조는 코드베이스가 커질 때 수평 스크롤을 줄일 수 있습니다.
CLI 생성기(스캐폴딩)는 점착성이 있습니다. 생성기가 각 엔드포인트마다 컨트롤러+서비스 쌍을 만든다면, 사람들은 간단한 함수로 충분할 때도 계속 그렇게 만들 것입니다. 반면 명확한 경계를 가진 모듈을 생성하면, 팀은 압박 속에서도 그 경계를 지킬 가능성이 커집니다.
이 동일한 역학은 “빠른 코드 작성(vibe-coding)” 워크플로에서도 나타납니다: 플랫폼의 기본값이 예측 가능한 레이아웃과 명확한 모듈 이음새를 만든다면, 팀은 코드베이스를 일관되게 유지하면서 성장시킬 가능성이 큽니다. 예를 들어 Koder.ai는 채팅 프롬프트로 풀스택 앱을 생성하고, 실무적 이점(속도 외에도)은 팀이 초기부터 일관된 구조와 패턴을 표준화할 수 있게 해 준다는 점입니다—원하면 소스 코드를 내보내 완전한 제어를 할 수도 있습니다.
컨트롤러를 중심에 놓는 프레임워크는 팀이 비즈니스 규칙을 요청 핸들러에 몰아넣도록 유혹할 수 있습니다. 유용한 경험 법칙: 컨트롤러는 HTTP → 애플리케이션 호출을 번역하는 역할만 하게 하세요. 비즈니스 로직은 서비스/유스케이스 레이어(또는 모듈 도메인 레이어)에 두어 HTTP 없이도 테스트하고 백그라운드 작업이나 CLI에서 재사용할 수 있게 하세요.
“가격 책정 로직은 어디에 있나요?”라는 질문에 한 문장으로 답할 수 없다면, 프레임워크 기본값이 도메인과 충돌하고 있을 수 있습니다. 초기에 조정하세요—폴더는 바꾸기 쉽지만 습관은 그렇지 않습니다.
백엔드 프레임워크는 단순한 라이브러리 모음이 아니라 요청이 코드로 어떻게 이동해야 하는지를 정의합니다. 모두가 동일한 요청 경로를 따르면 기능 배포가 빨라지고 리뷰도 스타일이 아니라 정확성에 집중됩니다.
라우트는 API의 목차처럼 읽혀야 합니다. 좋은 프레임워크는 라우트를 다음과 같이 장려합니다:
실용적인 규약은 라우트 파일을 매핑에 집중하게 하는 것입니다: GET /orders/:id -> OrdersController.getById, "사용자가 VIP면 X를 한다" 같은 로직은 넣지 마세요.
컨트롤러(또는 핸들러)는 HTTP와 핵심 로직 사이의 번역기로서 가장 잘 동작합니다:
프레임워크가 파싱, 검증, 응답 포맷팅 헬퍼를 제공하면 팀은 로직을 컨트롤러에 쌓기 쉬워집니다. 더 건강한 패턴은 “얇은 컨트롤러, 두꺼운 서비스”입니다: 요청/응답 관련 처리는 컨트롤러에 두고 비즈니스 결정은 HTTP를 모르는 별도의 레이어에 두세요.
미들웨어(또는 필터/인터셉터)는 인증, 로깅, 레이트 리미팅, 요청 ID 같은 반복 동작의 위치를 형성합니다. 주요 규약: 미들웨어는 요청을 보강하거나 보호해야 하며 제품 규칙을 구현하면 안 됩니다.
예를 들어, 인증 미들웨어는 req.user를 첨부할 수 있고, 컨트롤러는 그 정체성을 핵심 로직에 전달하면 됩니다. 로깅 미들웨어는 매번 컨트롤러가 새로 고안하지 않도록 표준화된 로그를 남길 수 있습니다.
예측 가능한 이름에 합의하세요:
OrdersController, OrdersService, CreateOrder(use-case)authMiddleware, requestIdMiddlewarevalidateCreateOrder(스키마/밸리데이터)이름이 의도를 담으면 코드 리뷰는 “여기에 무엇을 넣어야 했는가”가 아니라 동작에 집중합니다.
프레임워크는 단순히 엔드포인트를 제공하는 데 그치지 않고 팀을 특정한 코드 "형태"로 밀어넣습니다. 초기에 경계를 정의하지 않으면 기본 중력은 보통: 컨트롤러가 ORM을 호출하고 ORM이 DB를 호출하며 비즈니스 규칙이 여기저기 뿌려지는 방향입니다.
간단하고 견고한 분리는 다음과 같습니다:
CreateInvoice, CancelSubscription). 작업과 트랜잭션을 오케스트레이션하지만 프레임워크 의존을 최소화합니다."컨트롤러 + 서비스 + 리포지토리"를 생성하는 프레임워크는 유용할 수 있습니다—단, 모든 기능이 모든 레이어를 필요로 한다는 요구사항이 아니라 방향성 있는 흐름으로 받아들이는 것이 중요합니다.
ORM은 편리하고 어느 정도 검증되어 있기 때문에 DB 모델을 여기저기 전달하는 유혹을 만듭니다. 리포지토리는 "고객을 ID로 가져오기", "송장 저장" 같은 좁은 인터페이스를 제공해 애플리케이션과 도메인 코드가 ORM 세부사항에 의존하지 않게 도와줍니다.
"모든 것이 DB에 의존하는" 설계를 피하려면:
로직이 엔드포인트 간 재사용되거나 트랜잭션이 필요하거나 규칙을 일관되게 강제해야 한다면 서비스/애플리케이션 유스케이스 레이어를 추가하세요. 단순 CRUD로 비즈니스 동작이 전혀 없다면 레이어를 추가하는 것이 의례적이고 혼란을 초래할 수 있으므로 생략해도 됩니다.
의존성 주입(DI)은 프레임워크 기본값 중 하나로 팀 전체의 습관을 길들입니다. 프레임워크에 DI가 내장되면 임의의 위치에서 서비스 인스턴스를 생성하는 대신 의존성을 선언하고, 연결하고, 의도적으로 교체하는 습관이 생깁니다.
DI는 컨트롤러가 서비스에 의존하고 서비스는 리포지토리에 의존하는 식으로 작은 집중된 컴포넌트로 팀을 유도합니다. 이는 테스트 용이성을 높이고 구현 교체를 쉽게 합니다(예: 실제 결제 게이트웨이 vs. 목).
단점은 DI가 복잡성을 숨길 수 있다는 점입니다. 모든 클래스가 다섯 개의 다른 클래스에 의존하면 어떤 코드가 실제로 요청 시 실행되는지 이해하기 어려워집니다. 잘못 구성된 컨테이너는 당신이 수정하던 코드와 거리가 먼 곳에서 발생한 오류를 만들 수도 있습니다.
대부분의 프레임워크는 의존성을 명시적으로 만들고 "서비스 로케이터" 패턴을 방지하기 위해 생성자 주입을 권장합니다.
유용한 습관은 생성자 주입과 인터페이스 중심 설계를 짝지어 사용하는 것입니다: 코드가 특정 벤더 클라이언트가 아닌 안정적인 계약(예: EmailSender)에 의존하면 제공업체를 바꾸거나 리팩토링할 때 변경 범위를 국한시킬 수 있습니다.
DI는 모듈이 응집될 때 가장 잘 작동합니다: 하나의 모듈이 기능 한 조각(orders, billing, auth)을 소유하고 작은 공개 표면을 노출하는 식입니다.
순환 의존성은 흔한 실패 모드입니다. 이는 보통 경계가 불명확하다는 신호입니다—두 모듈이 공유하는 개념은 별도의 모듈로 분리되어야 하거나 한 모듈이 너무 많은 일을 하고 있다는 뜻입니다.
팀은 의존성이 등록되는 위치에 합의해야 합니다: 단일 구성 루트(startup/bootstrap)와 모듈 내부의 레벨별 배선.
배선을 중앙화하면 코드 리뷰가 쉬워집니다: 리뷰어는 새로운 의존성을 확인하고 정당성을 판단하며 DI가 도구에서 미스터리로 변하는 "컨테이너 확장(container sprawl)"을 방지할 수 있습니다.
백엔드 프레임워크는 팀에서 "좋은 API"가 무엇인지에 영향을 미칩니다. 검증이 일급 기능(데코레이터, 스키마, 파이프, 가드)이라면 사람들은 엔드포인트를 명확한 입력과 예측 가능한 출력 중심으로 설계하게 됩니다—왜냐하면 올바른 일을 하는 것이 무언가를 건너뛰는 것보다 더 쉬워지기 때문입니다.
검증이 경계에 있을 때(비즈니스 로직 이전), 팀은 요청 페이로드를 클라이언트가 보내는 임의의 데이터가 아니라 계약으로 다루기 시작합니다. 보통 다음을 가져옵니다:
프레임워크는 검증이 어디에 정의되는지, 오류가 어떻게 표면화되는지, 미지 필드를 허용할지 여부 같은 공유 규약을 장려합니다.
글로벌 예외 필터/핸들러를 지원하는 프레임워크는 일관성을 달성하기 쉽게 만듭니다. 각 컨트롤러가 임의의 응답을 만들지 않고 표준화할 수 있습니다:
code, message, details, traceId)일관된 오류 형태는 프론트엔드의 분기 로직을 줄이고 API 문서를 신뢰하기 쉽게 만듭니다.
많은 프레임워크가 DTO(입력)와 뷰 모델(출력)을 권장합니다. 이 분리는 건강합니다: 내부 필드를 우발적으로 노출하는 것을 방지하고, 클라이언트가 DB 스키마에 결속되는 것을 피하며 리팩토링을 안전하게 만듭니다. 실용적인 규칙: 컨트롤러는 DTO로 말하고 서비스는 도메인 모델로 말하세요.
작은 API도 진화합니다. 라우팅 규약은 버전이 URL 기반(/v1/...)인지 헤더 기반인지 결정하는 경향이 있습니다. 어떤 방식을 택하든 초기에 기본 규칙을 정하세요: 필드를 제거할 때는 폐기 창을 마련하고, 필드는 하위 호환 방식으로 추가하고, 변경사항은 한 곳(/docs나 /changelog)에 문서화하세요.
백엔드 프레임워크는 기능을 배포하는 데 도움을 줄 뿐 아니라 테스트 방식도 좌우합니다. 내장된 테스트 러너, 부트스트래핑 유틸리티, DI 컨테이너는 무엇이 쉬운지를 결정하고, 쉬운 것이 곧 팀이 실제로 하는 일이 됩니다.
많은 프레임워크는 컨테이너를 띄우고 라우트를 등록하며 메모리 내에서 요청을 실행할 수 있는 "테스트 앱" 부트스트래퍼를 제공합니다. 이로 인해 통합 테스트가 초기에 널리 쓰이는 경향이 있습니다—단위 테스트보다 몇 줄 더 쓰면 되기 때문입니다.
실용적 구분은 다음과 같습니다:
대부분 서비스에서는 속도가 완전한 피라미드 이상으로 중요합니다. 좋은 규칙은: 작은 단위 테스트는 많이 유지하고, 경계(데이터베이스, 큐)에 대한 통합 테스트는 집중적으로 유지하며, 계약을 증명하는 얇은 E2E 계층을 두는 것입니다.
프레임워크가 요청 시뮬레이션을 저비용으로 만든다면 통합 테스트에 약간 더 의존할 수 있지만, 도메인 로직을 분리해 단위 테스트가 안정적이도록 하세요.
목킹 전략은 프레임워크가 의존성을 해결하는 방식을 따라야 합니다:
프레임워크 부트 시간이 CI에서 지배적일 수 있습니다. 비용이 큰 설정을 캐시하고, DB 마이그레이션을 스위트당 한 번 실행하며, 병렬화는 격리가 보장되는 곳에서만 사용해 테스트를 빠르게 유지하세요. 실패 진단을 쉽게 하려면 일관된 시딩, 결정론적 시계, 엄격한 정리 훅을 사용하세요. "재시도"에 의존하는 방식은 피하세요.
프레임워크는 첫 API 출시뿐 아니라 "하나의 서비스"가 여러 기능, 팀, 통합으로 전환될 때 코드가 어떻게 성장하는지를 형성합니다. 프레임워크가 쉽게 제공하는 모듈/패키지 메커니즘이 장기 아키텍처가 되는 경우가 많습니다.
대부분의 백엔드 프레임워크는 설계상 모듈화를 유도합니다: 앱, 플러그인, 블루프린트, 모듈, 기능 폴더, 패키지 등이 그 예입니다. 이것이 기본값이면 팀은 새 기능을 전체 프로젝트에 파일을 흩뿌리기보다 "한 개의 모듈"로 추가하는 경향이 있습니다.
실용적인 규칙: 각 모듈을 자체 공개 표면(라우트/핸들러, 서비스 인터페이스), 비공개 내부, 테스트를 가진 미니 제품으로 취급하세요. 프레임워크가 자동 발견(모듈 스캐닝)을 지원하면 신중히 사용하세요—명시적 임포트가 종종 의존성을 이해하기 쉽게 만듭니다.
코드베이스가 커지면 비즈니스 규칙과 어댑터를 뒤섞는 것이 비용이 큽니다. 유용한 분리는 다음과 같습니다:
프레임워크 규약이 "서비스 클래스"를 권장하면 도메인 서비스는 코어 모듈에 두고 프레임워크 특정 배선(컨트롤러, 미들웨어, 프로바이더)은 경계에 두세요.
팀은 종종 너무 일찍 공유하려는 경향이 있습니다. 다음과 같은 경우에 추출을 고려하세요:
추출할 때는 내부 패키지(또는 워크스페이스 라이브러리)를 게시하고 엄격한 소유권과 변경 로그 규율을 유지하세요.
모듈형 모놀리쓰는 종종 최선의 중간 규모 전략입니다. 모듈에 명확한 경계와 최소한의 상호 임포트가 있으면 모듈을 서비스로 분리할 때 교체 비용이 적습니다. 모듈은 기술 레이어가 아니라 비즈니스 역량을 중심으로 설계하세요. 더 깊은 전략은 /blog/modular-monolith를 참조하세요.
프레임워크의 구성 모델은 배포가 일관되게(또는 혼란스럽게) 느껴지는 방식을 형성합니다. 구성이 임의의 파일, 환경 변수, "이 상수 하나만"으로 흩어지면 팀은 기능을 개발하기보다 차이를 디버깅하게 됩니다.
대부분의 프레임워크는 기본 출처 하나를 권장합니다: 구성 파일, 환경 변수, 또는 코드 기반 구성(모듈/플러그인). 어느 길을 택하든 초기에 표준화하세요:
config/default.yml).좋은 규약은: 기본값은 버전 관리되는 구성 파일에, 환경별 오버라이드는 환경 변수로, 코드에서는 하나의 타입화된 구성 객체만 읽는다입니다. 이렇게 하면 인시던트 시 값을 어디서 바꿀지 명확해집니다.
프레임워크는 env var 읽기, 시크릿 스토어 통합, 부팅 시 구성 검증을 돕는 헬퍼를 제공하는 경우가 많습니다. 이 도구를 사용해 시크릿을 잘못 다루기 어렵게 만드세요:
.env 난립보다 런타임 주입(CI/CD, 컨테이너 오케스트레이터, 시크릿 매니저)을 선호하세요목표로 하는 운영 습관은 간단합니다: 개발자는 안전한 플레이스홀더로 로컬 실행할 수 있고, 실제 자격 증명은 그것이 필요한 환경에서만 존재하도록 하세요.
프레임워크 기본값은 동등성을 장려하거나(모든 환경에서 동일한 부트 프로세스) 특수 사례를 만들 수 있습니다("프로덕션은 다른 엔트리포인트를 사용한다"). 시작 명령과 구성 스키마를 모든 환경에서 동일하게 유지하고 값만 바꾸는 것을 목표로 하세요.
스테이징은 리허설로 취급하세요: 동일한 기능 플래그, 동일한 마이그레이션 경로, 동일한 백그라운드 작업—단지 규모만 작게.
구성이 문서화되지 않으면 팀원들이 추측하게 되고 그 추측이 장애로 이어집니다. 리포에 간단한 참조(예: /docs/configuration)를 유지해 다음을 적으세요:
많은 프레임워크는 부팅 시 구성 검증을 제공합니다. 문서화와 함께 사용하면 “내 머신에서는 동작” 문제가 드물어집니다.
백엔드 프레임워크는 운영 환경에서 시스템을 이해하는 기준을 설정합니다. 관찰성이 내장되거나 강하게 권장되면 팀은 로그와 메트릭을 "나중에 할 일"이 아니라 API의 일부로 설계하기 시작합니다.
많은 프레임워크는 구조화 로그, 분산 트레이싱, 메트릭 수집과 직접 통합합니다. 이러한 통합은 코드 조직에 영향을 줍니다: 컨트롤러 전반에 print 문을 흩뿌리는 대신 횡단 관심사를 중앙화(로깅 미들웨어, 트레이싱 인터셉터, 메트릭 수집기)하게 됩니다.
좋은 표준은 모든 요청 관련 로그 라인이 포함해야 할 필드 소수를 정의하는 것입니다:
correlation_id(또는 request_id)로 서비스 간 로그 연결route와 method로 어떤 엔드포인트인지 파악user_id 또는 account_id로 지원 조사 용이성 제공duration_ms와 status_code로 성능 및 신뢰성 파악프레임워크 규약(요청 컨텍스트 객체나 미들웨어 파이프라인 등)은 상관 ID를 일관되게 생성하고 전달하기 쉽게 만들어 개발자가 기능마다 패턴을 재발명하지 않게 합니다.
프레임워크 기본값은 헬스 체크를 일급 시민으로 만들거나 사후 사고로 만들 수 있습니다. /health(liveness)와 /ready(readiness) 같은 표준 엔드포인트는 정의된 “완료(Definition of Done)”의 일부가 되고, 운영 요구사항이 무작위한 기능 코드로 새어나가는 것을 막습니다:
이 엔드포인트를 초기에 표준화하면 운영 요구사항이 기능 코드에 스며드는 일이 줄어듭니다.
관찰성 데이터는 리팩토링 의사결정 도구이기도 합니다. 트레이스가 특정 엔드포인트가 항상 동일한 의존성에서 시간을 소비한다는 것을 보여주면 모듈 추출, 캐싱 추가, 쿼리 재설계 등의 신호입니다. 로그가 오류 형태가 일관되지 않음을 드러내면 오류 처리를 중앙화해야 한다는 신호가 됩니다. 즉, 프레임워크의 관찰성 훅은 디버그뿐 아니라 코드베이스를 자신 있게 재구성하는 데도 도움을 줍니다.
백엔드 프레임워크는 단지 코드를 정리하는 것만이 아니라 팀이 일하는 “하우스 룰”을 설정합니다. 모두가 동일한 규약(파일 배치, 명명, 의존성 배선 방식)을 따르면 리뷰는 빨라지고 온보딩은 쉬워집니다.
스캐폴딩 툴은 새 엔드포인트, 모듈, 테스트를 몇 분 만에 표준화할 수 있습니다. 함정은 생성기가 도메인 모델을 지시하게 두는 것입니다.
스캐폴드를 일관된 셸을 만드는 데 사용하되(라우트/컨트롤러, DTO, 테스트 스텁), 생성된 코드를 즉시 편집해 팀의 아키텍처 규칙에 맞추세요. 좋은 정책은: 생성기는 허용되지만 최종 코드는 항상 신중하게 설계된 것처럼 읽혀야 한다는 것입니다.
AI 보조 워크플로를 사용하는 경우에도 동일한 규율을 적용하세요: 생성된 코드를 스캐폴딩으로 보고 팀 규약(모듈 경계, DI 패턴, 오류 형태)을 리뷰로 강제하세요—속도는 구조를 해치지 않을 때만 유용합니다.
프레임워크는 종종 관용적 구조를 암시합니다: 검증 위치, 오류 발생 방식, 서비스 명명 방식 등. 이러한 기대를 짧은 팀 스타일 가이드에 담으세요:
간단하고 실행 가능하게 유지하고 /contributing에서 링크하세요.
기준을 자동화하세요. 포매터와 린터를 프레임워크 관례(임포트, 데코레이터/어노테이션, async 패턴 등)에 맞게 구성하고 프리커밋 훅과 CI로 일관되게 강제하세요. 그래야 리뷰는 공백과 이름이 아니라 설계에 집중합니다.
프레임워크 기반 체크리스트는 일관성의 느린 침식을 방지합니다. PR 템플릿에 리뷰어가 확인해야 할 항목을 추가하세요:
작은 워크플로 가드레일이 팀이 커질수록 코드베이스를 유지 가능하게 만드는 요소입니다.
프레임워크 선택은 패턴을 고착시키기 쉽습니다—디렉터리 레이아웃, 컨트롤러 스타일, DI 방식, 심지어 테스트 방식까지. 목표는 완벽한 프레임워크를 고르는 것이 아니라 팀의 소프트웨어 전달 방식에 맞는 것을 골라 필요할 때 변경 가능성을 유지하는 것입니다.
기능 목록이 아니라 전달 제약에서 시작하세요. 작은 팀은 강한 관례, 배터리 포함 도구, 빠른 온보딩의 이점을 보통 얻습니다. 큰 팀은 명확한 모듈 경계, 안정적 확장 포인트, 숨은 결합을 만들기 어렵게 하는 패턴이 필요합니다.
실용적인 질문을 던지세요:
재작성은 작은 문제들이 장기간 무시되어 누적된 결과인 경우가 많습니다. 주의할 신호는:
중단 없이 진화할 수 있습니다. 시접을 도입하세요:
커밋하기(또는 다음 메이저 업그레이드 전에) 전에 짧은 실험을 하세요:
옵션을 평가하는 구조화된 방법이 필요하면 가벼운 RFC를 만들어 코드베이스에 저장하세요(예: /docs/decisions). 나중에 팀이 왜 그 선택을 했고 어떻게 안전하게 변경할 수 있는지 이해하는 데 도움이 됩니다.
추가 고려 사항: 팀이 더 빠른 빌드 루프(채팅 기반 개발 포함)를 실험하고 있다면 워크플로가 동일한 아키텍처 산출물(명확한 모듈, 강제 가능한 계약, 운영 가능한 기본값)을 만들어내는지 평가하세요. 가장 좋은 가속화 수단(프레임워크 CLI든 Koder.ai 같은 플랫폼이든)은 사이클 타임을 줄이면서 유지보수 가능한 백엔드를 지탱하는 관례를 침식하지 않는 것입니다.
백엔드 프레임워크는 애플리케이션을 구축하는 데 대한 의견이 강한 방식(기본 프로젝트 구조, 요청 라이프사이클 규약: 라우팅 → 미들웨어 → 컨트롤러/핸들러, 내장 도구, 그리고 권장 패턴)을 제공합니다. 반면 라이브러리는 보통 라우팅, 검증, ORM처럼 개별 문제를 해결하지만 팀 전체에서 이들 조각이 어떻게 맞물려야 하는지는 강제하지 않습니다.
프레임워크의 규약은 일상적인 결정에 대한 기본 답이 됩니다: 코드가 어디에 위치해야 하는지, 요청이 어떻게 흐르는지, 오류가 어떻게 형성되는지, 의존성이 어떻게 연결되는지 등입니다. 이런 일관성은 온보딩을 빠르게 하고 리뷰 논쟁을 줄여주지만, 동시에 특정 패턴에 ‘잠김(lock-in)’을 만들어 나중에 변경하기 어려운 비용을 초래할 수 있습니다.
기술적 관심사의 명확한 분리와 공통 횡단 관심사(인증, 검증, 로깅) 중앙화를 원하면 계층형 구조(controllers/services/models)를 선택하세요.
비즈니스 기능(예: Billing) 내에서 팀이 로컬로 작업하고 폴더를 옮기지 않도록 하려면 기능 모듈 방식(모듈별 폴더)을 선택하세요.
어떤 방식을 택하든 규칙을 문서화하고 리뷰에서 강제해 코드베이스가 성장해도 구조가 일관되도록 하세요.
생성기를 사용해 일관된 골격(라우트/컨트롤러, DTO, 테스트 스텁)을 빠르게 만들되, 생성 결과물을 출발점으로 보고 아키텍처 규칙에 맞게 즉시 수정하세요.
만약 스캐폴딩이 모든 엔드포인트에 controller+service+repo를 생성한다면 단순 엔드포인트에 불필요한 절차가 생길 수 있습니다. 주기적으로 생성 템플릿을 검토해 실제로 원하는 방식에 맞게 업데이트하세요.
컨트롤러는 HTTP 번역에 집중하게 하세요:
비즈니스 규칙은 애플리케이션/서비스 또는 도메인 레이어로 이동시켜 웹 스택을 부팅하지 않고도 재사용 및 테스트할 수 있게 하세요.
미들웨어는 요청을 풍부하게 하거나 보호하는 역할이어야 하며, 제품 규칙을 구현해서는 안 됩니다.
적합한 예:
가격 책정, 적격성 판단, 워크플로 분기 등 비즈니스 결정은 서비스/유스케이스에 두세요.
DI는 테스트 용이성을 높이고(예: 결제 제공자 교체, 테스트용 페이크 사용), 의존성을 명시적으로 선언・연결・교체하도록 장려합니다.
DI를 이해하기 쉽게 유지하려면:
순환 의존성이 보이면 보통 경계가 불명확하다는 신호입니다—DI 자체의 문제가 아닙니다.
요청/응답을 계약으로 다루세요:
code, message, details, traceId)를 사용하세요DTO/뷰 모델을 사용해 내부 필드 노출을 방지하고, 클라이언트가 DB 스키마에 결속되지 않도록 하세요. 컨트롤러는 DTO로 말하고 서비스는 도메인 모델로 말하게 하세요.
프레임워크 도구에 따라 편한 방식으로 테스트하게 되지만 의도적인 분리를 유지하세요:
의존성 재정의(바인딩 교체)나 인메모리 어댑터를 사용해 취약한 몽키패치를 피하세요. CI에서는 프레임워크 부트와 DB 설정 반복을 최소화해 빠르고 안정적으로 유지하세요.
리라이트로 이어질 가능성이 높은 징후를 주시하세요:
리라이트 위험을 줄이려면 다음과 같은 시접(seams)을 만드세요: