ai에게 부탁해서 만든 프로젝트는 내가 프롬포트를 잘게 쪼개지 않는이상 다음과같이 한파일에 전부 구현한다.
stock_detail.vue
const loadStock = async (id) => {
if (!id) return
try {
const { data } = await axios.get(`/api/v1/stock/${id}`)
// 다양한 응답 구조 대응
let stock = null
if (Array.isArray(data?.items)) {
stock = data.items[0]
} else if (Array.isArray(data)) {
stock = data[0]
} else if (data?.item) {
stock = data.item
} else {
stock = data
}
stockInfo.value = stock
} catch (error) {
console.error('❌ Failed to load stock', error)
}
}
이러면 생기는 문제는 backend에서 응답에 결과를 추가하거나 바꾸면 어디서 단계적으로 접근할지 매번 찾는게 일이다.
stock_detail 말고 다른곳도 문제있다면 사방 팔방 뛰어댕겨야한다.
디버그로 오류라도 자세히 알려주면 좋지만 자바에비해 너그러운 언어다보니 찾기 힘들다. 그래서 아래와같이 한다고한다. 리팩토링 작업을 진행해야한다...
1. 문제 인지: 왜 JS 환경에서 데이터 관리가 힘든가?
자바스크립트(JS)는 타입에 관대합니다. 서버 API가 변경되어 데이터 필드명이 바뀌거나, 특정 값이 null로 내려와도 실행 전까지는 오류를 알 수 없습니다.
- Runtime Error: undefined 객체의 속성에 접근할 때 발생하는 TypeError는 서비스 중단의 주범입니다.
- 유지보수 지옥: API 응답 필드명이 바뀌면, 해당 데이터를 사용하는 모든 컴포넌트 파일을 찾아 일일이 수정해야 합니다.
- 의사소통 비용: 백엔드 DTO의 암호 같은 필드명(예: stck_oprc)이 프론트엔드 로직에 그대로 노출되면 코드 가독성이 떨어집니다.
2. 해결책: 인터페이스 정의와 데이터 매핑 (Layered Architecture)
이를 해결하기 위해 프론트엔드 내부에 데이터를 정제하는 전용 계층을 둡니다.
① Interface (규격화)
먼저 백엔드 DTO와 일치하는 타입을 정의합니다. TypeScript를 사용하면 가장 좋으며, JS 환경이라면 주석이나 명세서로 관리합니다.
TypeScript
// src/types/stock.ts
// 백엔드 StockPriceResponseDto와 1:1 매칭
export interface DailyPrice {
date: string; // 날짜 (YYYY-MM-DD)
open: number; // 시가
high: number; // 고가
low: number; // 저가
close: number; // 종가
volume: number; // 거래량
}
export interface StockPriceResponse {
stockCode: string;
stockName: string;
prices: DailyPrice[];
}
② API Service & Mapper (데이터 정제)
API 호출부에서 데이터를 받아올 때, 컴포넌트로 넘겨주기 전 "매핑(Mapping)" 과정을 거칩니다. 이 단계가 가장 중요합니다.
JavaScript
// src/api/stockService.js
import axios from 'axios';
/**
* 서버의 원본 데이터(Raw Data)를 프론트엔드 규격에 맞게 변환하는 매퍼
*/
const transformStockResponse = (data) => {
return {
symbol: data.stockCode,
name: data.stockName || 'Unknown',
// 데이터 안정성 확보: prices가 없을 경우 빈 배열로 방어
dailyList: (data.prices || []).map(p => ({
date: p.date,
open: p.open ?? 0,
high: p.high ?? 0,
low: p.low ?? 0,
close: p.close ?? 0,
volume: p.volume ?? 0
}))
};
};
export const getDailyPrices = async (stockCode, days) => {
const response = await axios.get(`/${stockCode}/prices`, { params: { days } });
// 호출하는 곳(Component)은 항상 정제된 데이터만 받게 됨
return transformStockResponse(response.data);
};
3. 실전 폴더 구조 (Best Practice)
효율적인 관리를 위해 프로젝트 구조를 다음과 같이 나눕니다.
| 폴더명 | 설명 | 비고 |
| src/types | 데이터 구조 정의 (Interface) | 백엔드 DTO와 통일 |
| src/api | 통신 로직 및 데이터 매퍼 (Mapper) | 에러 발생 시 이곳만 수정 |
| src/views | UI 컴포넌트 | 정제된 데이터 사용에만 집중 |
| src/utils | 데이터 포맷터 (금액 콤마, 날짜 변환) | 공통 유틸 함수 |
4. 이 구조가 주는 강력한 이점
- 에러 추적의 단일화: 서버 데이터 키가 바뀌면 api/ 폴더 내의 매핑 함수 한 곳만 고치면 됩니다. (수십 개의 화면 파일을 열어볼 필요가 없습니다.)
- 데이터 안전성 (Null Safety): ?? 0이나 || [] 같은 처리를 매퍼에서 미리 해두므로, 컴포넌트에서 undefined 에러로 앱이 뻗는 일을 원천 차단합니다.
- 코드 가독성: 컴포넌트 로직에 stckOprc 같은 백엔드 용어 대신 open 같이 직관적인 이름을 사용할 수 있어 비즈니스 로직에만 집중할 수 있습니다.
- 테스트 용이성: API 서버가 완성되지 않아도 매퍼가 반환할 가짜 데이터(Mock Data) 구조만 맞춰두면 프론트엔드 개발을 멈추지 않고 진행할 수 있습니다.
'Archive(완료된 내용) > 포트폴리오 강화' 카테고리의 다른 글
| [Stock101] @AuthenticationPrincipal을 활용한 유저 정보 관리 (0) | 2026.01.31 |
|---|---|
| [Stock101] 프론트를 재정비하자.- 8일차 (1) | 2026.01.29 |
| [Stock101] 반기 / 분기 공시 요약 및 전망 AI 기능 마무리- 8일차 (0) | 2026.01.28 |
| [Stock101]PDF 내용 요약 구현 - 7일차 (1) | 2026.01.27 |
| [Stock101] RAG PDF내용 요약과 전망을 도출하자 - 6일차 (0) | 2026.01.26 |