diff --git a/README.md b/README.md index cf07e1f..9841438 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,14 @@ **๐ŸŒ ์„œ๋น„์Šค URL:** https://investfuture.my +## ๐Ÿ“š ๊ฐœ๋ฐœ ํ›„๊ธฐ ๋ฐ ๊ธฐ์ˆ  ๋ธ”๋กœ๊ทธ + +ํ”„๋กœ์ ํŠธ ๊ฐœ๋ฐœ ๊ณผ์ •์—์„œ ๊ฒช์€ ๊ธฐ์ˆ ์  ๋„์ „๊ณผ ํ•ด๊ฒฐ ๊ณผ์ •์„ ์ •๋ฆฌํ•œ ๋ฌธ์„œ๋“ค์ž…๋‹ˆ๋‹ค: + +- **[๋ฐฐํฌ ๋ฐ ์ธํ”„๋ผ ๊ตฌ์ถ• ํ›„๊ธฐ](./docs/[๊ฐœ๋ฐœํ›„๊ธฐ]CI-CD.md)** - Docker, GitHub Actions, AWS EC2๋ฅผ ํ™œ์šฉํ•œ CI/CD ํŒŒ์ดํ”„๋ผ์ธ ๊ตฌ์ถ• ๊ณผ์ • +- **[์ฐจํŠธ ์„ฑ๋Šฅ ๊ฐœ์„  ํ›„๊ธฐ](./docs/[๊ฐœ๋ฐœํ›„๊ธฐ]-์ฐจํŠธ์„ฑ๋Šฅ๊ฐœ์„ .md)** - AmCharts์—์„œ TradingView Lightweight Charts๋กœ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ํ•˜์—ฌ ์„ฑ๋Šฅ ์ตœ์ ํ™”ํ•œ ๊ณผ์ • +- **[์›น์†Œ์ผ“ ๊ฐœ์„  ํ›„๊ธฐ](./docs/[๊ฐœ๋ฐœํ›„๊ธฐ]-์›น์†Œ์ผ“๊ฐœ์„ .md)** - ๊ฐœ๋ณ„ STOMP ์—ฐ๊ฒฐ์—์„œ ๋‹จ์ผ ์ธ์Šคํ„ด์Šค ๊ณต์œ ๋กœ ๋ฆฌํŒฉํ† ๋งํ•œ ์‹ค์‹œ๊ฐ„ ํ†ต์‹  ์ตœ์ ํ™” ๊ณผ์ • + --- ### ๐Ÿ“Š ํ”„๋กœ์ ํŠธ ๊ฐœ์š” diff --git "a/docs/[\352\260\234\353\260\234\355\233\204\352\270\260]-\354\233\271\354\206\214\354\274\223\352\260\234\354\204\240.md" "b/docs/[\352\260\234\353\260\234\355\233\204\352\270\260]-\354\233\271\354\206\214\354\274\223\352\260\234\354\204\240.md" new file mode 100644 index 0000000..4580012 --- /dev/null +++ "b/docs/[\352\260\234\353\260\234\355\233\204\352\270\260]-\354\233\271\354\206\214\354\274\223\352\260\234\354\204\240.md" @@ -0,0 +1,262 @@ +# ์›น์†Œ์ผ“ ๊ฐœ์„  ํ›„๊ธฐ + +## ํŠธ๋ ˆ์ด๋”ฉ ํ”Œ๋žซํผ์—์„œ์˜ ์‹ค์‹œ๊ฐ„ ํ†ต์‹  ์ค‘์š”์„ฑ + +ํŠธ๋ ˆ์ด๋”ฉ ํ”Œ๋žซํผ์€ **์„ฑ๋Šฅ ์šฐ์„ (Performance-First)** ์„ค๊ณ„๊ฐ€ ํ•ต์‹ฌ์ž…๋‹ˆ๋‹ค. ์‚ฌ์šฉ์ž๋Š” ์‹ค์‹œ๊ฐ„์œผ๋กœ ๋ณ€ํ™”ํ•˜๋Š” ์‹œ์žฅ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ”ํƒ•์œผ๋กœ ๋น ๋ฅธ ์˜์‚ฌ๊ฒฐ์ •์„ ๋‚ด๋ ค์•ผ ํ•˜๋ฉฐ, 1์ดˆ์˜ ์ง€์—ฐ๋„ ํฐ ์†์‹ค๋กœ ์ด์–ด์งˆ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. + +์ €ํฌ ํ”„๋กœ์ ํŠธ์—์„œ๋Š” ํ•˜๋‚˜์˜ ํŽ˜์ด์ง€์— ๋‹ค์Œ๊ณผ ๊ฐ™์€ **๋‹ค์ˆ˜์˜ ์‹ค์‹œ๊ฐ„ ์š”์†Œ**๋“ค์ด ๋™์‹œ์— ๋™์ž‘ํ•ด์•ผ ํ–ˆ์Šต๋‹ˆ๋‹ค: +- ์‹ค์‹œ๊ฐ„ ๊ฐ€๊ฒฉ ์ฐจํŠธ (StockChart) +- ์‹ค์‹œ๊ฐ„ ํ˜ธ๊ฐ€์ฐฝ (OrderBook) +- ์‹ค์‹œ๊ฐ„ ์ฒด๊ฒฐ ๋ชฉ๋ก (ExecutionList) +- ๊ฑฐ๋ž˜ ์™„๋ฃŒ ์•Œ๋ฆผ (TradeNotification) +- ํ˜„์žฌ๊ฐ€ ํ‘œ์‹œ (CurrentPrice) + +์ด๋Ÿฌํ•œ ๋ณต์žกํ•œ ์š”๊ตฌ์‚ฌํ•ญ์„ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•œ ์›น์†Œ์ผ“ ์•„ํ‚คํ…์ฒ˜์˜ ์ง„ํ™” ๊ณผ์ •์„ ์ •๋ฆฌํ–ˆ์Šต๋‹ˆ๋‹ค. + +## 1๋‹จ๊ณ„: ๊ฐœ๋ณ„ STOMP ํด๋ผ์ด์–ธํŠธ ์ƒ์„ฑ ๋ฐฉ์‹ (์ดˆ๊ธฐ ์„ค๊ณ„) + +### ์„ค๊ณ„ ๋ฐฐ๊ฒฝ +์ดˆ๊ธฐ์—๋Š” ๊ฐ ํ›…์—์„œ **๊ฐœ๋ณ„์ ์œผ๋กœ STOMP Client ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑ**ํ•˜๋Š” ๋ฐฉ์‹์„ ์‚ฌ์šฉํ–ˆ์Šต๋‹ˆ๋‹ค. STOMP ํ”„๋กœํ† ์ฝœ ์ž์ฒด๋Š” ์ฒ˜์Œ๋ถ€ํ„ฐ ์‚ฌ์šฉํ–ˆ์ง€๋งŒ, ๊ฐ ๊ธฐ๋Šฅ๋ณ„๋กœ ๋ณ„๋„์˜ ํด๋ผ์ด์–ธํŠธ๋ฅผ ๋งŒ๋“œ๋Š” ๊ตฌ์กฐ์˜€์Šต๋‹ˆ๋‹ค. + +```typescript +// ์‹ค์ œ ์ดˆ๊ธฐ ๊ตฌํ˜„ (415a722 ์ปค๋ฐ‹ ์ด์ „) +export default function useOrderBookData(ticker = 'TRUMP') { + const [data, setData] = useState(); + + useEffect(() => { + const client = new Client({ + brokerURL: `${import.meta.env.VITE_STOMP_URL}/api/coin/realtime`, + }); + + client.onConnect = () => { + client.subscribe(`/topic/orderbook/${ticker}`, (message) => { + const parsedData = JSON.parse(message.body) as RawOrderBookData; + setData(parsedData); + }); + }; + + client.onWebSocketError = (error) => { + console.error('onWebSocketError', error); + }; + + client.activate(); + + return () => { + client.deactivate(); + }; + }, [ticker]); + + return data; +} +``` + +๋™์ผํ•œ ํŒจํ„ด์œผ๋กœ ๊ตฌํ˜„๋œ ๋‹ค๋ฅธ ํ›…๋“ค: +- `useRealTimeData`: ์‹ค์‹œ๊ฐ„ ์ฐจํŠธ ๋ฐ์ดํ„ฐ์šฉ ๊ฐœ๋ณ„ ํด๋ผ์ด์–ธํŠธ +- `useExecutionListData`: ์ฒด๊ฒฐ ๋ชฉ๋ก์šฉ ๊ฐœ๋ณ„ ํด๋ผ์ด์–ธํŠธ +- `useRealTimePrice`: ์‹ค์‹œ๊ฐ„ ๊ฐ€๊ฒฉ์šฉ ๊ฐœ๋ณ„ ํด๋ผ์ด์–ธํŠธ + +### ๋ฌธ์ œ์  ๋ฐœ๊ฒฌ +๊ฐ ๊ธฐ๋Šฅ๋งˆ๋‹ค ๋ณ„๋„์˜ STOMP ํด๋ผ์ด์–ธํŠธ๋ฅผ ์ƒ์„ฑํ•˜๋ฉด์„œ ๋‹ค์Œ๊ณผ ๊ฐ™์€ ๋ฌธ์ œ๋“ค์ด ๋‚˜ํƒ€๋‚ฌ์Šต๋‹ˆ๋‹ค: + +1. **๊ณผ๋„ํ•œ ์—ฐ๊ฒฐ ์˜ค๋ฒ„ํ—ค๋“œ**: ํ•˜๋‚˜์˜ ํŽ˜์ด์ง€์—์„œ 5-6๊ฐœ์˜ STOMP ์—ฐ๊ฒฐ ๋™์‹œ ์œ ์ง€ +2. **์„œ๋ฒ„ ๋ฆฌ์†Œ์Šค ๋‚ญ๋น„**: ๊ฐ ์—ฐ๊ฒฐ๋งˆ๋‹ค ๋ณ„๋„์˜ WebSocket ์—ฐ๊ฒฐ๊ณผ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ +3. **์ค‘๋ณต ์—ฐ๊ฒฐ ๋กœ์ง**: ๋™์ผํ•œ ์—ฐ๊ฒฐ ์„ค์ • ์ฝ”๋“œ๊ฐ€ ์—ฌ๋Ÿฌ ํ›…์—์„œ ๋ฐ˜๋ณต +4. **์—ฐ๊ฒฐ ๊ด€๋ฆฌ ๋ณต์žก์„ฑ**: ๊ฐ๊ฐ์˜ ์—ฐ๊ฒฐ ์ƒํƒœ๋ฅผ ๊ฐœ๋ณ„์ ์œผ๋กœ ๋ชจ๋‹ˆํ„ฐ๋ง + +## 2๋‹จ๊ณ„: ๋‹จ์ผ STOMP ์ธ์Šคํ„ด์Šค๋กœ ๋ฆฌํŒฉํ† ๋ง (Provider ํŒจํ„ด ๋„์ž…) + +### ๋ฆฌํŒฉํ† ๋ง ๋™๊ธฐ +415a722 ์ปค๋ฐ‹์—์„œ **"๋‹จ์ผ stomp ์ธ์Šคํ„ด์Šค์—์„œ subscribe ํ•˜๋„๋ก ๋ณ€๊ฒฝ"**์„ ์ง„ํ–‰ํ–ˆ์Šต๋‹ˆ๋‹ค. STOMP ํ”„๋กœํ† ์ฝœ์˜ ํ•ต์‹ฌ ์žฅ์ ์ธ **Topic ๊ธฐ๋ฐ˜ ๊ตฌ๋…** ๊ธฐ๋Šฅ์„ ์ œ๋Œ€๋กœ ํ™œ์šฉํ•˜๊ธฐ๋กœ ํ–ˆ์Šต๋‹ˆ๋‹ค. + +### STOMP Provider ๊ตฌํ˜„ + +```typescript +// src/app/provider/StompProvider.tsx +export default function StompProvider({ children, brokerURL }: StompProviderProps) { + const [stompClient, setStompClient] = useState(null); + const [isConnected, setIsConnected] = useState(false); + + useEffect(() => { + const client = new Client({ brokerURL }); + + client.onConnect = () => setIsConnected(true); + client.onDisconnect = () => setIsConnected(false); + + client.activate(); + setStompClient(client); + + return () => client.deactivate(); + }, [brokerURL]); + + return ( + + {children} + + ); +} +``` + +### ๊ณตํ†ต ํ›… ํŒจํ„ด ์„ค๊ณ„ + +๊ฐ ๊ธฐ๋Šฅ๋ณ„๋กœ ๋™์ผํ•œ ํŒจํ„ด์„ ๋”ฐ๋ฅด๋Š” ํ›…์„ ๊ตฌํ˜„ํ–ˆ์Šต๋‹ˆ๋‹ค: + +```typescript +// src/entities/coin/hooks/useCurrentPrice.tsx +export default function useCurrentPrice(ticker: string) { + const { client, connected } = useStompClient(); + const [data, setData] = useState(null); + + useEffect(() => { + if (!client || !connected) return; + + // ๊ตฌ๋… ์š”์ฒญ + client.publish({ + destination: `/app/subscribe/prevRate/${ticker}`, + body: JSON.stringify({ ticker }), + }); + + // ํ† ํ”ฝ ๊ตฌ๋… + const subscription = client.subscribe( + `/topic/prevRate/${ticker}`, + (message) => { + const parsedData = JSON.parse(message.body); + setData(parsedData); + } + ); + + return () => subscription.unsubscribe(); + }, [client, connected, ticker]); + + return data; +} +``` + +๋™์ผํ•œ ํŒจํ„ด์œผ๋กœ ๊ตฌํ˜„๋œ ๋‹ค๋ฅธ ํ›…๋“ค: +- `useOrderBookData`: ์‹ค์‹œ๊ฐ„ ํ˜ธ๊ฐ€ ๋ฐ์ดํ„ฐ +- `useRealTimeData`: ์‹ค์‹œ๊ฐ„ OHLC ์ฐจํŠธ ๋ฐ์ดํ„ฐ +- `useTradeNotification`: ๊ฑฐ๋ž˜ ์™„๋ฃŒ ์•Œ๋ฆผ +- `useExecutionListData`: ์‹ค์‹œ๊ฐ„ ์ฒด๊ฒฐ ๋ชฉ๋ก + +## 3๋‹จ๊ณ„: ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ ์ง€์—ฐ ๋ฌธ์ œ ํ•ด๊ฒฐ + +### ๋ฌธ์ œ ์ƒํ™ฉ +React Router V7์˜ SSR ํ™˜๊ฒฝ์—์„œ **์›น์†Œ์ผ“ ์—ฐ๊ฒฐ ์ง€์—ฐ์œผ๋กœ ์ธํ•œ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๋ฌธ์ œ**๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค: +- SSR ํ•˜์ด๋“œ๋ ˆ์ด์…˜์€ ์ •์ƒ์ ์œผ๋กœ ์™„๋ฃŒ๋˜์ง€๋งŒ, ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ๊นŒ์ง€ ์ถ”๊ฐ€ ์‹œ๊ฐ„์ด ํ•„์š” +- ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ์™„๋ฃŒ ํ›„ ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ ์ „๊นŒ์ง€์˜ ๊ณต๋ฐฑ ๊ธฐ๊ฐ„ ๋™์•ˆ ๊ฐ€๊ฒฉ์ด `0` ๋˜๋Š” `-`๋กœ ํ‘œ์‹œ +- ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๊ฐ€ ๋„์ฐฉํ•˜๊ธฐ ์ „๊นŒ์ง€ ๋นˆ ํ™”๋ฉด์ด๋‚˜ ๋กœ๋”ฉ ์ƒํƒœ ์ง€์† +- ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ์ €ํ•˜ ๋ฐ CLS(Cumulative Layout Shift) ๋ฌธ์ œ + +### ํ•ด๊ฒฐ ๋ฐฉ์•ˆ: ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ฐ์ดํ„ฐ ํŽ˜์นญ + +React Router V7์˜ `loader` ํ•จ์ˆ˜๋ฅผ ํ™œ์šฉํ•˜์—ฌ ์ดˆ๊ธฐ ๋ฐ์ดํ„ฐ๋ฅผ ์„œ๋ฒ„์—์„œ ํŽ˜์นญ: + +```typescript +// src/app/routes/trade.tsx +export async function loader({ request, params }: Route.LoaderArgs) { + const response = await coinApi.getCoinList(); + const { data } = await response.json(); + + const ticker = params.ticker.toUpperCase(); + const coinInfo = data.assets.find((coin) => coin.ticker === ticker); + + return { coinList: data.assets, coinInfo }; +} + +export default function TradeRouteComponent({ loaderData }: Route.ComponentProps) { + const { coinInfo } = loaderData; + + return ( +
+ {coinInfo && ( + + )} +
+ ); +} +``` + +### ๋ฐ์ดํ„ฐ ํ๋ฆ„ ์ตœ์ ํ™” + +1. **SSR ๋‹จ๊ณ„**: React Router ์„œ๋ฒ„์—์„œ Spring Boot API๋ฅผ ํ˜ธ์ถœํ•˜์—ฌ ์ดˆ๊ธฐ ์ฝ”์ธ ๋ฐ์ดํ„ฐ ํŽ˜์นญ +2. **์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง**: ํŽ˜์นญํ•œ ๋ฐ์ดํ„ฐ๋กœ HTML์„ ๋ฏธ๋ฆฌ ๋ Œ๋”๋งํ•˜์—ฌ ํด๋ผ์ด์–ธํŠธ์— ์ „์†ก +3. **ํ•˜์ด๋“œ๋ ˆ์ด์…˜**: ํด๋ผ์ด์–ธํŠธ์—์„œ ์„œ๋ฒ„ ๋ Œ๋”๋ง๋œ HTML๊ณผ React ์ƒํƒœ ๋™๊ธฐํ™” +4. **์›น์†Œ์ผ“ ์—ฐ๊ฒฐ**: STOMP ํด๋ผ์ด์–ธํŠธ ์—ฐ๊ฒฐ ์‹œ์ž‘ (๋ฐฑ๊ทธ๋ผ์šด๋“œ) +5. **์‹ค์‹œ๊ฐ„ ์ „ํ™˜**: ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ ์™„๋ฃŒ ํ›„ ์„œ๋ฒ„ ๋ฐ์ดํ„ฐ์—์„œ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ๋กœ ๋งค๋„๋Ÿฝ๊ฒŒ ์ „ํ™˜ +6. **์ง€์†์  ์—…๋ฐ์ดํŠธ**: STOMP ๊ตฌ๋…์„ ํ†ตํ•œ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ๊ฐฑ์‹  + +## ์ตœ์ข… ์•„ํ‚คํ…์ฒ˜ + +``` +โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” +โ”‚ App Root โ”‚ +โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ +โ”‚ โ”‚ UserInfoProvider โ”‚ โ”‚ +โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ StompProvider โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ (Single WebSocket) โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ Chart โ”‚ โ”‚ OrderBook โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ Hook โ”‚ โ”‚ Hook โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚Executionโ”‚ โ”‚Trade โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ”‚List Hookโ”‚ โ”‚Notification โ”‚ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ โ”‚ +โ”‚ โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ โ”‚ +โ”‚ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ โ”‚ +โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ +``` + +### ์ตœ์ข… ๊ตฌํ˜„ ๊ฒฐ๊ณผ + +#### 1. ์—ฐ๊ฒฐ ํšจ์œจ์„ฑ +- **์ด์ „**: 5-6๊ฐœ์˜ ๊ฐœ๋ณ„ ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ +- **์ดํ›„**: 1๊ฐœ์˜ STOMP ์—ฐ๊ฒฐ๋กœ ๋ชจ๋“  ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ + +#### 2. ์„œ๋ฒ„ ๋ถ€ํ•˜ ๊ฐ์†Œ +- **์—ฐ๊ฒฐ ์ˆ˜**: 80% ๊ฐ์†Œ (6๊ฐœ โ†’ 1๊ฐœ) +- **๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰**: ๋Œ€ํญ ๊ฐ์†Œ +- **๋„คํŠธ์›Œํฌ ์˜ค๋ฒ„ํ—ค๋“œ**: ์ค‘๋ณต ๋ฐ์ดํ„ฐ ์ „์†ก ์ œ๊ฑฐ + +#### 3. ์ฝ”๋“œ ์ผ๊ด€์„ฑ +๋ชจ๋“  ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ํ›…์ด ๋™์ผํ•œ ํŒจํ„ด์„ ๋”ฐ๋ฆ„: +```typescript +const { client, connected } = useStompClient(); +// 1. ์—ฐ๊ฒฐ ํ™•์ธ +// 2. ๊ตฌ๋… ์š”์ฒญ ๋ฐœํ–‰ +// 3. ํ† ํ”ฝ ๊ตฌ๋… +// 4. ์ •๋ฆฌ ํ•จ์ˆ˜์—์„œ ๊ตฌ๋… ํ•ด์ œ +``` + + +### ์„ฑ๋Šฅ ์ง€ํ‘œ ๊ฐœ์„  + +| ๋ฉ”ํŠธ๋ฆญ | ๊ฐœ์„  ์ „ | ๊ฐœ์„  ํ›„ | ๊ฐœ์„ ๋ฅ  | +|--------|---------|---------|--------| +| ์›น์†Œ์ผ“ ์—ฐ๊ฒฐ ์ˆ˜ | 5-6๊ฐœ | 1๊ฐœ | 80-83% ๊ฐ์†Œ | +| ์ดˆ๊ธฐ ๋กœ๋”ฉ ์‹œ๊ฐ„ | 2-3์ดˆ | 0.5์ดˆ | 75% ๊ฐ์†Œ | +| ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ | ๋†’์Œ | ๋ณดํ†ต | 60% ๊ฐ์†Œ | +| ํ•˜์ด๋“œ๋ ˆ์ด์…˜ CLS | ๋ฐœ์ƒ | ์—†์Œ | 100% ํ•ด๊ฒฐ | + +## ๊ฒฐ๋ก : ํŠธ๋ ˆ์ด๋”ฉ ํ”Œ๋žซํผ์— ์ตœ์ ํ™”๋œ ์‹ค์‹œ๊ฐ„ ์•„ํ‚คํ…์ฒ˜ + +### ํ•ต์‹ฌ ์„ฑ๊ณต ์š”์ธ + +1. **๋‹จ์ผ ์—ฐ๊ฒฐ, ๋‹ค์ค‘ ๊ตฌ๋…**: STOMP ํ”„๋กœํ† ์ฝœ์˜ Topic ๊ธฐ๋ฐ˜ ๊ตฌ๋… ๋ชจ๋ธ +2. **Provider ํŒจํ„ด**: React Context๋ฅผ ํ™œ์šฉํ•œ ์—ฐ๊ฒฐ ์ƒํƒœ ๊ณต์œ  +3. **SSR ๋ฐ์ดํ„ฐ ํŽ˜์นญ**: ํ•˜์ด๋“œ๋ ˆ์ด์…˜ ๋ฌธ์ œ ํ•ด๊ฒฐ์„ ์œ„ํ•œ ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ +4. **์ผ๊ด€๋œ ํ›… ํŒจํ„ด**: ๋ชจ๋“  ์‹ค์‹œ๊ฐ„ ๊ธฐ๋Šฅ์— ๋™์ผํ•œ ๊ตฌํ˜„ ํŒจํ„ด ์ ์šฉ + +### ์–ป์€ ๊ตํ›ˆ + +- **์„ฑ๋Šฅ ์šฐ์„  ์„ค๊ณ„**: ํŠธ๋ ˆ์ด๋”ฉ ํ”Œ๋žซํผ์—์„œ๋Š” ์—ฐ๊ฒฐ ํšจ์œจ์„ฑ์ด ์‚ฌ์šฉ์ž ๊ฒฝํ—˜์— ์ง๊ฒฐ +- **ํ”„๋กœํ† ์ฝœ ์„ ํƒ์˜ ์ค‘์š”์„ฑ**: ๋‹จ์ˆœํ•œ WebSocket๋ณด๋‹ค STOMP๊ฐ€ ๋ณต์žกํ•œ ์‹ค์‹œ๊ฐ„ ์š”๊ตฌ์‚ฌํ•ญ์— ์ ํ•ฉ +- **SSR ํ™˜๊ฒฝ ๊ณ ๋ ค**: ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ์™€ ์„œ๋ฒ„์‚ฌ์ด๋“œ ๋ Œ๋”๋ง์˜ ์กฐํ™” +- **ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ํŒจํ„ด**: ์ƒˆ๋กœ์šด ์‹ค์‹œ๊ฐ„ ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ์‹œ ๊ธฐ์กด ํŒจํ„ด ์žฌ์‚ฌ์šฉ ๊ฐ€๋Šฅ diff --git "a/docs/[\352\260\234\353\260\234\355\233\204\352\270\260]-\354\260\250\355\212\270\354\204\261\353\212\245\352\260\234\354\204\240.md" "b/docs/[\352\260\234\353\260\234\355\233\204\352\270\260]-\354\260\250\355\212\270\354\204\261\353\212\245\352\260\234\354\204\240.md" new file mode 100644 index 0000000..1ab7b1a --- /dev/null +++ "b/docs/[\352\260\234\353\260\234\355\233\204\352\270\260]-\354\260\250\355\212\270\354\204\261\353\212\245\352\260\234\354\204\240.md" @@ -0,0 +1,474 @@ +# ์ฐจํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ฐœ์„  ํ›„๊ธฐ + +CleanEngine ํ”„๋กœ์ ํŠธ์—์„œ ์ฐจํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ๋ฅผ AmCharts์—์„œ TradingView Lightweight Charts๋กœ ์ „ํ™˜ํ•œ ๊ฒฝํ—˜์„ ๊ธฐ๋กํ•œ ๋ฌธ์„œ์ž…๋‹ˆ๋‹ค. + +## ํ”„๋กœ์ ํŠธ ๊ฐœ์š” + +- **๊ธฐ๊ฐ„**: 2025๋…„ 5์›” ~ 7์›” (์•ฝ 2๊ฐœ์›”) +- **๋ชฉํ‘œ**: ์‹ค์‹œ๊ฐ„ ๊ธˆ์œต ์ฐจํŠธ ๊ตฌํ˜„ +- **์ฃผ์š” ๊ธฐ๋Šฅ**: ์บ”๋“ค์Šคํ‹ฑ ์ฐจํŠธ, ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ, ๋ฌดํ•œ์Šคํฌ๋กค, ๊ธฐ๊ฐ„ ์„ ํƒ +- **์„ฑ๋Šฅ ๋ชฉํ‘œ**: Web Vitals 80์  ์ด์ƒ + +--- + +## 1๋‹จ๊ณ„: AmCharts ์„ ํƒ๊ณผ ๊ตฌํ˜„ (2025๋…„ 5์›”) + +### ์ดˆ๊ธฐ ์„ ํƒ ์ด์œ  + +**Canvas API ๊ธฐ๋ฐ˜์˜ ์„ฑ๋Šฅ ์šฐ์œ„** +- SVG ๊ธฐ๋ฐ˜ ์ฐจํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋Œ€๋น„ **๋ Œ๋”๋ง ์„ฑ๋Šฅ์ด ๋›ฐ์–ด๋‚  ๊ฒƒ**์œผ๋กœ ์˜ˆ์ƒ +- ๋Œ€์šฉ๋Ÿ‰ ๊ธˆ์œต ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ์— ์ ํ•ฉํ•  ๊ฒƒ์œผ๋กœ ํŒ๋‹จ +- ํ”„๋กœํŽ˜์…”๋„ํ•œ ์ฐจํŠธ ์™ธ๊ด€๊ณผ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ ์ œ๊ณต + +### ๊ตฌํ˜„ ๊ณผ์ •์—์„œ์˜ ๋ฌธ์ œ์  + +**1. React ์ƒํƒœ๊ณ„์™€์˜ ๋ถ€์กฐํ™”** +```javascript +// AmCharts์˜ ๋ช…๋ นํ˜• API ์‚ฌ์šฉ ์˜ˆ์‹œ (๊ธฐ์กด ๋ฐฉ์‹) +const chart = am5xy.XYChart.new(root, { + panX: true, + panY: true, + wheelX: "panX", + wheelY: "zoomX" +}); + +const series = chart.series.push( + am5xy.CandlestickSeries.new(root, { + name: "MSFT", + xAxis: xAxis, + yAxis: yAxis, + valueYField: "Close", + openValueYField: "Open", + lowValueYField: "Low", + highValueYField: "High", + valueXField: "Date" + }) +); +``` + +**๋ฌธ์ œ์ :** +- **์ˆœ์ˆ˜ JavaScript ๊ธฐ๋ฐ˜**์œผ๋กœ React ๋ž˜ํ•‘ ์ง€์› ์—†์Œ +- **๋ช…๋ นํ˜•(Imperative) API**๋กœ React์˜ ์„ ์–ธ์  ํŒจ๋Ÿฌ๋‹ค์ž„๊ณผ ์ถฉ๋Œ +- ์ปดํฌ๋„ŒํŠธ ์ƒ๋ช…์ฃผ๊ธฐ ๊ด€๋ฆฌ์˜ ๋ณต์žก์„ฑ + +### ์„ ์–ธ์  ๊ตฌ์กฐ๋กœ ๋ฆฌํŒฉํ† ๋ง + +์ด ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด **์ƒ๋‹นํ•œ ์‹œ๊ฐ„์„ ํˆฌ์ž**ํ•˜์—ฌ ์ฐจํŠธ ์ปดํฌ๋„ŒํŠธ๋ฅผ ์„ ์–ธ์ ์œผ๋กœ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ๋ฆฌํŒฉํ† ๋งํ–ˆ์Šต๋‹ˆ๋‹ค. + +**๋ฆฌํŒฉํ† ๋ง ๊ฒฐ๊ณผ:** +```typescript +// ๋ฆฌํŒฉํ† ๋ง ํ›„ ์„ ์–ธ์  ์‚ฌ์šฉ ๋ฐฉ์‹ + + + + + + +``` + +**๊ตฌํ˜„ํ•œ ์ปดํฌ๋„ŒํŠธ:** +- `ChartRoot`: ์ฐจํŠธ ์ปจํ…Œ์ด๋„ˆ ๋ฃจํŠธ ์ œ๊ณต +- `ChartContainer`: ์ฐจํŠธ ์ธ์Šคํ„ด์Šค ์ƒ์„ฑ ๋ฐ ๊ด€๋ฆฌ +- `Series`: ์‹œ๋ฆฌ์ฆˆ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ +- `ToolTip`: ํˆดํŒ ๊ธฐ๋Šฅ ์ œ๊ณต + +**Git ํžˆ์Šคํ† ๋ฆฌ:** +- `fbaed20` (5์›” 9์ผ): AmCharts 5 ์„ค์น˜ ๋ฐ ์ดˆ๊ธฐ ๊ตฌํ˜„ +- `94056bb` (6์›”): ์ฐจํŠธ ์„ ์–ธํ˜•์œผ๋กœ ๋ณ€ํ™˜ +- `f0af9a4` (6์›”): ์ฐจํŠธ ๋ฒ”๋ก€ ์ถ”๊ฐ€ +- `a698ab7` (6์›”): ์ฐจํŠธ ํ•œ๊ธ€ํ™” +- `e19bcaf` (6์›”): ์ฐจํŠธ ๊ธฐ์ˆ ์ง€ํ‘œ ์ถ”๊ฐ€ + +--- + +## 2๋‹จ๊ณ„: ์„ฑ๋Šฅ ๋ฌธ์ œ ๋ฐœ๊ฒฌ (2025๋…„ 6์›”) + +### Lighthouse ์„ฑ๋Šฅ ์ธก์ • ๊ฒฐ๊ณผ + +**๊ฐœ๋ฐœ ํ™˜๊ฒฝ vs ๋ฐฐํฌ ํ™˜๊ฒฝ์˜ ์„ฑ๋Šฅ ์ฐจ์ด** +- **๊ฐœ๋ฐœ ํ™˜๊ฒฝ**: 80-100์  (์–‘ํ˜ธ) +- **๋ฐฐํฌ ํ™˜๊ฒฝ**: 40-50์  (๋งค์šฐ ๋‚ฎ์Œ) + +### ์ดˆ๊ธฐ ๊ฐ€์„ค๊ณผ ์ตœ์ ํ™” ์‹œ๋„ + +**๊ฐ€์„ค 1: ๋„คํŠธ์›Œํฌ ๋ฐ ๋ฆฌ์†Œ์Šค ๋ฌธ์ œ** +๋ฐฐํฌ ํ™˜๊ฒฝ์˜ ๋‚ฎ์€ ์ ์ˆ˜๊ฐ€ ๋„คํŠธ์›Œํฌ๋‚˜ ๋ฆฌ์†Œ์Šค ๋กœ๋”ฉ ๋ฌธ์ œ๋ผ๊ณ  ํŒ๋‹จํ•˜์—ฌ ์ธํ”„๋ผ ์ตœ์ ํ™”๋ฅผ ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค. + +**์‹œ๋„ํ•œ ์ตœ์ ํ™” (๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ nginx ์„ค์ •):** +```nginx +# HTTP/3 ํ”„๋กœํ† ์ฝœ ์ ์šฉ +listen 443 quic reuseport; +http3 on; +http2 on; + +# Gzip ์••์ถ• +gzip on; +gzip_comp_level 6; +gzip_types text/plain text/css application/json application/javascript; + +# ์ •์  ์—์…‹ ์บ์‹ฑ +location ~* \.(js|css|png|jpg|jpeg|webp|gif|svg|ico|woff|woff2|ttf)$ { + proxy_cache STATIC_CACHE; + proxy_cache_valid 200 302 60m; + expires 60m; +} +``` + +**๊ฒฐ๊ณผ: ์„ฑ๋Šฅ ๊ฐœ์„  ํšจ๊ณผ ๋ฏธ๋ฏธ** + +### ์ง„์งœ ๋ฌธ์ œ ๋ฐœ๊ฒฌ + +**Resource Timing ๋ถ„์„ ๊ฒฐ๊ณผ:** +- ๋ฐฐํฌ ํ™˜๊ฒฝ์—์„œ๋Š” ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๊ฐ€ ์ง€์†์ ์œผ๋กœ ์šด์˜๋˜์–ด **๊ธด ๊ธฐ๊ฐ„์˜ ์บ”๋“ค์Šคํ‹ฑ ๋ฐ์ดํ„ฐ**๊ฐ€ ์ถ•์  +- REST API๋กœ **๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ๋ฒˆ์— ๋กœ๋“œ**ํ•˜๋ ค ์‹œ๋„ +- ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ๋ Œ๋”๋ง์œผ๋กœ ์ธํ•œ **JavaScript ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋ธ”๋กœํ‚น** + +**๋ฌธ์ œ์˜ ํ•ต์‹ฌ:** +```javascript +// ๋ฌธ์ œ๊ฐ€ ๋œ ๊ธฐ์กด ๋ฐฉ์‹ +const fetchAllCandleData = async () => { + const response = await api.getCandleData(ticker, { + interval: '1m', + count: 10000 // ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ํ•œ๋ฒˆ์— ์š”์ฒญ + }); + + // ๋ฉ”์ธ ์Šค๋ ˆ๋“œ ๋ธ”๋กœํ‚น ๋ฐœ์ƒ + chart.series.data.setAll(response.data); +}; +``` + +--- + +## 3๋‹จ๊ณ„: AmCharts ํŽ˜์ด์ง€๋„ค์ด์…˜ ์‹œ๋„์™€ ํ•œ๊ณ„ (2025๋…„ 6์›”) + +### ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ตฌํ˜„ ์‹œ๋„ + +AmCharts์—์„œ ์„ฑ๋Šฅ ๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•˜๊ธฐ ์œ„ํ•ด ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ธฐ๋Šฅ ๊ตฌํ˜„์„ ์‹œ๋„ํ–ˆ์Šต๋‹ˆ๋‹ค. + +**๊ธฐ๋Œ€ํ–ˆ๋˜ ๋ฐฉ์‹:** +- ์ดˆ๊ธฐ์—๋Š” ์ตœ๊ทผ ๋ฐ์ดํ„ฐ๋งŒ ๋กœ๋“œ +- ์‚ฌ์šฉ์ž๊ฐ€ ์Šคํฌ๋กคํ•˜๋ฉด ์ถ”๊ฐ€ ๋ฐ์ดํ„ฐ ๋กœ๋“œ +- ์ ์ง„์  ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์œผ๋กœ ์ดˆ๊ธฐ ๋ Œ๋”๋ง ์‹œ๊ฐ„ ๋‹จ์ถ• + +### AmCharts์˜ ๊ตฌ์กฐ์  ํ•œ๊ณ„ + +**๋ฐœ๊ฒฌํ•œ ๋ฌธ์ œ์ :** +1. **AmCharts ๊ธฐ๋ณธ ๋™์ž‘**: ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฅผ ํ•œ๋ฒˆ์— ์ฒ˜๋ฆฌํ•˜๋Š” ๊ฒƒ์ด ๊ธฐ๋ณธ +2. **API ์ œ์•ฝ**: ๋ถ€๋ถ„์  ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ์„ ์œ„ํ•œ ๊ณต์‹ API ๋ถ€์žฌ +3. **๋ฉ”๋ชจ๋ฆฌ ๊ด€๋ฆฌ**: ๊ธฐ์กด ๋ฐ์ดํ„ฐ๋ฅผ ์œ ์ง€ํ•˜๋ฉด์„œ ์ƒˆ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ ์‹œ ๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ๊ธ‰์ฆ + +**Git ํžˆ์Šคํ† ๋ฆฌ:** +- `c26921e` (6์›” 18์ผ): ์ฐจํŠธ ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ž‘์—… ์ค‘ +- `6e4883e` (6์›” 20์ผ): ์ฐจํŠธ ๋ฌดํ•œ์Šคํฌ๋กค ๊ธฐ๋Šฅ ์ถ”๊ฐ€ ์‹œ๋„ +- `2cdd50d` (6์›” 24์ผ): ์ฐจํŠธ ๋ฌดํ•œ์Šคํฌ๋กค ์—๋Ÿฌ ๋ฐœ์ƒ + +### ๋Œ€์•ˆ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ฒ€ํ†  + +**๊ฐœ๋ฐœ ๊ธฐ๊ฐ„ ์••๋ฐ• ์ƒํ™ฉ** +- ๊ฐœ๋ฐœ ๋งˆ๊ฐ๊นŒ์ง€ ์–ผ๋งˆ ๋‚จ์ง€ ์•Š์€ ์ƒํ™ฉ +- ๋น ๋ฅธ ์˜์‚ฌ๊ฒฐ์ •์ด ํ•„์š”ํ•œ ์‹œ์  + +**TradingView Lightweight Charts ๋ฐœ๊ฒฌ** +- **ํ† ์Šค, ์—…๋น„ํŠธ ๋“ฑ ์ฃผ์š” ํ•€ํ…Œํฌ ๊ธฐ์—…**์—์„œ ์‚ฌ์šฉ ์ค‘ +- **๊ณต์‹ ๋ฌธ์„œ์— ํŽ˜์ด์ง€๋„ค์ด์…˜ ๊ธฐ๋Šฅ ๋ช…์‹œ** +- **ํ•˜์ง€๋งŒ ์—ฌ์ „ํžˆ ๋ช…๋ นํ˜• JavaScript API** (AmCharts์™€ ๋™์ผํ•œ ๋ฌธ์ œ) +- **๋‹คํ–‰ํžˆ ๊ณต์‹ ๋ฌธ์„œ์— React ๋ž˜ํ•‘ ๊ฐ€์ด๋“œ ์ œ๊ณต** (์„ ์–ธ์  ๊ตฌ์กฐ ๋ณ€ํ™˜ ์ฐธ๊ณ ์ž๋ฃŒ) + +--- + +## 4๋‹จ๊ณ„: TradingView Lightweight Charts๋กœ ์ „ํ™˜ (2025๋…„ 7์›”) + +### ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ฒฐ์ • + +**๊ฒฐ์ •์  ์š”์ธ๋“ค:** +1. **๊ณต์‹ ํŽ˜์ด์ง€๋„ค์ด์…˜ ์ง€์›**: ๋ฌธ์„œ์— ๋ช…์‹œ๋œ ๋ฌดํ•œ์Šคํฌ๋กค ๊ตฌํ˜„ ๋ฐฉ๋ฒ• +2. **์—…๊ณ„ ๊ฒ€์ฆ**: ์ฃผ์š” ๊ธˆ์œต ์„œ๋น„์Šค์—์„œ ์‹ค์ œ ์‚ฌ์šฉ ์ค‘ +3. **๊ฒฝ๋Ÿ‰ํ™”**: ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋งŒ ์„ ํƒ์  ๋กœ๋“œ ๊ฐ€๋Šฅ +4. **๊ธฐ์กด ์„ ์–ธ์  ๊ตฌ์กฐ ์žฌํ™œ์šฉ ๊ฐ€๋Šฅ**: AmCharts์šฉ์œผ๋กœ ๊ตฌ์ถ•ํ•œ ์ถ”์ƒํ™” ๊ณ„์ธต ํ™œ์šฉ + +### ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๊ณผ์ • + +**๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๊ต์ฒด:** +```json +// package.json ๋ณ€๊ฒฝ +{ + "dependencies": { + // "@amcharts/amcharts5": "^5.12.1", // ์ œ๊ฑฐ + "lightweight-charts": "^5.0.7", // ์ถ”๊ฐ€ + "recharts": "^3.0.2" // ํ˜ธ๊ฐ€์ฐฝ์šฉ ์ถ”๊ฐ€ + } +} +``` + +### TradingView๋„ ๋ช…๋ นํ˜• API์˜ ํ•œ๊ณ„ + +**๋™์ผํ•œ ๋ฌธ์ œ ๋ฐœ๊ฒฌ:** +TradingView Lightweight Charts๋„ AmCharts์™€ ๋งˆ์ฐฌ๊ฐ€์ง€๋กœ **์ˆœ์ˆ˜ JavaScript ๋ช…๋ นํ˜• API**๋ฅผ ์ œ๊ณตํ–ˆ์Šต๋‹ˆ๋‹ค. + +```javascript +// TradingView์˜ ๋ช…๋ นํ˜• API ์˜ˆ์‹œ +const chart = createChart(container, { + width: 400, + height: 300, + timeScale: { timeVisible: true } +}); + +const candlestickSeries = chart.addCandlestickSeries({ + upColor: '#26a69a', + downColor: '#ef5350', + borderVisible: false, + wickUpColor: '#26a69a', + wickDownColor: '#ef5350' +}); + +candlestickSeries.setData(data); +``` + +### ์„ ์–ธ์  ๊ตฌ์กฐ ์žฌ๊ตฌ์ถ• + +**๊ณต์‹ ๊ฐ€์ด๋“œ์™€ ๊ธฐ์กด ๊ฒฝํ—˜ ๊ฒฐํ•ฉ:** +TradingView ๊ณต์‹ ๋ฌธ์„œ์˜ React ๋ž˜ํ•‘ ๊ฐ€์ด๋“œ๋ฅผ ์ฐธ๊ณ ํ•˜๋ฉด์„œ, AmCharts์šฉ์œผ๋กœ ๊ตฌํ˜„ํ•œ ์„ ์–ธ์  ๊ตฌ์กฐ๋ฅผ TradingView์— ๋งž๊ฒŒ ์ˆ˜์ •ํ•˜์—ฌ ์žฌํ™œ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. + +```typescript +// TradingView๋ฅผ ์œ„ํ•œ ์„ ์–ธ์  ๊ตฌ์กฐ (๋‚ด๋ถ€ ๊ตฌํ˜„ ๋ณ€๊ฒฝ, ์ธํ„ฐํŽ˜์ด์Šค ์œ ์ง€) + + + + + + +``` + +**ํ•ต์‹ฌ ์ปดํฌ๋„ŒํŠธ ์žฌ๊ตฌํ˜„:** +```typescript +// ChartContainer ๋‚ด๋ถ€ - TradingView API ๋ž˜ํ•‘ (๊ณต์‹ ๊ฐ€์ด๋“œ ์ฐธ๊ณ ) +export default function ChartContainer({ children, chartOption, onChartReady }) { + const { root } = useChartRoot(); + + const chartApiRef = useRef({ + _instance: null, + getInstance() { + if (!this._instance) { + // ๊ณต์‹ ๋ฌธ์„œ์˜ React ํ†ตํ•ฉ ํŒจํ„ด ์ ์šฉ + this._instance = createChart(root, { + ...chartOption, + width: root.clientWidth, + height: root.clientHeight - INTERVAL_SELECTOR_HEIGHT, + }); + + // ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ์ •๋ฆฌ ํ•จ์ˆ˜๋„ ๊ณต์‹ ๊ฐ€์ด๋“œ ์ฐธ๊ณ  + this._instance.timeScale().fitContent(); + } + return this._instance; + } + }); + + // useLayoutEffect๋ฅผ ํ™œ์šฉํ•œ ์ƒ๋ช…์ฃผ๊ธฐ ๊ด€๋ฆฌ (๊ณต์‹ ๊ถŒ์žฅ์‚ฌํ•ญ) + useLayoutEffect(() => { + const chart = chartApiRef.current.getInstance(); + + return () => { + chart.remove(); // ๊ณต์‹ ๋ฌธ์„œ์˜ ๋ฉ”๋ชจ๋ฆฌ ์ •๋ฆฌ ํŒจํ„ด + }; + }, []); +} +``` + +**๊ณต์‹ ๊ฐ€์ด๋“œ์˜ ๋„์›€:** +- React ์ƒ๋ช…์ฃผ๊ธฐ์™€์˜ ์˜ฌ๋ฐ”๋ฅธ ํ†ตํ•ฉ ๋ฐฉ๋ฒ• +- ๋ฉ”๋ชจ๋ฆฌ ๋ˆ„์ˆ˜ ๋ฐฉ์ง€๋ฅผ ์œ„ํ•œ ์ •๋ฆฌ(cleanup) ํŒจํ„ด +- ๋ฆฌ์‚ฌ์ด์ง• ์ด๋ฒคํŠธ ์ฒ˜๋ฆฌ ๋ฐฉ๋ฒ• + +**Git ํžˆ์Šคํ† ๋ฆฌ:** +- `f8c3eec` (7์›” 1์ผ): Charts ์ปดํฌ๋„ŒํŠธ๋ฅผ shared ๋””๋ ‰ํ† ๋ฆฌ๋กœ ์ด๋™ +- `d2aa4ab` (7์›” 1์ผ): Recharts ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ถ”๊ฐ€ +- `7fec092` (7์›” 1์ผ): ํ˜ธ๊ฐ€์ฐฝ ์ปดํฌ๋„ŒํŠธ AmCharts โ†’ Recharts๋กœ ๊ต์ฒด + +### ๋ฌดํ•œ์Šคํฌ๋กค ๊ตฌํ˜„ + +**ํ•ต์‹ฌ ๊ตฌํ˜„:** +```typescript +// ๋ฌดํ•œ์Šคํฌ๋กค ํ•ธ๋“ค๋Ÿฌ +const handleLogicalRangeChange: LogicalRangeChangeEventHandler = useCallback( + (logicalRange) => { + if (!logicalRange) return; + + const barsInRange = logicalRange.to - logicalRange.from; + + // ์™ผ์ชฝ ๋์— ๋„๋‹ฌํ–ˆ์„ ๋•Œ ๊ณผ๊ฑฐ ๋ฐ์ดํ„ฐ ๋กœ๋“œ + if (logicalRange.from < 0 && Math.abs(logicalRange.from) > barsInRange / 4) { + loadMorePastData(); + } + }, + [loadMorePastData] +); + +// ์ ์ง„์  ๋ฐ์ดํ„ฐ ๋กœ๋”ฉ +const loadMorePastData = useCallback(async () => { + if (isLoading || !prevRequestDate.current) return; + + const moreData = await api.getPastCandleData({ + ticker, + interval: selectedInterval, + endTime: prevRequestDate.current, + count: 30 + }); + + // ๊ธฐ์กด ๋ฐ์ดํ„ฐ ์•ž์— ์ƒˆ ๋ฐ์ดํ„ฐ ์ถ”๊ฐ€ + const combinedData = [...moreData, ...existingData]; + seriesRef.current?.setData(combinedData); +}, [ticker, selectedInterval, isLoading]); +``` + +**Git ํžˆ์Šคํ† ๋ฆฌ:** +- `055193f` (6์›” 24์ผ): Chart ๋ฌดํ•œ ์Šคํฌ๋กค ์ค‘๋ณต๋ฐ์ดํ„ฐ ํŒจ์นญ ๋ฌธ์ œ ์ˆ˜์ • + +--- + +## 5๋‹จ๊ณ„: ๊ธฐ๋Šฅ ๊ตฌํ˜„๊ณผ ํŠธ๋ ˆ์ด๋“œ์˜คํ”„ (2025๋…„ 7์›”) + +### ์„ฑ๊ณต์ ์œผ๋กœ ๊ตฌํ˜„ํ•œ ๊ธฐ๋Šฅ + +**1. ๋ฌดํ•œ์Šคํฌ๋กค** +- ์ดˆ๊ธฐ ๋ Œ๋”๋ง ์‹œ๊ฐ„ ๋Œ€ํญ ๋‹จ์ถ• +- ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„  +- ๋ฉ”๋ชจ๋ฆฌ ํšจ์œจ์„ฑ ํ–ฅ์ƒ + +**2. ์ธํ„ฐ๋ฒŒ ์„ ํƒ** +```typescript +// ์ง€์›ํ•˜๋Š” ์‹œ๊ฐ„ ๊ฐ„๊ฒฉ +const INTERVALS = [ + { label: '1๋ถ„', value: 1 }, + { label: '5๋ถ„', value: 5 }, + { label: '15๋ถ„', value: 15 }, + { label: '1์‹œ๊ฐ„', value: 60 }, + { label: '1์ผ', value: 1440 } +] as const; +``` + +**3. ์ปค์Šคํ…€ ํˆดํŒ** +```typescript +// ์ปค์Šคํ…€ ํˆดํŒ ๊ตฌํ˜„ + { + const { time, open, high, low, close, volume } = data; + return ( +
+

์‹œ๊ฐ„: {formatTime(time)}

+

์‹œ๊ฐ€: {formatPrice(open)}

+

๊ณ ๊ฐ€: {formatPrice(high)}

+

์ €๊ฐ€: {formatPrice(low)}

+

์ข…๊ฐ€: {formatPrice(close)}

+

๊ฑฐ๋ž˜๋Ÿ‰: {formatVolume(volume)}

+
+ ); + }} +/> +``` + +### ๊ตฌํ˜„ํ•˜์ง€ ๋ชปํ•œ ๊ธฐ๋Šฅ + +**๊ธฐ์ˆ ์ง€ํ‘œ (Technical Indicators)** +- AmCharts์—์„œ๋Š” ๋‹ค์–‘ํ•œ ๊ธฐ์ˆ ์ง€ํ‘œ๋ฅผ ๊ธฐ๋ณธ ์ œ๊ณต +- TradingView Lightweight Charts๋Š” ๊ธฐ๋ณธ ์ง€์›ํ•˜์ง€ ์•Š์Œ +- ๊ฐœ๋ฐœ ์ผ์ •์ƒ ์ปค์Šคํ…€ ๊ตฌํ˜„ํ•˜์ง€ ๋ชปํ•จ + +**์˜ˆ์‹œ - ๊ตฌํ˜„ํ•˜์ง€ ๋ชปํ•œ ๊ธฐ์ˆ ์ง€ํ‘œ๋“ค:** +- ์ด๋™ํ‰๊ท ์„  (Moving Average) +- RSI (Relative Strength Index) +- MACD (Moving Average Convergence Divergence) +- ๋ณผ๋ฆฐ์ € ๋ฐด๋“œ (Bollinger Bands) + +--- + +## ์„ฑ๊ณผ ๋ฐ ๊ฒฐ๊ณผ + +### ์„ฑ๋Šฅ ๊ฐœ์„  ๊ฒฐ๊ณผ + +**Web Vitals ์ ์ˆ˜ ๊ฐœ์„ :** +- **๊ฐœ์„  ์ „**: 40-50์  (๋ฐฐํฌ ํ™˜๊ฒฝ) +- **๊ฐœ์„  ํ›„**: 80์  ์ด์ƒ (๋ฐฐํฌ ํ™˜๊ฒฝ) + +### ์‚ฌ์šฉ์ž ๊ฒฝํ—˜ ๊ฐœ์„  + +**1. ์ดˆ๊ธฐ ๋กœ๋”ฉ ์‹œ๊ฐ„ ๋‹จ์ถ•** +- ๋Œ€์šฉ๋Ÿ‰ ๋ฐ์ดํ„ฐ ์ผ๊ด„ ๋กœ๋“œ โ†’ ์ ์ง„์  ๋กœ๋“œ +- ์‚ฌ์šฉ์ž๊ฐ€ ์ฆ‰์‹œ ์ฐจํŠธ ์ƒํ˜ธ์ž‘์šฉ ๊ฐ€๋Šฅ + +**2. ๋ถ€๋“œ๋Ÿฌ์šด ์Šคํฌ๋กค ๊ฒฝํ—˜** +- ๋ฌดํ•œ์Šคํฌ๋กค๋กœ ๊ณผ๊ฑฐ ๋ฐ์ดํ„ฐ ํƒ์ƒ‰ + +**3. ๋ฐ˜์‘ํ˜• ์ธํ„ฐํŽ˜์ด์Šค** +- ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์—…๋ฐ์ดํŠธ + +### ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ + +**1. ์„ ์–ธ์  ๊ตฌ์กฐ ์žฌํ™œ์šฉ** +- ๊ธฐ์กด AmCharts์šฉ ์ปดํฌ๋„ŒํŠธ ๊ตฌ์กฐ๋ฅผ ๊ทธ๋Œ€๋กœ ํ™œ์šฉ +- ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ์‹œ๊ฐ„ ๋‹จ์ถ• + +**2. ์„ ์–ธ์  ๊ตฌ์กฐ์˜ ๊ฐ€์น˜ ์ž…์ฆ** +- ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ๋ณ€๊ฒฝ์—๋„ ๋ถˆ๊ตฌํ•˜๊ณ  ์ปดํฌ๋„ŒํŠธ ์ธํ„ฐํŽ˜์ด์Šค ์œ ์ง€ +- ๋‚ด๋ถ€ ๊ตฌํ˜„๋งŒ ๋ณ€๊ฒฝํ•˜์—ฌ ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋น„์šฉ ์ตœ์†Œํ™” +- React ์ƒ๋ช…์ฃผ๊ธฐ์™€ ์ž์—ฐ์Šค๋Ÿฌ์šด ํ†ตํ•ฉ + +--- + +## ํšŒ๊ณ  ๋ฐ ๊ตํ›ˆ + +### ๊ธฐ์ˆ ์  ๊ตํ›ˆ + +**1. ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ ํƒ ์‹œ ๊ณ ๋ ค์‚ฌํ•ญ** +- **์„ฑ๋Šฅ๋งŒํผ ์ค‘์š”ํ•œ ๊ฒƒ์€ ์ƒํƒœ๊ณ„ ์ ํ•ฉ์„ฑ** +- Canvas ๊ธฐ๋ฐ˜์ด๋ผ๊ณ  ๋ฌด์กฐ๊ฑด ๋น ๋ฅธ ๊ฒƒ์€ ์•„๋‹˜ +- **์‹ค์ œ ์‚ฌ์šฉ ํ™˜๊ฒฝ์—์„œ์˜ ํ…Œ์ŠคํŠธ**๊ฐ€ ์ค‘์š” + +**2. ์กฐ๊ธฐ ์„ฑ๋Šฅ ํ…Œ์ŠคํŠธ์˜ ์ค‘์š”์„ฑ** +- ๊ฐœ๋ฐœ ํ™˜๊ฒฝ๊ณผ ๋ฐฐํฌ ํ™˜๊ฒฝ์˜ ์ฐจ์ด๋ฅผ ๊ฐ„๊ณผํ–ˆ์Œ +- **์‹ค์ œ ๋ฐ์ดํ„ฐ ๊ทœ๋ชจ**๋กœ ํ…Œ์ŠคํŠธํ•ด์•ผ ํ•จ +- Lighthouse ์ ์ˆ˜๋ฅผ ๊ฐœ๋ฐœ ์ดˆ๊ธฐ๋ถ€ํ„ฐ ๋ชจ๋‹ˆํ„ฐ๋ง + +**3. ์„ ์–ธ์  ์ถ”์ƒํ™”์˜ ๊ฐ€์น˜** +- **๋‘ ์ฐจ๋ก€์˜ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ „ํ™˜** ๋ชจ๋‘์—์„œ ๋ช…๋ นํ˜• โ†’ ์„ ์–ธ์  ๋ณ€ํ™˜ ํ•„์š” +- AmCharts ์ „ํ™˜ ์‹œ: ๊ณต์‹ ๊ฐ€์ด๋“œ ์—†์ด ์ง์ ‘ ๊ตฌํ˜„์œผ๋กœ ๋งŽ์€ ์‹œํ–‰์ฐฉ์˜ค +- TradingView ์ „ํ™˜ ์‹œ: **๊ณต์‹ React ๊ฐ€์ด๋“œ + ๊ธฐ์กด ๊ฒฝํ—˜**์œผ๋กœ ๋น ๋ฅธ ๊ตฌํ˜„ +- **์ถ”์ƒํ™”์— ํˆฌ์žํ•œ ์‹œ๊ฐ„์ด ๋งˆ์ด๊ทธ๋ ˆ์ด์…˜ ๋น„์šฉ์„ ํฌ๊ฒŒ ์ ˆ์•ฝ** + +### ํ”„๋กœ์ ํŠธ ๊ด€๋ฆฌ ๊ตํ›ˆ + +**1. ๊ธฐ์ˆ  ๋ถ€์ฑ„ vs ๊ธฐ๋Šฅ ์™„์„ฑ๋„** +- AmCharts์˜ ๋‹ค์–‘ํ•œ ๊ธฐ๋Šฅ vs ์„ฑ๋Šฅ ๋ฌธ์ œ +- TradingView์˜ ์„ฑ๋Šฅ vs ๊ธฐ๋Šฅ ์ œ์•ฝ +- **ํ”„๋กœ์ ํŠธ ์šฐ์„ ์ˆœ์œ„์— ๋”ฐ๋ฅธ ์„ ํƒ** + +**2. ์ถ”์ƒํ™” ๊ณ„์ธต์˜ ์ „๋žต์  ๊ฐ€์น˜** +- AmCharts์šฉ์œผ๋กœ ๊ตฌ์ถ•ํ•œ ์„ ์–ธ์  ๊ตฌ์กฐ๊ฐ€ TradingView ์ „ํ™˜ ์‹œ **ํ•ต์‹ฌ ์ž์‚ฐ**์ด ๋จ + +**3. ์—…๊ณ„ ๋ฒค์น˜๋งˆํฌ์˜ ๊ฐ€์น˜** +- ํ† ์Šค, ์—…๋น„ํŠธ ๋“ฑ์—์„œ ์‚ฌ์šฉํ•˜๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ฐธ๊ณ  +- **๊ฒ€์ฆ๋œ ์†”๋ฃจ์…˜**์˜ ์•ˆ์ •์„ฑ + +### ์•„์‰ฌ์šด ์  + +**1. ๊ธฐ์ˆ ์ง€ํ‘œ ๋ฏธ๊ตฌํ˜„** +- AmCharts์—์„œ ๊ธฐ๋ณธ ์ œ๊ณต๋˜๋˜ ๊ธฐ๋Šฅ +- ์‚ฌ์šฉ์ž ์š”๊ตฌ์‚ฌํ•ญ ์ค‘ ์ผ๋ถ€ ๋ฏธ์ถฉ์กฑ +- ํ–ฅํ›„ ๊ฐœ๋ฐœ ๊ณผ์ œ๋กœ ๋‚จ์Œ + +**2. ์ดˆ๊ธฐ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์„ ํƒ** +- ๋” ์‹ ์ค‘ํ•œ ๊ฒ€ํ† ๊ฐ€ ํ•„์š”ํ–ˆ์Œ +- POC ๋‹จ๊ณ„์—์„œ ์‹ค์ œ ๋ฐ์ดํ„ฐ ๊ทœ๋ชจ๋กœ ํ…Œ์ŠคํŠธ ๋ถ€์กฑ + +--- + +## ๊ฒฐ๋ก  + +CleanEngine ํ”„๋กœ์ ํŠธ์˜ ์ฐจํŠธ ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ ์ „ํ™˜์€ **์„ฑ๋Šฅ ์ค‘์‹ฌ์˜ ์˜์‚ฌ๊ฒฐ์ •**์ด์—ˆ์Šต๋‹ˆ๋‹ค. AmCharts์˜ ํ’๋ถ€ํ•œ ๊ธฐ๋Šฅ์„ ํฌ๊ธฐํ•˜๊ณ  TradingView Lightweight Charts์˜ ์„ฑ๋Šฅ๊ณผ ๋ฌดํ•œ์Šคํฌ๋กค ๊ธฐ๋Šฅ์„ ์„ ํƒํ•œ ๊ฒƒ์€ **์‚ฌ์šฉ์ž ๊ฒฝํ—˜์„ ์šฐ์„ ์‹œํ•œ ๊ฒฐ์ •**์ด์—ˆ์Šต๋‹ˆ๋‹ค. + +**ํ•ต์‹ฌ ์„ฑ๊ณผ:** +- Web Vitals ์ ์ˆ˜ **1.8๋ฐฐ ์ด์ƒ ๊ฐœ์„ ** (40-50์  โ†’ 80์ +) +- ์ดˆ๊ธฐ ๋กœ๋”ฉ ์‹œ๊ฐ„ **60% ๋‹จ์ถ•** +- ๋ฌดํ•œ์Šคํฌ๋กค์„ ํ†ตํ•œ **์›ํ™œํ•œ ๋ฐ์ดํ„ฐ ํƒ์ƒ‰** ๊ฒฝํ—˜ ์ œ๊ณต + +๋น„๋ก ์ผ๋ถ€ ๊ณ ๊ธ‰ ๊ธฐ๋Šฅ์„ ์žƒ์—ˆ์ง€๋งŒ, **ํ•ต์‹ฌ ์‚ฌ์šฉ์ž ๊ฒฝํ—˜**์ธ ๋น ๋ฅธ ๋กœ๋”ฉ๊ณผ ๋ถ€๋“œ๋Ÿฌ์šด ์ธํ„ฐ๋ž™์…˜์„ ํ™•๋ณดํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” **์„ฑ๋Šฅ์ด ๊ธฐ๋Šฅ๋ณด๋‹ค ์ค‘์š”ํ•œ ์ƒํ™ฉ**์—์„œ์˜ ์˜ฌ๋ฐ”๋ฅธ ๊ธฐ์ˆ ์  ์„ ํƒ์ด์—ˆ๋‹ค๊ณ  ์ƒ๊ฐํ•ฉ๋‹ˆ๋‹ค. + diff --git "a/docs/[\352\260\234\353\260\234\355\233\204\352\270\260]CI-CD.md" "b/docs/[\352\260\234\353\260\234\355\233\204\352\270\260]CI-CD.md" new file mode 100644 index 0000000..d45e0dc --- /dev/null +++ "b/docs/[\352\260\234\353\260\234\355\233\204\352\270\260]CI-CD.md" @@ -0,0 +1,298 @@ +# CI/CD ๊ฐœ๋ฐœ์ผ์ง€ + +- **cleanengine-fe**: React Router V7 ๊ธฐ๋ฐ˜ ํ”„๋ก ํŠธ์—”๋“œ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ +- **cleanengine-reverseproxy**: Nginx ๊ธฐ๋ฐ˜ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ ์„œ๋ฒ„ +- **๋„๋ฉ”์ธ**: investfuture.my +- **์„œ๋น„์Šค**: ์‹ค์‹œ๊ฐ„ ๊ฑฐ๋ž˜์†Œ ์ถ”์ข… ๋ชจ์˜ํˆฌ์ž ์‹œ๋ฎฌ๋ ˆ์ดํ„ฐ + +## ๋ฐฐํฌ ์šฐ์„  ๊ฐœ๋ฐœ ์ „๋žต + +CleanEngine ํŒ€์€ **"๋ฐฐํฌ ํ™˜๊ฒฝ ๋จผ์ €, ๊ฐœ๋ฐœ์€ ๋‚˜์ค‘์—"** ๋ผ๋Š” ์ „๋žต์„ ์ฑ„ํƒํ–ˆ์Šต๋‹ˆ๋‹ค. ์ด๋Š” ๋‹ค์Œ๊ณผ ๊ฐ™์€ ์ด์œ  ๋•Œ๋ฌธ์ด์—ˆ์Šต๋‹ˆ๋‹ค: + +### ์™œ ๋ฐฐํฌ๋ฅผ ๋จผ์ € ๊ตฌ์ถ•ํ–ˆ๋Š”๊ฐ€? + +**1. ํŒ€ ๋™๊ธฐํ™” (Team Alignment)** +- ๋ชจ๋“  ํŒ€์›์ด **๋™์ผํ•œ ํ™˜๊ฒฝ**์„ ๋ฐ”๋ผ๋ณด๋ฉฐ ๊ฐœ๋ฐœํ•  ์ˆ˜ ์žˆ์Œ +- ๊ฐ์ž ๋กœ์ปฌ์—์„œ ์ž‘์—…ํ•œ ๋‚ด์šฉ์ด **์‹ค์ œ ์šด์˜ ํ™˜๊ฒฝ**์—์„œ ์–ด๋–ป๊ฒŒ ๋™์ž‘ํ•˜๋Š”์ง€ ์ฆ‰์‹œ ํ™•์ธ ๊ฐ€๋Šฅ +- ํŒ€ ์ „์ฒด๊ฐ€ **๊ฐ™์€ ๋ชฉํ‘œ์™€ ์ง„ํ–‰ ์ƒํ™ฉ**์„ ๊ณต์œ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ์ค€์  ์ œ๊ณต + +**2. ์ง€์†์  ํ”ผ๋“œ๋ฐฑ ๋ฃจํ”„ (Continuous Feedback Loop)** +- **CI/CD ํŒŒ์ดํ”„๋ผ์ธ**์ด ๊ตฌ์ถ•๋˜์–ด ์žˆ์–ด์•ผ ๊ฐœ๋ฐœ ๊ฒฐ๊ณผ๋ฌผ์„ ์ฆ‰์‹œ ๋ฐฐํฌํ•˜๊ณ  ํ…Œ์ŠคํŠธ ๊ฐ€๋Šฅ +- ๊ธฐ๋Šฅ ๊ฐœ๋ฐœ ํ›„ **์ฆ‰์‹œ ์‹ค์ œ ํ™˜๊ฒฝ์—์„œ ๊ฒ€์ฆ**ํ•  ์ˆ˜ ์žˆ์–ด ๋น ๋ฅธ ํ”ผ๋“œ๋ฐฑ ์ˆ˜์ง‘ +- ๋ฒ„๊ทธ๋‚˜ ๋ฌธ์ œ์ ์„ **์กฐ๊ธฐ์— ๋ฐœ๊ฒฌ**ํ•˜๊ณ  ์ˆ˜์ •ํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ ๊ตฌ์„ฑ + +**3. ๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ ํ–ฅ์ƒ** +- ๋กœ์ปฌ ํ™˜๊ฒฝ๊ณผ ์šด์˜ ํ™˜๊ฒฝ์˜ **์ฐจ์ด๋กœ ์ธํ•œ ๋ฌธ์ œ ์ตœ์†Œํ™”** +- ํŒ€์› ๊ฐ„ **"๋‚ด ์ปดํ“จํ„ฐ์—์„œ๋Š” ์ž˜ ๋Œ์•„๊ฐ€๋Š”๋ฐ"** ๋ผ๋Š” ๋ฌธ์ œ ์ƒํ™ฉ ๋ฐฉ์ง€ +- **ํ‘œ์ค€ํ™”๋œ ๋ฐฐํฌ ํ”„๋กœ์„ธ์Šค**๋กœ ๊ฐœ๋ฐœ์—๋งŒ ์ง‘์ค‘ํ•  ์ˆ˜ ์žˆ๋Š” ํ™˜๊ฒฝ ์กฐ์„ฑ + +**4. ํ”„๋กœ์ ํŠธ ์ง„ํ–‰ ์ƒํ™ฉ ํˆฌ๋ช…์„ฑ** +- ์–ธ์ œ๋“ ์ง€ **์‹ค์ œ ๋™์ž‘ํ•˜๋Š” ์„œ๋น„์Šค**๋ฅผ ํ†ตํ•ด ํ”„๋กœ์ ํŠธ ํ˜„ํ™ฉ ํ™•์ธ ๊ฐ€๋Šฅ +- ํด๋ผ์ด์–ธํŠธ๋‚˜ ์ดํ•ด๊ด€๊ณ„์ž์—๊ฒŒ **๊ตฌ์ฒด์ ์ธ ๊ฒฐ๊ณผ๋ฌผ** ์‹œ์—ฐ ๊ฐ€๋Šฅ +- ๊ฐœ๋ฐœ ์ค‘์ธ ๊ธฐ๋Šฅ๋“ค์ด **์‹ค์ œ ์‚ฌ์šฉ์ž ํ™˜๊ฒฝ**์—์„œ ์–ด๋–ป๊ฒŒ ์ž‘๋™ํ•˜๋Š”์ง€ ์‹ค์‹œ๊ฐ„ ๊ฒ€์ฆ + +์ด๋Ÿฌํ•œ ๋ฐฐํฌ ์šฐ์„  ์ „๋žต ๋•๋ถ„์— CleanEngine ํŒ€์€ ๊ฐœ๋ฐœ ์ดˆ๊ธฐ๋ถ€ํ„ฐ ์•ˆ์ •์ ์ด๊ณ  ํ™•์žฅ ๊ฐ€๋Šฅํ•œ ์ธํ”„๋ผ ์œ„์—์„œ ๊ฐœ๋ฐœ์„ ์ง„ํ–‰ํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. + +--- + +## ๋ฐฐํฌ ์•„ํ‚คํ…์ฒ˜ ์ง„ํ™” ๊ณผ์ • + +### 1๋‹จ๊ณ„: ๋‹จ์ผ EC2 ๊ธฐ๋ฐ˜ ๋ฉ€ํ‹ฐ ์ปจํ…Œ์ด๋„ˆ ๋ฐฐํฌ (2025๋…„ 4์›”-5์›”) + +**๋ฐฐ๊ฒฝ:** +- ์ฒ˜์Œ๋ถ€ํ„ฐ HTTPS ์ ์šฉ์„ ๋ชฉํ‘œ๋กœ ํ•œ ๋ณด์•ˆ ์ค‘์‹ฌ ์„ค๊ณ„ +- HttpOnly, Secure ์ฟ ํ‚ค ์˜ต์…˜ ์‚ฌ์šฉ์œผ๋กœ ์ธํ•œ SSL/TLS ํ•„์ˆ˜ +- Docker Hub ๋ฌด๋ฃŒ ํ‹ฐ์–ด ํ™œ์šฉํ•œ ์ด๋ฏธ์ง€ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ + +**์ดˆ๊ธฐ ์•„ํ‚คํ…์ฒ˜:** +``` +[GitHub Actions] โ†’ [Docker Hub] โ†’ [๋‹จ์ผ EC2 ์ธ์Šคํ„ด์Šค] + โ†“ + [Docker Compose] + โ”œโ”€โ”€ Nginx Reverse Proxy (80,443) + โ”œโ”€โ”€ React Frontend (3000) + โ”œโ”€โ”€ Spring Boot Backend (8080) + โ”œโ”€โ”€ AI Server (8000) + โ””โ”€โ”€ MariaDB (3306) +``` + +**์ฃผ์š” ํŠน์ง•:** +- ์ฒ˜์Œ๋ถ€ํ„ฐ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ ๊ตฌ์„ฑ์œผ๋กœ HTTPS ์ง€์› +- Docker Compose๋ฅผ ํ†ตํ•œ ๋ฉ€ํ‹ฐ ์ปจํ…Œ์ด๋„ˆ ๊ด€๋ฆฌ +- Let's Encrypt SSL ์ธ์ฆ์„œ ์ž๋™ ๊ฐฑ์‹  +- GitHub Actions ๊ธฐ๋ฐ˜ CI/CD ํŒŒ์ดํ”„๋ผ์ธ + +**ํ•œ๊ณ„์ :** +- ๋‹จ์ผ EC2์—์„œ ๋ชจ๋“  ์„œ๋น„์Šค ์‹คํ–‰์œผ๋กœ ์ธํ•œ ์„ฑ๋Šฅ ๋ณ‘๋ชฉ +- ์—ฌ๋Ÿฌ ๊ฑฐ๋ž˜์†Œ API ์—ฐ๋™์œผ๋กœ ์ธํ•œ ๋ฐฑ์—”๋“œ ์„œ๋ฒ„ ๋ถ€ํ•˜ +- AI ์„œ๋ฒ„ ๋ฆฌ์†Œ์Šค ์‚ฌ์šฉ๋Ÿ‰์œผ๋กœ ์ธํ•œ ์ „์ฒด ์‹œ์Šคํ…œ ์„ฑ๋Šฅ ์ €ํ•˜ + +### 2๋‹จ๊ณ„: ์„œ๋น„์Šค๋ณ„ EC2 ๋ถ„๋ฆฌ - ์„ฑ๋Šฅ ์ตœ์ ํ™” (2025๋…„ 6์›”-7์›”) + +**๋ถ„๋ฆฌ ๋ฐฐ๊ฒฝ:** +- ๋ฐฑ์—”๋“œ์—์„œ ๋‹ค์ค‘ ๊ฑฐ๋ž˜์†Œ ์—ฐ๋™ (์—…๋น„ํŠธ, ๋น—์ธ ๋“ฑ) ์ฒ˜๋ฆฌ ๋ถ€ํ•˜ ์ฆ๊ฐ€ +- ๋‹ค์ค‘ ์ฝ”์ธ ์ง€์›์œผ๋กœ ์ธํ•œ ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ๋Ÿ‰ ๊ธ‰์ฆ +- AI ์„œ๋ฒ„์˜ ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ ์‹คํ–‰์œผ๋กœ ์ธํ•œ CPU/๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰ ์ฆ๊ฐ€ + +**๋ถ„์‚ฐ ์•„ํ‚คํ…์ฒ˜:** +``` +[์ธํ„ฐ๋„ท] โ†’ [๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ EC2] + โ”œโ”€โ”€ / โ†’ [ํ”„๋ก ํŠธ์—”๋“œ EC2] + โ”œโ”€โ”€ /api/ โ†’ [๋ฐฑ์—”๋“œ EC2] โ† [MariaDB EC2] + โ””โ”€โ”€ /ai/ โ†’ [AI ์„œ๋ฒ„ EC2] +``` + +**EC2 ์ธ์Šคํ„ด์Šค ๊ตฌ์„ฑ:** +1. **๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ EC2**: Nginx ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ ์ „์šฉ + - t3.small (2 vCPU, 2GB RAM) + - SSL ์ข…๋ฃŒ, ๋ผ์šฐํŒ…, ์ •์  ํŒŒ์ผ ์บ์‹ฑ + - ๋ชจ๋“  ์™ธ๋ถ€ ํŠธ๋ž˜ํ”ฝ์˜ ์ง„์ž…์  + +2. **ํ”„๋ก ํŠธ์—”๋“œ EC2**: React ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์ „์šฉ + - t3.small (2 vCPU, 2GB RAM) + - React Router V7 SSR/SPA ํ•˜์ด๋ธŒ๋ฆฌ๋“œ + +3. **๋ฐฑ์—”๋“œ EC2**: Spring Boot API ์„œ๋ฒ„ + - t3.medium (2 vCPU, 4GB RAM) + - ๋‹ค์ค‘ ๊ฑฐ๋ž˜์†Œ API ์—ฐ๋™, ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ฒ˜๋ฆฌ + - MariaDB์™€ Private IP๋กœ ํ†ต์‹  + +4. **AI ์„œ๋ฒ„ EC2**: ๋จธ์‹ ๋Ÿฌ๋‹ ๋ชจ๋ธ ์„œ๋ฒ„ + - t3.large (2 vCPU, 8GB RAM) + - ํˆฌ์ž ์ถ”์ฒœ ์•Œ๊ณ ๋ฆฌ์ฆ˜, ์‹œ์žฅ ๋ถ„์„ ๋ชจ๋ธ + - ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ + +5. **MariaDB EC2**: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์„œ๋ฒ„ + - t3.small (2 vCPU, 2GB RAM) + - Private IP ๊ธฐ๋ฐ˜ ์ ‘๊ทผ (10.0.0.x:3306) + - ๋ฐฑ์—”๋“œ ์„œ๋ฒ„๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅ + - ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ์ง์ ‘ ์ ‘๊ทผ ๋ถˆ๊ฐ€ + +**๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ ๋ผ์šฐํŒ… ์„ค์ •:** +```nginx +# cleanengine-reverseproxy/nginx.conf +upstream react_app { + server 10.0.0.4:3000; # ํ”„๋ก ํŠธ์—”๋“œ EC2 private IP +} + +upstream api-server { + server 10.0.0.138:8080; # ๋ฐฑ์—”๋“œ EC2 private IP +} + +upstream ai-server { + server 10.0.0.12:8000; # AI ์„œ๋ฒ„ EC2 private IP +} + +server { + listen 443 ssl; + server_name investfuture.my; + + # ํ”„๋ก ํŠธ์—”๋“œ๋กœ ๋ผ์šฐํŒ… + location / { + proxy_pass http://react_app/; + } + + # ๋ฐฑ์—”๋“œ API๋กœ ๋ผ์šฐํŒ… + location /api/ { + proxy_pass http://api-server/api/; + } + + # AI ์„œ๋ฒ„๋กœ ๋ผ์šฐํŒ… + location /ai/ { + proxy_pass http://ai-server/; + } +} +``` + +**ํŠธ๋ž˜ํ”ฝ ํ๋ฆ„:** +- ํด๋ผ์ด์–ธํŠธ โ†’ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ โ†’ ํ”„๋ก ํŠธ์—”๋“œ (์ผ๋ฐ˜ ํŽ˜์ด์ง€) +- ํด๋ผ์ด์–ธํŠธ โ†’ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ โ†’ ๋ฐฑ์—”๋“œ (๊ฑฐ๋ž˜ API) +- ํด๋ผ์ด์–ธํŠธ โ†’ ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ โ†’ AI ์„œ๋ฒ„ (AI ๋ถ„์„ API) +- ๋ฐฑ์—”๋“œ โ†’ MariaDB (Private IP ํ†ต์‹ , ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ฟผ๋ฆฌ) + +**๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ ‘๊ทผ ์ œ์–ด:** +- **๋ณด์•ˆ ์„ค๊ณ„**: ํ”„๋ก ํŠธ์—”๋“œ์—์„œ ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ง์ ‘ ์ ‘๊ทผ ์ฐจ๋‹จ +- **์ค‘์•™ ์ง‘์ค‘์‹ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ**: ๋ชจ๋“  ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ž‘์—…์€ ๋ฐฑ์—”๋“œ API๋ฅผ ํ†ตํ•ด์„œ๋งŒ ์ˆ˜ํ–‰ +- **Private Network**: MariaDB๋Š” Private IP๋กœ๋งŒ ์ ‘๊ทผ ๊ฐ€๋Šฅํ•˜์—ฌ ์™ธ๋ถ€ ๋…ธ์ถœ ๋ฐฉ์ง€ + +**์„ฑ๋Šฅ ๊ฐœ์„  ํšจ๊ณผ:** +- AI ์„œ๋ฒ„ ๋…๋ฆฝ ์‹คํ–‰์œผ๋กœ ์ „์ฒด ์‹œ์Šคํ…œ ์•ˆ์ •์„ฑ ํ–ฅ์ƒ +- ์„œ๋น„์Šค๋ณ„ ๋…๋ฆฝ ๋ฐฐํฌ ๋ฐ ์Šค์ผ€์ผ๋ง ๊ฐ€๋Šฅ + +### 3๋‹จ๊ณ„: ์„ฑ๋Šฅ ์ตœ์ ํ™” ๋ฐ ํ’ˆ์งˆ ๊ด€๋ฆฌ ๊ฐ•ํ™” (2025๋…„ 6์›”-8์›”) + +**์„ฑ๋Šฅ ์ตœ์ ํ™” ์„ค์ • ์ ์šฉ (6์›” 12์ผ):** +- **HTTP/3 ๋ฐ QUIC ํ”„๋กœํ† ์ฝœ**: ์—ฐ๊ฒฐ ์ง€์—ฐ์‹œ๊ฐ„ ์ตœ์†Œํ™” +- **Gzip ์••์ถ•**: ๋Œ€์—ญํญ ์‚ฌ์šฉ๋Ÿ‰ ์ตœ์ ํ™” +- **์ •์  ํŒŒ์ผ ์บ์‹ฑ**: Nginx ๊ธฐ๋ฐ˜ ์—์ง€ ์บ์‹ฑ +- **WebSocket ํ”„๋ก์‹œ**: ์‹ค์‹œ๊ฐ„ ๊ฑฐ๋ž˜ ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆฌ๋ฐ + +**์—์…‹ ๋กœ๋”ฉ์‹œ๊ฐ„ ๋‹จ์ถ•์„ ์œ„ํ•œ Nginx ์„ค์ •:** +```nginx +# HTTP/3 ๋ฐ ์„ฑ๋Šฅ ์ตœ์ ํ™” +listen 443 quic reuseport; +http3 on; +http2 on; +add_header Alt-Svc 'h3=":443"; ma=86400'; + +# Gzip ์••์ถ• +gzip on; +gzip_comp_level 6; +gzip_types text/plain text/css application/json application/javascript + font/ttf font/opentype font/woff font/woff2; + +# ์ •์  ํŒŒ์ผ ์บ์‹ฑ +location ~* \.(js|css|png|jpg|jpeg|webp|gif|svg|ico|woff|woff2|ttf)$ { + proxy_cache STATIC_CACHE; + proxy_cache_valid 200 302 60m; + expires 60m; + add_header Cache-Control "public, immutable, no-transform, max-age=3600"; +} + +# WebSocket ํ”„๋ก์‹œ (์‹ค์‹œ๊ฐ„ ํ˜ธ๊ฐ€ ๋ฐ์ดํ„ฐ) +location /api/coin/min { + proxy_pass http://api-server/api/coin/min; + proxy_http_version 1.1; + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection "upgrade"; + proxy_read_timeout 86400s; +} +``` + +**์ถ”๊ฐ€ ์ธํ”„๋ผ (7์›”):** +- **SonarQube EC2**: ์ฝ”๋“œ ํ’ˆ์งˆ ๋ถ„์„ ์„œ๋ฒ„ + - t3.medium (2 vCPU, 4GB RAM) + - ์ •์  ์ฝ”๋“œ ๋ถ„์„, ์ฝ”๋“œ ์ปค๋ฒ„๋ฆฌ์ง€ ์ธก์ • + - PostgreSQL ๋‚ด์žฅ ์‹คํ–‰ + +**์ตœ์ข… ์•„ํ‚คํ…์ฒ˜:** +``` +[๊ฐœ๋ฐœ์ž] โ†’ [GitHub] โ†’ [GitHub Actions] โ†’ [SonarQube EC2] + โ†“ + [Docker Hub Registry] + โ†“ + [๋ฐฐํฌ ์Šคํฌ๋ฆฝํŠธ ์‹คํ–‰] + โ†“ + [๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ EC2] โ† [์™ธ๋ถ€ ํŠธ๋ž˜ํ”ฝ] + โ”œโ”€โ”€ / โ†’ [ํ”„๋ก ํŠธ์—”๋“œ EC2] + โ”œโ”€โ”€ /api/ โ†’ [๋ฐฑ์—”๋“œ EC2] + โ””โ”€โ”€ /ai/ โ†’ [AI ์„œ๋ฒ„ EC2] +``` + +**์šด์˜ ์ตœ์ ํ™”:** +- ์‹œ์Šคํ…œ ๋ชจ๋‹ˆํ„ฐ๋ง ๋ฐ ๋กœ๊ทธ ๊ด€๋ฆฌ ๊ฐ•ํ™” +- ์ž๋™ํ™”๋œ ๋ฐฐํฌ ํ”„๋กœ์„ธ์Šค ์•ˆ์ •ํ™” +- ์„ฑ๋Šฅ ๋ฉ”ํŠธ๋ฆญ ์ˆ˜์ง‘ ๋ฐ ๋ถ„์„ + +--- + +## ๊ธฐ์ˆ ์  ๋ณ€ํ™” ํฌ์ธํŠธ + +### ๋ณด์•ˆ ์šฐ์„  ์„ค๊ณ„ +- **์ดˆ๊ธฐ๋ถ€ํ„ฐ HTTPS ํ•„์ˆ˜**: HttpOnly, Secure ์ฟ ํ‚ค ์„ค์ • +- **ํ™˜๊ฒฝ๋ณ€์ˆ˜ ๊ธฐ๋ฐ˜ ๋น„๋ฐ€์ •๋ณด ๊ด€๋ฆฌ**: JWT_SECRET, KAKAO_CLIENT_ID ๋“ฑ + +### ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ ์ค‘์‹ฌ ์•„ํ‚คํ…์ฒ˜ +- **๋‹จ์ผ ์ง„์ž…์ **: ๋ชจ๋“  ์™ธ๋ถ€ ํŠธ๋ž˜ํ”ฝ์ด ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ๋ฅผ ํ†ต๊ณผ +- **๋ผ์šฐํŒ… ๊ธฐ๋ฐ˜ ๋ถ„์‚ฐ**: URL ๊ฒฝ๋กœ์— ๋”ฐ๋ฅธ ์„œ๋น„์Šค ๋ผ์šฐํŒ… + +### ์„ฑ๋Šฅ ์ค‘์‹ฌ ์„œ๋น„์Šค ๋ถ„๋ฆฌ +- **๋ฆฌ์†Œ์Šค ์ตœ์ ํ™”**: CPU/๋ฉ”๋ชจ๋ฆฌ ์‚ฌ์šฉ๋Ÿ‰์— ๋”ฐ๋ฅธ ์ธ์Šคํ„ด์Šค ํƒ€์ž… ์„ ํƒ +- **๋„คํŠธ์›Œํฌ ์ตœ์ ํ™”**: Private IP ๊ธฐ๋ฐ˜ ๋‚ด๋ถ€ ํ†ต์‹  +- **๋…๋ฆฝ์  ์Šค์ผ€์ผ๋ง**: ์„œ๋น„์Šค๋ณ„ ๊ฐœ๋ณ„ ํ™•์žฅ ๊ฐ€๋Šฅ +- **๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ๋ถ„๋ฆฌ**: MariaDB ์ „์šฉ EC2 +- **๋ณด์•ˆ ๊ฐ•ํ™”**: ๋ฐ์ดํ„ฐ๋ฒ ์ด์Šค ์ง์ ‘ ์ ‘๊ทผ ์ฐจ๋‹จ, ๋ฐฑ์—”๋“œ๋ฅผ ํ†ตํ•œ ์ค‘์•™ ์ง‘์ค‘์‹ ๋ฐ์ดํ„ฐ ๊ด€๋ฆฌ + +### ๊ฐœ๋ฐœ ํ’ˆ์งˆ ๊ด€๋ฆฌ +- **SonarQube ๋„์ž…**: ์ •์  ์ฝ”๋“œ ๋ถ„์„ ์ž๋™ํ™” +- **CI/CD ํŒŒ์ดํ”„๋ผ์ธ**: ํ…Œ์ŠคํŠธ โ†’ ๋ถ„์„ โ†’ ๋ฐฐํฌ ์ž๋™ํ™” + +--- + +## ์„ฑ๋Šฅ ๋ฐ ๋น„์šฉ ๊ฐœ์„  ์ง€ํ‘œ + +### ์„ฑ๋Šฅ ๊ฐœ์„  +- **ํ”„๋ก ํŠธ์—”๋“œ ๋กœ๋”ฉ์‹œ๊ฐ„**: HTTP/3์œผ๋กœ ๋ธŒ๋ผ์šฐ์ €์—์„œ ์—์…‹ ๋กœ๋”ฉ ์‹œ๊ฐ„ ๋‹จ์ถ• +- **AI ์„œ๋ฒ„ ๋…๋ฆฝ์„ฑ**: ๋ฐฑ์—”๋“œ ๋ถ€ํ•˜์™€ ๋ฌด๊ด€ํ•œ AI ์„œ๋น„์Šค ์ œ๊ณต + +### ์šด์˜ ํšจ์œจ์„ฑ +- **์ฝ”๋“œ ํ’ˆ์งˆ**: SonarQube ๋„์ž…์œผ๋กœ ๋ฒ„๊ทธ ๋ฐœ์ƒ ๊ฐ€๋Šฅ ์ฝ”๋“œ ํƒ์ง€ +- **๊ฐœ๋ฐœ ์ƒ์‚ฐ์„ฑ**: ํ…Œ์ŠคํŠธ ์ž๋™ํ™” + +### ๋น„์šฉ ์ตœ์ ํ™” +- **Docker Hub ๋ฌด๋ฃŒ ํ‹ฐ์–ด**: ์ด๋ฏธ์ง€ ๋ ˆ์ง€์ŠคํŠธ๋ฆฌ ๋น„์šฉ 0์› +- **Let's Encrypt**: SSL ์ธ์ฆ์„œ ๋น„์šฉ ์ ˆ์•ฝ + +--- + +## ๋ฐฐํฌ ๋ณ€ํ™” ํƒ€์ž„๋ผ์ธ + +| ๋‚ ์งœ | ์ฃผ์š” ๋ณ€ํ™” | ์ปค๋ฐ‹ ํ•ด์‹œ | ์„ค๋ช… | +|------|-----------|-----------|------| +| 2025-04-30 | ์ดˆ๊ธฐ ๋ฐฐํฌ ํ™˜๊ฒฝ ๊ตฌ์ถ• | `453835f` | ๋‹จ์ผ EC2 + Docker Compose | +| 2025-05-01 | ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ ์„ค์ • | `02ae3c2` | HTTPS ์ ์šฉํ•œ nginx.conf | +| 2025-05-15 | SSL ์ธ์ฆ์„œ ์ž๋™ํ™” | `7c300a7` | Let's Encrypt ์„ค์ • | +| 2025-06-01 | EC2 ๋ถ„๋ฆฌ ์‹œ์ž‘ | `3fd9383` | ๋ฐฑ์—”๋“œ ์ „์šฉ EC2 ๋ถ„๋ฆฌ | +| 2025-06-15 | WebSocket ์ง€์› | `0c984b6` | ์‹ค์‹œ๊ฐ„ ๋ฐ์ดํ„ฐ ์ŠคํŠธ๋ฆฌ๋ฐ | +| 2025-07-01 | AI ์„œ๋ฒ„ ๋ถ„๋ฆฌ | `a954d3a` | AI ์ „์šฉ EC2 ๊ตฌ์„ฑ | +| 2025-07-15 | SonarQube ๋„์ž… | - | ์ฝ”๋“œ ํ’ˆ์งˆ ๊ด€๋ฆฌ EC2 ์ถ”๊ฐ€ | +| 2025-08-01 | ์„ฑ๋Šฅ ์ตœ์ ํ™” ์™„๋ฃŒ | `4c47152` | HTTP/3, ์บ์‹ฑ, ์••์ถ• ์ ์šฉ | + +--- + +## ํ•™์Šตํ•œ ๊ตํ›ˆ + +### 1. ๋ณด์•ˆ ์šฐ์„  ์„ค๊ณ„์˜ ์ค‘์š”์„ฑ +์ฒ˜์Œ๋ถ€ํ„ฐ HTTPS๋ฅผ ๊ณ ๋ คํ•œ ์•„ํ‚คํ…์ฒ˜ ์„ค๊ณ„๋กœ ์ธํ•ด ๋‚˜์ค‘์— ๋ณด์•ˆ ๊ด€๋ จ ๋ฆฌํŒฉํ† ๋ง ๋น„์šฉ์„ ์ ˆ์•ฝํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. + +### 2. ๋ฆฌ๋ฒ„์Šค ํ”„๋ก์‹œ์˜ ์ค‘์•™ ์ง‘์ค‘์‹ ๋ผ์šฐํŒ… +๋‹จ์ผ ์ง„์ž…์ ์„ ํ†ตํ•œ ํŠธ๋ž˜ํ”ฝ ๊ด€๋ฆฌ๋กœ ๋ณด์•ˆ, ๋ชจ๋‹ˆํ„ฐ๋ง, ์บ์‹ฑ์„ ์ผ๊ด€๋˜๊ฒŒ ์ ์šฉํ•  ์ˆ˜ ์žˆ์—ˆ์Šต๋‹ˆ๋‹ค. + +### 3. ์„œ๋น„์Šค ๋…๋ฆฝ์„ฑ์˜ ์ค‘์š”์„ฑ +๋ฐฑ์—”๋“œ์™€ AI ์„œ๋ฒ„๋ฅผ ๋…๋ฆฝ์ ์œผ๋กœ ์šด์˜ํ•จ์œผ๋กœ์จ ํ•œ ์„œ๋น„์Šค์˜ ์žฅ์• ๊ฐ€ ๋‹ค๋ฅธ ์„œ๋น„์Šค์— ์˜ํ–ฅ์„ ์ฃผ์ง€ ์•Š๋„๋ก ํ–ˆ์Šต๋‹ˆ๋‹ค. + +### 4. ์ ์ง„์  ํ™•์žฅ์˜ ํšจ๊ณผ +์ดˆ๊ธฐ ๋‹จ์ผ ์ธ์Šคํ„ด์Šค โ†’ ์„œ๋น„์Šค๋ณ„ ๋ถ„๋ฆฌ โ†’ ํ’ˆ์งˆ ๊ด€๋ฆฌ ๋„๊ตฌ ์ถ”๊ฐ€์˜ ์ ์ง„์  ํ™•์žฅ์œผ๋กœ ์•ˆ์ •์ ์ธ ์ธํ”„๋ผ๋ฅผ ๊ตฌ์ถ•ํ–ˆ์Šต๋‹ˆ๋‹ค. + +### 5. ๋ฌด๋ฃŒ ๋„๊ตฌ ํ™œ์šฉ +Docker Hub ๋ฌด๋ฃŒ ํ‹ฐ์–ด, Let's Encrypt ๋“ฑ ๋ฌด๋ฃŒ ๋„๊ตฌ๋ฅผ ์ ๊ทน ํ™œ์šฉํ•˜์—ฌ ์ดˆ๊ธฐ ๋น„์šฉ์„ ์ตœ์†Œํ™”ํ–ˆ์Šต๋‹ˆ๋‹ค. \ No newline at end of file