[{"data":1,"prerenderedAt":481},["ShallowReactive",2],{"post-document:\u002Fproject\u002F2026\u002F08\u002F17\u002F1-프로젝트-셋업\u002F":3},{"id":4,"title":5,"body":6,"categories":466,"date":468,"description":469,"extension":470,"image":471,"key_concepts":471,"last_modified_at":471,"legacyPath":472,"meta":473,"navigation":475,"part":471,"path":476,"published":475,"robots":471,"seo":477,"series":471,"stem":478,"strengths":471,"summary":471,"tags":479,"tradeoffs":471,"__hash__":480},"posts\u002Fposts\u002Fproject\u002F1. 프로젝트 셋업.md","Spring Boot, WebSocket, Vue, Docker 로 Raspberry Pi 실시간 모니터링 프로젝트 만들기 - 1편",{"type":7,"value":8,"toc":452},"minimark",[9,13,17,20,39,42,47,56,67,73,77,80,100,103,106,109,112,116,119,124,150,154,171,175,189,193,204,208,211,217,221,224,227,289,292,303,306,312,315,320,323,327,330,333,336,345,351,354,376,379,382,385,391,394,400,403,407,410,413,416,419,425,428,434,437,445,448],[10,11,5],"h1",{"id":12},"spring-boot-websocket-vue-docker-로-raspberry-pi-실시간-모니터링-프로젝트-만들기-1편",[14,15,16],"p",{},"Spring Boot를 공부하면서 Controller, Service, Repository, JPA 개념은 배우기 했지만,\n따로 공부하다 보니 실제 서비스에서는 이 기술들이 어떻게 연결되는지 감이 잘 안 온다... ㅇㅇ",[14,18,19],{},"그리고 REST API 를 공부하고 나서는 더더욱 의문을 갖게 되었다..",[21,22,23,27,30,33,36],"ul",{},[24,25,26],"li",{},"REST API 만으로 실시간으로 서비스를 개발이 가능한가?",[24,28,29],{},"WebSocket은 REST API랑 뭐가 다른거임?",[24,31,32],{},"스프링부트에서 받은 데이터를 실제 DB에서는 어떻게 저장하는거임?",[24,34,35],{},"Vue랑 스프링부트는 도대체 어떻게 통신하는 거임?",[24,37,38],{},"Raspberry Pi 같은 외부 장치는 서버와 어떻게 연결하는 거임?",[14,40,41],{},"이 프로젝트에서는 내가 배운 것들은 종합해보면서, 하나의 작은 시스템 안에서 연결해보려 한다.",[43,44,46],"h2",{"id":45},"_1-프로젝트-목표","1. 프로젝트 목표",[48,49,50,53],"blockquote",{},[14,51,52],{},"Raspberry Pi의 시스템 상태를 Spring boot의 서버로 전송하고, 데이터를 DB에 저장하고 Vue 대시보드에 WebSocket을 통해 실시간으로 확인하는 것",[14,54,55],{},"최종적으로는 Spring Boot, Vue, PostgreSQL을 Docker Compose를 통해서 한 번에 실행할 수 있게 구성하는 것",[57,58,64],"pre",{"className":59,"code":61,"language":62,"meta":63},[60],"language-text","Raspberry Pi\n    ↓\nREST API\n    ↓\nSpring Boot\n    ↓\nService\n    ↓\nRepository\n    ↓\nJPA\n    ↓\nPostgreSQL\n","text","",[65,66,61],"code",{"__ignoreMap":63},[57,68,71],{"className":69,"code":70,"language":62,"meta":63},[60],"Spring Boot\n    ↓\nWebSocket\n    ↓\nVue\n    ↓\n실시간 Dashboard\n",[65,72,70],{"__ignoreMap":63},[43,74,76],{"id":75},"_2-센서-데이터","2. 센서 데이터",[14,78,79],{},"Raspberry Pi에서는 일정 주기로 자신의 상태를 수집한다",[21,81,82,85,88,91,94,97],{},[24,83,84],{},"CPU temperature",[24,86,87],{},"CPU Usage",[24,89,90],{},"Memory Usage",[24,92,93],{},"Disk Usage",[24,95,96],{},"Network Traffic",[24,98,99],{},"Timestamp",[14,101,102],{},"Raspberry Pi는 수집한 데이터를 Spring BOOT REST API로 전송",[14,104,105],{},"이후에 Spring Boot는 해당 데이터를 DB에 저장한 후 WebSocket을 이용해 Vue Frontend에 전달한다.",[14,107,108],{},"Vue에서는 새로고침 없이 데이터를 받아 실시간으로 화면을 갱신",[110,111],"hr",{},[43,113,115],{"id":114},"_3-이번-프로젝트를-통해서-학습할-기술","3. 이번 프로젝트를 통해서 학습할 기술",[14,117,118],{},"이번 프로젝트에서는 다음 기술을 익힐 예정",[120,121,123],"h3",{"id":122},"backend","Backend",[21,125,126,129,132,135,138,141,144,147],{},[24,127,128],{},"Java",[24,130,131],{},"Spring Boot",[24,133,134],{},"Spring MVC",[24,136,137],{},"Spring Data JPA",[24,139,140],{},"WebSocket",[24,142,143],{},"STOMP",[24,145,146],{},"PostgreSQL",[24,148,149],{},"Gradle",[120,151,153],{"id":152},"frontend","Frontend",[21,155,156,159,162,165,168],{},[24,157,158],{},"Vue3",[24,160,161],{},"Compositon API",[24,163,164],{},"Axios",[24,166,167],{},"STOMP.js",[24,169,170],{},"Chart Library",[120,172,174],{"id":173},"infrastructure","Infrastructure",[21,176,177,180,183,186],{},[24,178,179],{},"Docker",[24,181,182],{},"Dockerfile",[24,184,185],{},"Docker Compose",[24,187,188],{},"Nginx",[120,190,192],{"id":191},"device-network","Device & Network",[21,194,195,198,201],{},[24,196,197],{},"Raspberry Pi",[24,199,200],{},"Python",[24,202,203],{},"HTTP Request",[43,205,207],{"id":206},"_4-전체-시스템-architecture","4. 전체 시스템 Architecture",[14,209,210],{},"전체 Architecture를 먼저 간략하게 정리해보자, 구조는 아래와 같다.\n(기술 상세는 일단 아래에서 적자)",[57,212,215],{"className":213,"code":214,"language":62,"meta":63},[60],"┌───────────────────────┐\n│     Raspberry Pi      │\n│                       │\n│ CPU Temperature       │\n│ CPU Usage             │\n│ Memory Usage          │\n└───────────┬───────────┘\n            │\n            │ HTTP POST\n            ▼\n┌───────────────────────┐\n│     Spring Boot       │\n│                       │\n│ REST Controller       │\n│ Service               │\n│ Repository            │\n└───────┬─────────┬─────┘\n        │         │\n        │         │ WebSocket\n        ▼         ▼\n┌────────────┐  ┌─────────────────┐\n│ PostgreSQL │  │ Vue Frontend    │\n│            │  │                 │\n│ SensorData │  │ Dashboard       │\n└────────────┘  └─────────────────┘\n",[65,216,214],{"__ignoreMap":63},[43,218,220],{"id":219},"_5-rest-api의-역할","5. REST API의 역할",[14,222,223],{},"Raspberry Pi가 Spring Boot로 데이터를 보내는 데에는 REST API를 사용",[14,225,226],{},"예시",[57,228,232],{"className":229,"code":230,"language":231,"meta":63,"style":63},"language-json shiki shiki-themes github-light github-dark","{\n  \"temperature\": 52.3,\n  \"cpuUsage\": 18.5,\n  \"memoryUsage\": 41.2\n}\n","json",[65,233,234,243,259,272,283],{"__ignoreMap":63},[235,236,239],"span",{"class":237,"line":238},"line",1,[235,240,242],{"class":241},"sVt8B","{\n",[235,244,246,250,253,256],{"class":237,"line":245},2,[235,247,249],{"class":248},"sj4cs","  \"temperature\"",[235,251,252],{"class":241},": ",[235,254,255],{"class":248},"52.3",[235,257,258],{"class":241},",\n",[235,260,262,265,267,270],{"class":237,"line":261},3,[235,263,264],{"class":248},"  \"cpuUsage\"",[235,266,252],{"class":241},[235,268,269],{"class":248},"18.5",[235,271,258],{"class":241},[235,273,275,278,280],{"class":237,"line":274},4,[235,276,277],{"class":248},"  \"memoryUsage\"",[235,279,252],{"class":241},[235,281,282],{"class":248},"41.2\n",[235,284,286],{"class":237,"line":285},5,[235,287,288],{"class":241},"}\n",[14,290,291],{},"이를 API로 전송한다.",[57,293,297],{"className":294,"code":295,"language":296,"meta":63,"style":63},"language-http shiki shiki-themes github-light github-dark","POST \u002Fapi\u002Fsensors\n","http",[65,298,299],{"__ignoreMap":63},[235,300,301],{"class":237,"line":238},[235,302,295],{},[14,304,305],{},"그래서 전체흐름은 다음과 같아진다.",[57,307,310],{"className":308,"code":309,"language":62,"meta":63},[60],"Raspberry Pi\n    ↓\nPOST \u002Fapi\u002Fsensors\n    ↓\nController\n    ↓\nService\n    ↓\nRepository\n    ↓\nJPA\n    ↓\nPostgreSQL\n",[65,311,309],{"__ignoreMap":63},[14,313,314],{},"REST API는 기본적으로 Client가 Server에게 요청하는 구조\nRaspberry Pi가 Spring Boot에게",[48,316,317],{},[14,318,319],{},"현재 내 상태는 이거야",[14,321,322],{},"라고 데이터를 보내는 역할을 한다. (동시에 데이터를 생성하기도 한다. Raspberry Pi의 Status를 전송하기 때문에)",[43,324,326],{"id":325},"_6-websocket의-역할","6. WebSocket의 역할",[14,328,329],{},"근데 우리가 데이터를 Vue를 통해서 보여줄거잖슴?",[14,331,332],{},"Vue에서 Raspberry Pi 데이터를 실시간으로 표시하려면 데이터가 변경되었는지 계속 확인해야 한다.",[14,334,335],{},"WebSocket이 없다면 다음과 같은 Polling 구조를 가져야 한다.",[48,337,338],{},[14,339,340,344],{},[341,342,343],"strong",{},"Polling(폴링)",": 클라이언트가 일정한 간격으로 서버에 반복 요청을 보내 새 데이터가 있는지 확인하는 방식.",[57,346,349],{"className":347,"code":348,"language":62,"meta":63},[60],"Vue\n ↓\n데이터 있어?\n ↓\nSpring\n\nVue\n ↓\n새 데이터 있어?\n ↓\nSpring\n\nVue\n ↓\n또 데이터 있어?\n ↓\nSpring\n",[65,350,348],{"__ignoreMap":63},[14,352,353],{},"예를 들어 2초마다 API를 호출하면",[57,355,359],{"className":356,"code":357,"language":358,"meta":63,"style":63},"language-javascript shiki shiki-themes github-light github-dark","setInterval(() => {\n  axios.get(\"\u002Fapi\u002Fsensors\u002Flatest\")\n}, 2000)\n","javascript",[65,360,361,366,371],{"__ignoreMap":63},[235,362,363],{"class":237,"line":238},[235,364,365],{},"setInterval(() => {\n",[235,367,368],{"class":237,"line":245},[235,369,370],{},"  axios.get(\"\u002Fapi\u002Fsensors\u002Flatest\")\n",[235,372,373],{"class":237,"line":261},[235,374,375],{},"}, 2000)\n",[14,377,378],{},"와 같이 API 세팅을 해줘야 한다.",[14,380,381],{},"하지만 WebSocket을 사용하면 방향을 반대로 만들 수 있다.",[14,383,384],{},"즉 서버가 클라이언트에게 먼저 데이터를 전달할 수 있다. (굳이 프론트에서 세팅을 할 필요가 없다.)",[57,386,389],{"className":387,"code":388,"language":62,"meta":63},[60],"Spring Boot\n     ↓\n새 데이터가 들어왔음\n     ↓\nWebSocket Push\n     ↓\nVue\n",[65,390,388],{"__ignoreMap":63},[14,392,393],{},"이번 프로젝트에서는 다음 구조를 사용한다.",[57,395,398],{"className":396,"code":397,"language":62,"meta":63},[60],"Raspberry Pi\n      ↓\nPOST \u002Fapi\u002Fsensors\n      ↓\nSpring Boot\n      ↓\nDB 저장\n      ↓\nWebSocket\n      ↓\n\u002Ftopic\u002Fsensors\n      ↓\nVue\n",[65,399,397],{"__ignoreMap":63},[14,401,402],{},"이 흐름이 프로젝트 전반적인 구조이므로 잘 알고 있다면 좋을 것 같다.",[43,404,406],{"id":405},"_7-rest-api-와-websocket을-동시에-사용하는-이유","7. REST API 와 WebSocket을 동시에 사용하는 이유",[14,408,409],{},"전부 WebSocket으로 구현하면 되는 것 아닌가? 라는 좋은 생각이 들 수도 있다.",[14,411,412],{},"기술을 분리해서 사용하는 것은 역할이 달라서다.",[14,414,415],{},"일단 기술의 역할을 알아보자.",[14,417,418],{},"REST API는 주로 요청과 응답에 사용한다.",[57,420,423],{"className":421,"code":422,"language":62,"meta":63},[60],"Client → Server\n",[65,424,422],{"__ignoreMap":63},[14,426,427],{},"WebSocket은 연결을 유지한채로 양방향 통신을 할 수 있다.",[57,429,432],{"className":430,"code":431,"language":62,"meta":63},[60],"Client ↔ Server\n",[65,433,431],{"__ignoreMap":63},[14,435,436],{},"이번 프로젝트에서는 다음과 같이 역할을 나눈다.",[21,438,439,442],{},[24,440,441],{},"Raspberry Pi → Spring Boot: REST",[24,443,444],{},"Spring Boot → Vue: WebSocket",[14,446,447],{},"즉 데이터 수집에는 REST를 사용하고, 실시간 화면 갱신에는 WebSocket을 사용할 예정이다.",[449,450,451],"style",{},"html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sVt8B, html code.shiki .sVt8B{--shiki-default:#24292E;--shiki-dark:#E1E4E8}html pre.shiki code .sj4cs, html code.shiki .sj4cs{--shiki-default:#005CC5;--shiki-dark:#79B8FF}",{"title":63,"searchDepth":245,"depth":245,"links":453},[454,455,456,462,463,464,465],{"id":45,"depth":245,"text":46},{"id":75,"depth":245,"text":76},{"id":114,"depth":245,"text":115,"children":457},[458,459,460,461],{"id":122,"depth":261,"text":123},{"id":152,"depth":261,"text":153},{"id":173,"depth":261,"text":174},{"id":191,"depth":261,"text":192},{"id":206,"depth":245,"text":207},{"id":219,"depth":245,"text":220},{"id":325,"depth":245,"text":326},{"id":405,"depth":245,"text":406},[467],"project","2026-08-17 01:05:35 +0900","Spring Boot를 공부하면서 Controller, Service, Repository, JPA 개념은 배우기 했지만, 따로 공부하다 보니 실제 서비스에서는 이 기술들이 어떻게 연결되는지 감이 잘 안 온다... ㅇㅇ","md",null,"\u002Fproject\u002F2026\u002F08\u002F17\u002F1-프로젝트-셋업\u002F",{"layout":474},"post",true,"\u002Fposts\u002Fproject",{"title":5,"description":469},"posts\u002Fproject\u002F1. 프로젝트 셋업",[],"8Rs7d0XdkagHRnH-uQ6PqiFMduM_JcrJ7nsi1D8BoKE",1788744789953]