-
Notifications
You must be signed in to change notification settings - Fork 0
Frontend Wiki
์ด ๋ฌธ์๋ ํ๋ก ํธ์๋ ํ์ ๊ธฐ์ , ๊ท์น, ๊ฒฐ์ ์ฌํญ, ์ด์ ๋ฐฉ์์ ๊ธฐ๋กํ๊ณ ๊ณต์ ํ๊ธฐ ์ํ ์ํค์ ๋๋ค. ๋ชจ๋ ๋ณ๊ฒฝ ์ฌํญ์ ๋ฌธ์ํ ๋ฐ ๋ ์ง ๊ธฐ๋ก์ ์์น์ผ๋ก ํฉ๋๋ค.
| ๊ธฐ์ | ๋ฌธ์ |
|---|---|
| Stackflow | https://stackflow.so |
| TanStack Query | https://tanstack.com/query/latest |
| Zustand | https://zustand.docs.pmnd.rs |
| Ky | https://github.com/sindresorhus/ky |
| TailwindCSS 4 | https://tailwindcss.com/docs |
| CVA | https://cva.style |
| @webview-bridge | https://github.com/gronxb/webview-bridge |
| openapi-typescript | https://openapi-ts.dev |
| Expo | https://docs.expo.dev |
| ํ๊ฒฝ | URL |
|---|---|
| ํ๋ก๋์ ์น | https://hellocs.site |
| API | https://api.hellocs.site |
| API Docs (Swagger) | https://api.hellocs.site/v3/api-docs |
| Grafana | https://hellocs.site/grafana/ |
# Web
cd web && pnpm install && pnpm dev # http://localhost:5173
# Mobile
cd mobile && npm install && npm start # Expo dev server๋ฐฑ์๋ API๊ฐ ๋ณ๊ฒฝ๋์ ๋ ์คํ:
cd web && pnpm generate:api-models.env์ API_SWAGGER_URL ์ค์ ํ์.
-
src/pages/<feature>/<PageName>.tsx์์ฑ -
src/app/stackflow-route.tsx์{ name, component, path }๋ฑ๋ก - ํ์ ์
src/api/<feature>/,src/model/<feature>/์ถ๊ฐ
-
mobile/bridge/index.ts์AppBridgeType์ ๋ฉ์๋ ์๊ทธ๋์ฒ ์ถ๊ฐ -
bridge()๊ตฌํ์ฒด์ ์ค์ ๋ก์ง ์์ฑ - ์น์์
window.bridge.xxx()ํธ์ถ๋ก ๊ฒ์ฆ
-
api-models.ts๋ ์๋์์ฑ ํ์ผ โ ์ง์ ์์ ๊ธ์ง,pnpm generate:api-models๋ก๋ง ๊ฐฑ์ - ์ปค๋ฐ ๋ฉ์์ง ํ์ ๋ฏธ์ค์ ์
commitlintํ ์ด ๊ฑฐ๋ถํจ -
main๋ธ๋์น push ์ GitHub Actions๊ฐ ์๋ ๋น๋ + ๋ฐฐํฌ ํธ๋ฆฌ๊ฑฐ๋จ (web ๊ฒฝ๋ก ๋ณ๊ฒฝ ์) - ESLint import ์์ ์๋ฐ ์ CI์์ ์คํจ โ ๋ก์ปฌ์์
pnpm lint๋จผ์ ํ์ธ
๋ก์ปฌ ๊ฐ๋ฐ ์ ์๋ ๊ฒฝ๋ก๋ https://hellocs.site๋ก ํ๋ก์๋จ:
/api//v3//swagger-ui/
์ด ์น์ ์ ํ๋ก์ ํธ์์ ์ ์ฉํ ๊ธฐ์ ๊ฐ๋ ์ ํ์ ์ ๊ด์ ์์ ๊ธฐ์ ํ๋ค. ์ค์ ๊ตฌํ ํ์ผ๊ณผ ์ฐ๊ฒฐํด ์ด๋ก ๊ณผ ์ค์ฒ์ ์ฐ๊ฒฐ๊ณ ๋ฆฌ๋ฅผ ํ์ธํ ์ ์๋ค.
๋ค์ดํฐ๋ธ ์ฑ๊ณผ ์น ์ฑ์ ์ด๋ถ๋ฒ ๋์ , WebView๋ฅผ ์(Shell)๋ก ์ฌ์ฉํ๊ณ ์น ์ฑ์ด UI ์ ์ฒด๋ฅผ ๋ด๋นํ๋ ํ์ด๋ธ๋ฆฌ๋ ์ํคํ ์ฒ. ๋จ์ผ ์ฝ๋๋ฒ ์ด์ค๋ก iOS/Android๋ฅผ ๋์ ์ง์ํ๋ฉด์, ์น์ ๋น ๋ฅธ ๋ฐฐํฌ ์ฃผ๊ธฐ๋ฅผ ์ ์งํ ์ ์๋ค.
[React Native Shell]
โโ LinearGradient ๋ฐฐ๊ฒฝ
โโ SafeAreaView
โโ <WebView source={{ uri: WEBVIEW_URL }} /> โ ์น ์ฑ ์ ์ฒด ๋ ๋๋ง
โโ @webview-bridge๋ก Native API ๋
ธ์ถ
- ๋ค์ดํฐ๋ธ ์ญํ : ๋ง์ดํฌ, ๋ก๊ทธ์ธ ์ํ, ๋ฅ๋งํฌ, ์คํ๋์ ํ๋ฉด
- ์น ์ญํ : ์ ์ฒด UI/UX, ๋ผ์ฐํ , ๋น์ฆ๋์ค ๋ก์ง
| ์ ๊ทผ ๋ฐฉ์ | ์์ | ํน์ง |
|---|---|---|
| ๋ค์ดํฐ๋ธ ์ฑ | Swift, Kotlin | ์ต๊ณ ์ฑ๋ฅ, ํ๋ซํผ๋ณ ๊ฐ๋ฐ ๋น์ฉ |
| ํฌ๋ก์ค ํ๋ซํผ ๋ค์ดํฐ๋ธ | React Native, Flutter | ๋จ์ผ ์ฝ๋, ๋ค์ดํฐ๋ธ ๋ ๋๋ง |
| WebView ํ์ด๋ธ๋ฆฌ๋ | ์ด ํ๋ก์ ํธ | ์น ๋ฐฐํฌ ์๋ + ๋ค์ดํฐ๋ธ ๊ธฐ๋ฅ |
| PWA | Service Worker ๊ธฐ๋ฐ | ์ค์น ์์ด ์ฑ ์ ์ฌ ๊ฒฝํ |
๊ด๋ จ ํค์๋: Hybrid Mobile Application Architecture, WebView Bridge Pattern, Cross-Platform Development
WebView์ Native ์ฌ์ด์ ํต์ ์ ์ ํต์ ์ผ๋ก postMessage(string) โ ๋จ๋ฐฉํฅ ๋ฌธ์์ด ๋ฉ์์ง๋ก ์ด๋ฃจ์ด์ก๋ค. ์ด ๋ฐฉ์์:
- ์๋ต์ ์ถ์ ํ ์๋จ์ด ์์ (Fire-and-forget)
- ํ์ ์ ๋ณด ์์ค (์ง๋ ฌํ/์ญ์ง๋ ฌํ)
- ์๋ฌ ์ฒ๋ฆฌ ๋ถ๋ช ํ
์์ฒญ ํ๋ฆ:
Web: window.bridge.startRecording()
โ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๊ฐ ๊ณ ์ messageId ๋ถ์ฌ + JSON ์ง๋ ฌํ โ postMessage
โ Native: messageId๋ก ์๋ต ๋งคํ โ Promise resolve
ํ์
๊ณ์ฝ:
interface AppBridgeType extends Bridge {
startRecording: () => Promise<{ status: "success" | "error" }>
}
// Web๊ณผ Native๊ฐ ๋์ผ ์ธํฐํ์ด์ค ํ์
์ ๊ณต์
- RPC (Remote Procedure Call) ๊ฐ๋ ์ WebView ๋ด ์ ์ฉ: ๋ก์ปฌ ํจ์ ํธ์ถ์ฒ๋ผ ๋ณด์ด์ง๋ง ์ง๋ ฌํ โ ์ฑ๋ ์ ์ก โ ์ญ์ง๋ ฌํ ๊ณผ์ ์ ๊ฑฐ์นจ
- ํ์ ์์ IPC(Inter-Process Communication): ์ปดํ์ผ ํ์์ ์ธํฐํ์ด์ค ๋ถ์ผ์น๋ฅผ ๊ฐ์ง
- Promise ๊ธฐ๋ฐ ๋น๋๊ธฐ ์ฒ๋ฆฌ๋ก ์ฝ๋ฐฑ ์ง์ฅ(Callback Hell) ๋ฌธ์ ํด๊ฒฐ
๊ด๋ จ ํค์๋: JavaScript Bridge, Type-Safe IPC, WebView Communication, RPC Pattern
FSM(Finite State Machine): ์ ํํ ์ํ(State)์ ์ํ ๊ฐ ์ ์ด(Transition)๋ก ์์คํ ์ ๋ชจ๋ธ๋งํ๋ ๊ณ์ฐ ์ด๋ก ์ ๊ธฐ๋ณธ ๊ฐ๋ .
ํด์ฆ ํ์ด ๋จ๊ณ๋ ๋ช
์์ FSM์ผ๋ก ๊ตฌํ๋์ด ์๋ค (useQuizStore.ts):
States: "ox" | "select" | "text"
Transitions:
์ด๊ธฐํ โ ox (OX ๋ฌธ์ ์์ผ๋ฉด)
โ select (๊ฐ๊ด์๋ง ์์ผ๋ฉด)
โ text (๋จ๋ตํ๋ง ์์ผ๋ฉด)
ox โ ox (๋ค์ OX ๋ฌธ์ )
ox โ select (OX ์๋ฃ, ๊ฐ๊ด์ ์์ผ๋ฉด)
ox โ text (OX ์๋ฃ, ๊ฐ๊ด์ ์๊ณ ๋จ๋ตํ ์์ผ๋ฉด)
ox โ [์๋ฃ] (OX ์๋ฃ, ์ดํ ๋ฌธ์ ์์ผ๋ฉด completionPayload ์ธํ
)
select โ select (๋ค์ ๊ฐ๊ด์)
select โ text (๊ฐ๊ด์ ์๋ฃ, ๋จ๋ตํ ์์ผ๋ฉด)
select โ [์๋ฃ]
text โ text (๋ค์ ๋จ๋ตํ)
text โ [์๋ฃ]
- ์ญ๋ฐฉํฅ ์ ์ด(
goToPreviousQuestion)๋ ๊ตฌํ: ์คํ ๊ธฐ๋ฐ์ผ๋ก ์ด์ ์ํ ๋ณต์ - UI ์ปดํฌ๋ํธ๋ ํ์ฌ state๋ง ๋ฐ์ ๋ ๋๋ง โ ์ํ์ ๋ทฐ์ ๋ช ํํ ๋ถ๋ฆฌ
- UI ๊ฐ๋ฐ์์ FSM ๋ชจ๋ธ๋ง์ XState ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก๋ ๋๋ฆฌ ์ฐ์. ์ด ํ๋ก์ ํธ๋ Zustand ๋ด์ ์ง์ FSM์ ๊ตฌํํ ์ฌ๋ก
- **๋ช ์์ ์ํ(Explicit State)**๋ "impossible state"๋ฅผ ํ์ ์์คํ ์ผ๋ก ๋ฐฉ์ง (vs. ์ฌ๋ฌ boolean ์กฐํฉ)
- React์
useReducer๋ FSM ๊ตฌํ ์๋จ์ผ๋ก ์์ฃผ ์ธ์ฉ๋จ
๊ด๋ จ ํค์๋: Finite State Machine, UI State Modeling, Explicit State, XState, Impossible States
try/catch ๊ธฐ๋ฐ ์๋ฌ ์ฒ๋ฆฌ๋ ํจ์ ์๊ทธ๋์ฒ์์ ์คํจ ๊ฐ๋ฅ์ฑ์ด ๋๋ฌ๋์ง ์๋๋ค. ํธ์ถ์๊ฐ ์๋ฌ ์ฒ๋ฆฌ๋ฅผ ์์ด๋ ์ปดํ์ผ๋ฌ๊ฐ ๊ฐ์งํ์ง ๋ชปํ๋ค.
type Ok<T> = { ok: true; data: T; status: number; headers: Headers }
type Err = { ok: false; error: { message: string }; status: number }
export type ApiResult<T> = Ok<T> | Errํธ์ถ๋ถ์์ ok ์ฌ๋ถ๋ฅผ ํ์ธํ๊ธฐ ์ ๊น์ง data์ ์ ๊ทผ ๋ถ๊ฐ:
const res = await postQuizList(params);
if (!res.ok) throw new Error("ํด์ฆ ์กฐํ ์คํจ");
return res.data; // ์ฌ๊ธฐ์๋ง data ํ์
๋ณด์ฅ- Rust์
Result<T, E>ํ์ , Haskell์Either๋ชจ๋๋์์ ์๊ฐ - TypeScript์ Discriminated Union(ํ๋ณ ์ ๋์จ) ํจํด์ ์ค๋ฌด ์ ์ฉ ์ฌ๋ก
- Railway-Oriented Programming: ์ฑ๊ณต/์คํจ ๋ ๋ ์ผ๋ก ๋ก์ง์ ํ๋ ค๋ณด๋ด๋ ํจ์ํ ์๋ฌ ์ฒ๋ฆฌ ๊ธฐ๋ฒ
๊ด๋ จ ํค์๋: Result Type, Discriminated Union, Railway-Oriented Programming, Type-Safe Error Handling
์ ํต์ ๋ฐฉ์(Redux + ์๋ fetch)์์ ์๋ฒ ์ํ๋ฅผ ๊ด๋ฆฌํ๋ฉด:
- ๋ก๋ฉ/์๋ฌ/์ฑ๊ณต ์ํ๋ฅผ ๋งค๋ฒ ์ง์ ๊ด๋ฆฌ
- ์บ์ ๋ฌดํจํ ํ์ด๋ฐ ์ง์ ์ง์
- ์ค๋ณต ์์ฒญ ๋ฐฉ์ง ๋ก์ง ๋ฐ๋ณต ์์ฑ
// ์ ์ธ์ ์ฟผ๋ฆฌ ์ ์
export const quizQueries = {
getQuizTopicQuery: () =>
queryOptions({
queryKey: ["quiz", "topic"],
queryFn: async () => { /* fetch */ },
}),
};
// ์ปดํฌ๋ํธ์์๋ ์บ์/๋ก๋ฉ/์๋ฌ๋ฅผ ์๋์ผ๋ก ์ฒ๋ฆฌ
const { data, isLoading, isError } = useQuery(quizQueries.getQuizTopicQuery());์บ์ ํค ๊ณ์ธต ๊ตฌ์กฐ:
["quiz"]
โโ ["quiz", "topic"]
โโ ["quiz", "list", { topicId, count }]
โโ ["quiz", "grading-log", gradingLogId]
- Stale-While-Revalidate(SWR): ์บ์๋ ๋ฐ์ดํฐ๋ฅผ ์ฆ์ ๋ฐํํ๋ฉด์ ๋ฐฑ๊ทธ๋ผ์ด๋์์ ๊ฐฑ์ โ HTTP RFC 5861์์ ์ ์, React Query๊ฐ ์ด ์ ๋ต์ UI์ ์ ์ฉ
- ์๋ฒ ์ํ(Server State) vs ํด๋ผ์ด์ธํธ ์ํ(Client State) ๋ถ๋ฆฌ: ์๋ฒ ์ํ๋ ๋น๋๊ธฐ์ ์ด๊ณ ๊ณต์ ๋จ โ ์ ์ฉ ๋ผ์ด๋ธ๋ฌ๋ฆฌ๋ก ๊ด๋ฆฌ
- React Query์ ์บ์๋ Normalized Cache ๋ฐฉ์์ด ์๋ Key-Value ๋ฐฉ์ (Apollo์ ์ฐจ์ด์ )
๊ด๋ จ ํค์๋: Server State Management, Stale-While-Revalidate, Cache Invalidation, Declarative Data Fetching
SPA์์ Access Token ๋ง๋ฃ ์ ์ฌ๋ฌ API ์์ฒญ์ด ๋์์ 401์ ๋ฐ์ผ๋ฉด, Refresh Token์ผ๋ก ํ ํฐ ๊ฐฑ์ ์์ฒญ์ด N๋ฒ ์ค๋ณต ๋ฐ์ํ ์ ์๋ค โ Refresh Token์ด ๋จ ํ ๋ฒ๋ง ์ ํจํ ๊ฒฝ์ฐ ๋๋จธ์ง ์์ฒญ ์คํจ.
// Promise๋ฅผ ๋ณ์์ ๊ณต์ โ ์ค๋ณต ์์ฒญ ๋ฐฉ์ง
let refreshAccessTokenPromise: Promise<string | null> | null = null;
async function refreshAccessToken() {
if (!refreshAccessTokenPromise) {
refreshAccessTokenPromise = (async () => {
// ์ค์ ๊ฐฑ์ ์์ฒญ (ํ ๋ฒ๋ง ์คํ)
const result = await apiClientHandler(baseApiClient, END_POINTS.AUTH.REISSUE, { method: "POST" });
// ...
})().finally(() => {
refreshAccessTokenPromise = null; // ์๋ฃ ํ ์ด๊ธฐํ
});
}
return refreshAccessTokenPromise; // ์งํ ์ค์ด๋ฉด ๊ฐ์ Promise ๋ฐํ
}๋์์ 5๊ฐ ์์ฒญ์ด 401์ ๋ฐ์๋ ๊ฐฑ์ ์์ฒญ์ ์ ํํ 1ํ๋ง ๋ฐ์ํ๊ณ , ๋๋จธ์ง 4๊ฐ๋ ๊ฐ์ Promise๋ฅผ await.
- Promise Coalescing / Request Deduplication: ๋์ผ ๋น๋๊ธฐ ์์ ์ ํ๋๋ก ํฉ์น๋ ํจํด
- Refresh Token Rotation: ๊ฐฑ์ ์๋ง๋ค ์ Refresh Token ๋ฐ๊ธ โ ํ์ทจ๋ Refresh Token ์ฌ์ฌ์ฉ ๋ฐฉ์ง (OAuth 2.0 BCP)
- Silent Authentication: ์ฌ์ฉ์ ๊ฐ์ ์์ด ์ธ์ ์ ์๋ ๋ณต๊ตฌํ๋ UX ํจํด
๊ด๋ จ ํค์๋: Token Refresh Race Condition, Promise Coalescing, Silent Authentication, OAuth 2.0 Token Rotation
SPA(Single Page Application)์ ๋จ์ ์ธ ์ด๊ธฐ ๋ฒ๋ค ํฌ๊ธฐ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๋ ๊ธฐ๋ฒ. ๋ผ์ฐํธ ๋จ์๋ก JavaScript๋ฅผ ๋ถ๋ฆฌํด ํ์ํ ๋๋ง ๋ก๋.
// stackflow-route.tsx: ๋ชจ๋ Activity๊ฐ lazy()๋ก ์ ์ธ๋จ
const QuizSolvePage = lazy(() => import("@/pages/quiz/QuizSolvePage"));
const RankingPage = lazy(() => import("@/pages/ranking/RankingPage"));
// ...
// stackflow-auth-shell.tsx: lazy๋ AuthInitializer๋ฅผ Suspense๋ก ๊ฐ์ธ
<Suspense fallback={null}>
<AuthInitializer>{children}</AuthInitializer>
</Suspense>- ์ด๊ธฐ ์ง์
์
LoginPage๋ฒ๋ค๋ง ๋ก๋ - ํด์ฆ ํ์ด์ง ์ง์
์์ ์
QuizSolvePage๋ฒ๋ค ๋ก๋ - Vite๊ฐ ๊ฐ lazy import๋ฅผ ๋ณ๋ chunk ํ์ผ๋ก ๋ถ๋ฆฌ
- PRPL ํจํด (Push, Render, Pre-cache, Lazy-load): Google์ด ์ ์ํ Progressive Web App ์ฑ๋ฅ ์ ๋ต
- Core Web Vitals ์ค LCP(Largest Contentful Paint) ๊ฐ์ ์ ์ง์ ๊ธฐ์ฌ
-
Dynamic Import (
import()): ECMAScript 2020 ํ์ค. Webpack/Vite๊ฐ ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก ์ฒญํฌ ๋ถ๋ฆฌ
๊ด๋ จ ํค์๋: Code Splitting, Lazy Loading, Dynamic Import, PRPL Pattern, Core Web Vitals
ํ์ผ์ ์ญํ ๊ธฐ์ค(components/, services/, utils/)์ด ์๋ ๊ธฐ๋ฅ(Feature) ๊ธฐ์ค์ผ๋ก ๊ทธ๋ฃนํํ๋ ๊ตฌ์กฐํ ๋ฐฉ์.
src/
โโโ api/
โ โโโ quiz/ โ quiz ๊ธฐ๋ฅ์ API ์ ๋ถ
โ โ โโโ api.model.ts (ํ์
)
โ โ โโโ api.query.ts (React Query options)
โ โ โโโ postQuizList.ts (fetch ํจ์)
โ โโโ ranking/ โ ranking ๊ธฐ๋ฅ์ API ์ ๋ถ
โโโ model/
โ โโโ quiz/ โ quiz ๊ธฐ๋ฅ์ ์ํ ์ ๋ถ
โ โโโ auth/
โโโ components/
โโโ quiz/ โ quiz ๊ธฐ๋ฅ์ UI ์ ๋ถ
โโโ common/ โ ๊ณต์ ์ปดํฌ๋ํธ
ํ ๊ธฐ๋ฅ์ ์์ ํ ๋ ๊ด๋ จ ํ์ผ์ด ๊ฐ์ ํด๋์ ๋ชจ์ฌ ์์ด ์์ง๋(Cohesion) ๋์, ๊ธฐ๋ฅ ๊ฐ ์ง์ ์์กด์ฑ ๋ฎ์ ๊ฒฐํฉ๋(Coupling) ๋ฎ์.
- ๊ณ ์์ง ์ ๊ฒฐํฉ(High Cohesion, Low Coupling): ์ํํธ์จ์ด ๊ณตํ์ ๋ชจ๋ ์ค๊ณ ์์น
- Screaming Architecture (Robert C. Martin): ๋๋ ํ ๋ฆฌ ๊ตฌ์กฐ๋ฅผ ๋ณด๋ฉด ์์คํ ์ด ๋ฌด์์ ํ๋์ง ๋ฐ๋ก ์ ์ ์์ด์ผ ํ๋ค
- Vertical Slice Architecture: ๋ ์ด์ด(Controller-Service-Repository) ๋์ ๊ธฐ๋ฅ ๋จ์๋ก ์ฝ๋๋ฅผ ์์ง์ผ๋ก ์๋ฅด๋ ํจํด
๊ด๋ จ ํค์๋: Feature-Based Architecture, Screaming Architecture, Vertical Slice, High Cohesion Low Coupling
๋ฐฑ์๋๊ฐ OpenAPI(Swagger) ์คํ์ ๊ธฐ์ค(Contract)์ผ๋ก ์ ์ํ๊ณ , ํ๋ก ํธ์๋๋ ๊ทธ ์คํ์์ ํ์ ์ ์๋ ์์ฑ. API ๋ณ๊ฒฝ ์ ์ฌ์์ฑ๋ง ํ๋ฉด ํ์ ๋ถ์ผ์น๋ฅผ ์ปดํ์ผ ํ์์ ๊ฐ์ง.
๋ฐฑ์๋ ์๋ฒ
โโ /v3/api-docs (OpenAPI JSON ์คํ ๋
ธ์ถ)
โ pnpm generate:api-models
web/src/api/config/api-models.ts (์๋ ์์ฑ)
โโ ๊ฐ api/*.model.ts์์ ํ์
์ฐธ์กฐ
โโ ์ปดํฌ๋ํธ์์ ํ์
์์ ํ๊ฒ ์ฌ์ฉ
- Schema-First / Contract-First Development: API ์คํ์ ์ฝ๋๋ณด๋ค ๋จผ์ ์ ์ โ ํ๋ก ํธ/๋ฐฑ์๋ ๋ณ๋ ฌ ๊ฐ๋ฐ ๊ฐ๋ฅ
- Code Generation: ๋ฐ๋ณต์ ์ธ ๋ณด์ผ๋ฌํ๋ ์ดํธ๋ฅผ ์๋ํ โ ํด๋จผ ์๋ฌ ๊ฐ์, ์ผ๊ด์ฑ ์ ์ง
- OpenAPI 3.0์ REST API์ ์ฌ์ค์ ํ์ค ๋ช ์ธ ์ธ์ด๋ก, IDL(Interface Definition Language)์ ์ญํ ์ํ
๊ด๋ จ ํค์๋: Contract-First API Design, OpenAPI, Code Generation, Schema-Driven Development, IDL
- ๐ฃ๏ธ Roadmap ------------------------------
- ๐ Sprint Planning
- ๐ Sprint Backlog