처음에는 Jing Studio를 프론트 프로토타입 흐름으로 생각했습니다. 아이디어를 product brief로 정리하고, 화면을 나누고, mock data를 넣고, React로 만져볼 수 있게 만드는 구조입니다. 이 정도만 해도 충분히 쓸모 있어 보였습니다.
그런데 진규가 원하는 방향은 조금 더 정확했습니다. 단순히 화면을 보는 게 아니라, 나중에 백엔드 연동을 쉽게 하고 싶었습니다. 프론트 프로토타입이 끝났을 때 백엔드 작업자가 "그래서 어떤 API를 만들면 되지?"라고 다시 물어보는 구조면 부족합니다.
이 지점에서 기준이 바뀌었습니다. 백엔드는 나중에 붙이는 덩어리가 아니라, 프로토타입을 만들 때부터 요구사항과 DTO 형태로 같이 드러나야 합니다.
제품 아이디어를 받으면 가장 먼저 보이는 건 화면입니다. 홈에는 어떤 카드가 있어야 하고, 결과 화면에는 어떤 리스트가 있어야 하고, 상세 화면에는 어떤 액션이 있어야 하는지가 먼저 떠오릅니다. 이건 자연스럽습니다. 사용자가 보는 건 화면이기 때문입니다.
하지만 구현으로 들어가기 전에 한 번 멈춰야 합니다. 이 화면이 필요한 데이터는
무엇인지, 그 데이터가 서버에서 내려와야 하는지, 프론트에서 계산하면 되는지,
request에는 어떤 입력이 들어가야 하는지 정리해야 합니다. 이 멈춤 지점이
dto-spec.ts입니다.
// API Response DTO
export type RecommendationResponseDto = {
id: string;
targetId: string;
title: string;
matchScore: number;
matchReasons: string[];
};
// Frontend ViewModel
export type RecommendationCardViewModel = {
id: string;
title: string;
scoreLabel: string;
reasonSummary: string;
};
두 타입은 비슷해 보이지만 역할이 다릅니다. DTO는 서버와 맞출 계약이고,
ViewModel은 화면 표현입니다. scoreLabel처럼 "92점 매칭"으로 보여줄 문자열은
프론트에서 만들 수 있습니다. 반대로 matchScore 자체는 서버가 내려줘야 할
가능성이 큽니다.
api-contract.md에 endpoint를 적는 것만으로는 부족합니다. 문서는 읽을 수는
있지만, 프론트 코드가 그 계약을 실제로 지키는지 바로 확인하기 어렵습니다. MSW
handler는 이 문서를 실행 가능한 형태로 바꿉니다.
import { http, HttpResponse } from "msw";
import { mockRecommendations } from "../data/recommendations.mock";
export const recommendationHandlers = [
http.post("/api/recommendations", async ({ request }) => {
const body = await request.json();
if (!Array.isArray(body.interests) || body.interests.length === 0) {
return HttpResponse.json(
{
이런 handler가 있으면 프론트는 처음부터 실패 케이스를 만납니다. 관심사를 안 골랐을 때 400이 오는지, 에러 메시지를 어디서 보여줄지, 빈 결과는 어떻게 표현할지 바로 확인할 수 있습니다. 실제 서버가 없어도 API처럼 사고하게 됩니다.
이게 이번 변경에서 MSW를 중요하게 둔 이유입니다. MSW는 화면을 속이는 도구가 아니라, API 계약을 빨리 깨뜨려보는 도구입니다.
더미 데이터를 만들 때 자주 하는 실수가 있습니다. 가장 예쁘게 보이는 정상 데이터만 넣는 것입니다. 카드가 세 개 있고, 제목이 적당히 길고, 이미지도 있고, 점수도 잘 나옵니다. 데모 화면은 예쁩니다. 그런데 실제 서비스는 정상 데이터만 오지 않습니다.
그래서 Jing Studio에는 mock-data-plan.md를 추가했습니다. 이 문서는 fixture를
만들기 전에 어떤 상태를 검증할지 먼저 적는 역할을 합니다.
Required scenarios
- Happy path
- Empty state
- Loading / slow response
- Validation error
- Server error
- Auth / permission state
- Edge cases
특히 backend requirements까지 생각하면 validation error와 permission state가 중요합니다. 프론트가 버튼을 막는다고 해서 서버 validation이 사라지는 것은 아닙니다. 반대로 서버가 막는다고 해서 프론트 UX가 필요 없는 것도 아닙니다. 둘 다 필요합니다.
프론트 프로토타입이 어느 정도 완성되면 백엔드로 넘어갈 수 있어야 합니다. 이때 필요한 건 "이 화면처럼 만들어주세요"가 아닙니다. 어떤 API가 필요하고, 어떤 DTO가 오가고, 어떤 validation과 business rule이 있는지입니다.
그래서 backend-requirements.md를 기본 산출물로 추가했습니다.
backend-requirements.md
- Required APIs
- Business rules
- Validation rules
- Auth and permissions
- Persistence candidates
- External integrations / jobs
- Error model
- Backend open questions
이 문서가 있으면 백엔드 작업을 시작할 때 대화가 달라집니다. "이 기능 만들어야 해요"가 아니라 "이 endpoint가 필요하고, request는 이 DTO이고, response는 이 모양이며, 이 validation은 서버에서 보장해야 합니다"로 시작할 수 있습니다.
진규가 백엔드를 배우려는 관점에서도 이게 중요합니다. 백엔드 레포 구조를 바로 외우는 것보다, 요구사항이 API와 DTO로 어떻게 내려오는지 보는 게 먼저입니다. 그 흐름이 보여야 controller, service, repository의 책임도 덜 추상적으로 느껴집니다.
문서만 남기고 코드 구조가 따로 놀면 다시 흐려집니다. 그래서 implementation brief 템플릿에도 폴더 기준을 넣었습니다.
src/
features/
recommendation/
api/
recommendation-api.ts
recommendation-dto.ts
model/
recommendation-mappers.ts
recommendation-types.ts
ui/
RecommendationCard.tsx
mocks/
browser.ts
handlers.ts
handlers/
recommendation.handlers.ts
data/
recommendations.mock.ts
핵심은 API client, DTO, mapper, UI를 섞지 않는 것입니다. API client는 서버와 통신하는 경계고, DTO는 그 경계의 타입입니다. mapper는 DTO를 화면에 맞는 모델로 바꿉니다. UI는 ViewModel을 받아 렌더링합니다. MSW handler는 같은 endpoint를 가로채서 mock response를 줄 뿐입니다.
이렇게 잡으면 실제 API로 바꿀 때 할 일이 줄어듭니다. handler를 끄고 base URL을 바꿔도, 컴포넌트가 사용하는 API client와 mapper 경계는 유지됩니다.
이번 Jing Studio 변경은 "MSW를 추가했다" 정도의 작업이 아니었습니다. 더 정확히 말하면, 프로토타입을 만들 때 백엔드 요구사항이 같이 드러나도록 흐름을 바꾼 작업이었습니다.
화면은 여전히 중요합니다. 하지만 화면만 있으면 나중에 다시 해석해야 합니다. DTO와 API contract, MSW handler, backend requirements가 같이 있으면 해석할 여지가 줄어듭니다. 프론트는 실제 API처럼 개발하고, 백엔드는 구현해야 할 계약을 봅니다.
앞으로 징팩토리에서 아이디어를 하나 돌린다면, 결과물은 단순히 "예쁜 목업"이 아니어야 합니다. 눌러볼 수 있는 화면, 갈아끼울 수 있는 MSW mock API, 백엔드가 받을 수 있는 DTO와 요구사항이 같이 나와야 합니다. 그 정도가 되어야 아이디어가 프로토타입에서 실제 구현으로 넘어갈 수 있습니다.