<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>나는 언제나 낯선 사람들의 친절에 의지해왔다</title>
    <link>https://parkstate.tistory.com/</link>
    <description>박종연의 개발 블로그입니다.</description>
    <language>ko</language>
    <pubDate>Mon, 22 Jun 2026 14:04:53 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>박종연</managingEditor>
    <image>
      <title>나는 언제나 낯선 사람들의 친절에 의지해왔다</title>
      <url>https://tistory1.daumcdn.net/tistory/5330765/attach/1a9b405072484724b6f98d174563ffde</url>
      <link>https://parkstate.tistory.com</link>
    </image>
    <item>
      <title>내가 Codex 설정 후 바로 시킨 일</title>
      <link>https://parkstate.tistory.com/59</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 Codex를 업무에 도입했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Codex 계정을 받아 설치를 진행하고 처음 명령한 일은 프로젝트의 구조에대해 분석하여 마크다운 파일로 &lt;b&gt;&lt;i&gt;docs/ &lt;/i&gt;&lt;/b&gt;폴더 안에 정리하라는 일을 지시했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 첫 지시로 문서화를 선택한 이유는 명확했다. Codex가 우리 프로젝트의 컨텍스트를 얼마나 깊이 이해할 수 있는지 확인하기 위해서였다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 이미 머릿속으로 파악하고 있는 실제 프로젝트의 아키텍처와 Codex가 분석한 결과물이 일치하는지 비교해 보면, 이 도구의 성능을 단번에 알 수 있겠다고 생각했다. 프로젝트의 뼈대를 제대로 이해하는 녀석이라면, 향후 복잡한 비즈니스 로직 수정이나 신규 기능 개발 같은 까다로운 요청도 믿고 맡길 수 있을 테니까.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과물은 정말 뛰어났다. 요청한지 얼마 안가서 프로젝트의 구조를 파악하고 문서화했고, 그렇게 정리해준 프로젝트의 모듈마다의 문서에는 해당 모듈의 경로와 역할이 잘 정리되어있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;너무 뛰어나서 많이 놀랐다. 바퀴를 처음 접한 인류같았다. 프로젝트가 작은 프로젝트가 아닌데도 구조를 파악하고 정리하는데 시간이 얼마 안걸렸다. 시간을 얼마 안썼는데도 프로젝트 구조를 잘 파악했다. 심지어 gradle의 모듈 관계, custom Task, config까지도 모두 파악하여 정리했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 정말 AI를 잘 활용하는 사람이 살아남을수밖에 없을 것 같다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;추가로 당부의 글..&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 에이전트 AI 도구를 제대로 업무에 써보는건 처음이니 아직 미숙한 부분이 많을것이라고 생각합니다. 이 글을 읽게되시는 분들이 얼마나 될지 모르겠지만 제가 부족한 점이나 활용을 더 잘할수 있는 방법이나 내가 잘못 생각한 부분이 있다면 많은 피드백을 요청드립니다..&amp;nbsp;&lt;/p&gt;</description>
      <category>일상</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/59</guid>
      <comments>https://parkstate.tistory.com/59#entry59comment</comments>
      <pubDate>Wed, 27 May 2026 21:58:51 +0900</pubDate>
    </item>
    <item>
      <title>AWS Summit Seoul 2026 Day 1(Industry Day) 방문기</title>
      <link>https://parkstate.tistory.com/58</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은&amp;nbsp;삼성역&amp;nbsp;코엑스에서&amp;nbsp;개최된&amp;nbsp;AWS&amp;nbsp;Summit&amp;nbsp;Seoul에&amp;nbsp;참석했다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비바람이&amp;nbsp;불고&amp;nbsp;궂은&amp;nbsp;날씨였지만,&amp;nbsp;우산&amp;nbsp;하나에&amp;nbsp;의지해&amp;nbsp;행사&amp;nbsp;장소로&amp;nbsp;발걸음을&amp;nbsp;옮겼다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 기조연설에서 AWS 코리아 대표는 AI 발전으로 인한 거대한 변화를 강조했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AI는 하루가 다르게 새로운 미래를 이끌고 있으며, 불과 몇 년 전 화두였던 생성형 AI를 넘어 이제는 업무를 스스로 판단하고 수행하는 에이전트 AI, 그리고 디지털 세계에서 물리적 세계로 확장되는 피지컬 AI로 진화하고 있다고 언급했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 이 변화의 최전선에서 개발을 수행하고 있는 나로서는 굉장히 공감 가는 연설이었다. 2022년 말 대중에게 충격을 주며 등장한 OpenAI의 ChatGPT 이후, AI 기술은 엄청난 변화를 겪었다. 초기만 해도 질문에 엉뚱한 내용을 지어내어 답변하는 할루시네이션이 잦았지만, 최근에는 그 빈도가 확실히 줄어들고 답변의 품질도 눈에 띄게 정교해졌다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초창기에는&amp;nbsp;생성형&amp;nbsp;AI에게&amp;nbsp;단순한&amp;nbsp;기술적&amp;nbsp;질문을&amp;nbsp;던지는&amp;nbsp;수준에&amp;nbsp;그쳤다.&amp;nbsp;하지만&amp;nbsp;불과&amp;nbsp;몇&amp;nbsp;년이&amp;nbsp;지난&amp;nbsp;지금,&amp;nbsp;우리는&amp;nbsp;에이전트&amp;nbsp;AI에게&amp;nbsp;프로젝트의&amp;nbsp;전체&amp;nbsp;구조를&amp;nbsp;파악하게&amp;nbsp;하고,&amp;nbsp;새로운&amp;nbsp;기능을&amp;nbsp;추가하며,&amp;nbsp;복잡한&amp;nbsp;문제를&amp;nbsp;스스로&amp;nbsp;해결하도록&amp;nbsp;지시할&amp;nbsp;수&amp;nbsp;있게&amp;nbsp;되었다.&amp;nbsp;심지어&amp;nbsp;이제는&amp;nbsp;AI가&amp;nbsp;시스템&amp;nbsp;설계까지&amp;nbsp;주도할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;수준에&amp;nbsp;이르렀다.&amp;nbsp;짧은&amp;nbsp;시간&amp;nbsp;내에&amp;nbsp;일어난&amp;nbsp;이&amp;nbsp;엄청난&amp;nbsp;변화를&amp;nbsp;체감하며,&amp;nbsp;앞으로&amp;nbsp;다가올&amp;nbsp;미래를&amp;nbsp;대비해&amp;nbsp;정말&amp;nbsp;끊임없이&amp;nbsp;공부해야겠다는&amp;nbsp;생각이&amp;nbsp;들었다.&amp;nbsp;내가&amp;nbsp;제자리에&amp;nbsp;멈춰있다고&amp;nbsp;해서&amp;nbsp;시장이&amp;nbsp;나를&amp;nbsp;기다려주진&amp;nbsp;않는다.&amp;nbsp;시장은&amp;nbsp;무서운&amp;nbsp;속도로&amp;nbsp;발전할&amp;nbsp;것이고,&amp;nbsp;멈춰있는&amp;nbsp;자는&amp;nbsp;뒤처질&amp;nbsp;수밖에&amp;nbsp;없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘 AWS Summit의 여러 세션을 들으며 내린 결론은, AI가 이제 산업 전반을 지탱하는 필수 인프라가 되었다는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올해로 3년째 AWS Summit에 참석하고 있는데, 매년 AI 트렌드가 어떻게 진화하는지 피부로 느낄 수 있었다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2024년&amp;nbsp;(첫&amp;nbsp;방문):&amp;nbsp;생성형&amp;nbsp;AI를&amp;nbsp;비즈니스에&amp;nbsp;도입하기&amp;nbsp;위한&amp;nbsp;방법론과&amp;nbsp;기초적인&amp;nbsp;PoC(개념&amp;nbsp;증명)&amp;nbsp;구축&amp;nbsp;사례가&amp;nbsp;주를&amp;nbsp;이루었다.&lt;/li&gt;
&lt;li&gt;2025년&amp;nbsp;(두&amp;nbsp;번째&amp;nbsp;방문):&amp;nbsp;생성형&amp;nbsp;AI에&amp;nbsp;RAG(검색&amp;nbsp;증강&amp;nbsp;생성)를&amp;nbsp;결합한&amp;nbsp;데이터&amp;nbsp;분석과&amp;nbsp;이를&amp;nbsp;활용한&amp;nbsp;실질적인&amp;nbsp;혁신&amp;nbsp;사례들이&amp;nbsp;쏟아졌다.&lt;/li&gt;
&lt;li&gt;2026년&amp;nbsp;(올해):&amp;nbsp;단순한&amp;nbsp;생성형&amp;nbsp;AI&amp;nbsp;세션은&amp;nbsp;줄어들고,&amp;nbsp;그&amp;nbsp;자리를&amp;nbsp;에이전트&amp;nbsp;AI의&amp;nbsp;아키텍처&amp;nbsp;설계,&amp;nbsp;구축&amp;nbsp;방법론,&amp;nbsp;그리고&amp;nbsp;실제&amp;nbsp;운영&amp;nbsp;사례가&amp;nbsp;가득&amp;nbsp;채웠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 1년이 지날 때마다 생성형 AI의 등장 &amp;rarr; 정확도를 높이는 RAG 구축 &amp;rarr; 에이전트 AI의 등장 및 실제 사업 적용 으로 이어지는 변화의 흐름이 뚜렷하게 나타나고 있다. 이 거대한 물결에 잘 올라타야만 한다. 그러기 위해서는 배움을 멈추지 않고, 산업 트렌드에 뒤처지지 않도록 항상 눈과 귀를 열어두어야 한다. 앞으로 몰아칠 변화 속에서도 살아남고 한 단계 더 성장하기 위해, 오늘도 치열하게 살아내야겠다.&amp;nbsp;&lt;/p&gt;</description>
      <category>일상/개발 관련 생각</category>
      <category>AWS SUMMIT SEOUL</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/58</guid>
      <comments>https://parkstate.tistory.com/58#entry58comment</comments>
      <pubDate>Wed, 20 May 2026 23:33:13 +0900</pubDate>
    </item>
    <item>
      <title>안드로이드에 여러 제조사의 영수증 프린터 연동할때 어려웠던 점</title>
      <link>https://parkstate.tistory.com/57</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 POS/KIOSK로 나가는 안드로이드 기기의 기본적인 테스트를 하기 위한 앱이 필요할 것으로 예상되어 개인적으로 DeviceTestApplication 프로젝트를 파서 진행했다. 해당 프로젝트에서는 다양한 영수증 프린터(Epson, Sewoo 등)의 출력 테스트, 디스크 읽기/쓰기 속도 테스트, RFID Reader의 읽기 테스트, 네트워크 테스트 등을 구현할 계획이었다. 오늘은 그중에 다양한 영수증 프린터의 연동 및 출력 테스트를 제작한 것에 대하여 글을 남긴다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;나는 처음에 프린터 제조사의 안드로이드 연동 SDK 대로만 연동하면 될 것으로 예상했다. 하지만 처음에 생각했던것보다 난관이 의외로 많았다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;어려웠던 점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금 기억나는 연동시에 어려웠던 점들은 &lt;b&gt;하드웨어 제어 흐름의 파편화&lt;/b&gt;와 영수증 출력 레이아웃을 그리는 &lt;b&gt;레이아웃 코드의 가독성 붕괴&lt;/b&gt;였다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;비즈니스 로직과 어울리지 않는 어지러운 콜백 함수&lt;/b&gt;&amp;nbsp;&lt;br /&gt;&lt;br /&gt;프린터 모듈에서 영수증을 출력하는 과정은 &lt;i&gt;연결 -&amp;gt; 상태 확인 -&amp;gt; 출력 -&amp;gt; 커팅&lt;/i&gt; 이라는 지극히 동기적이고 순차적인 흐름이다. 하지만 코드는 연결 성공 콜백 안에서 상태 확인을 호출하고, 그 콜백 안에서 다시 출력을 지시하는 식으로 깊어지며 콜백이 반복되어 나중에는 알아보고 수정하기도 힘든 스파게티 코드가 되어갔다.&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;명령형으로&amp;nbsp;나열된&amp;nbsp;하드코딩&amp;nbsp;레이아웃&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;영수증 레이아웃을 출력할 때마다 printer.printText(), printer.setAlignment(), printer.setTextStyle() 같은 함수들을 수십 번씩 호출했다. 코드가 길어지니 결과물(영수증)이 어떻게 생겼을지 직관적으로 그려지지가 않았고, 조그마한 추가사항이나 수정이라도 직접 수정하고 뽑아보는 과정을 반복했다. 그래서 유지보수가 너무나 힘들었다.&lt;b&gt;&lt;br /&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;원인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 문제점들이 발생한 원인은, 제조사 SDK의 설계 패턴을 우리 프로젝트의 비즈니스 로직에 그대로 노출시킨 데에 있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;첫번째 문제점의 원인은 이렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분의 안드로이드 하드웨어 SDK는 C언어나 C++의 네이티브 so파일을 JNI로 연결해놓은 형태이다. 그래서 어쩔수 없이 비동기형태의 콜백 패턴을 강제한다. 이러한 SDK를 우리가 연동시에 적절하게 동기화 하여 사용하도록 래핑해서 사용하는게 가장 이상적이지만 그렇게 진행하지 못했던게 원인.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두번째 문제점의 원인은 이렇다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;영수증이라는 하나의 UI를 그리는 작업임에도 이 작업에 대한 구현 방식은 선언적으로 추상화하지 않고, 하드웨어에 직접 명령을 내리는 방식에 매몰되어 구현했던게 원인.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제의 원인&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;콜백&amp;nbsp;안에&amp;nbsp;콜백을&amp;nbsp;중첩하여&amp;nbsp;순차적&amp;nbsp;비즈니스&amp;nbsp;로직을&amp;nbsp;처리&lt;/li&gt;
&lt;li&gt;함수 호출을 한 줄씩 나열하는 명령형 영수증 템플릿 작성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;이상적 해결&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기&amp;nbsp;콜백을&amp;nbsp;동기적(순차적)&amp;nbsp;흐름으로&amp;nbsp;묶어내어&amp;nbsp;가독성&amp;nbsp;확보&lt;/li&gt;
&lt;li&gt;HTML이나&amp;nbsp;Compose처럼&amp;nbsp;직관적이고&amp;nbsp;선언적인&amp;nbsp;코드로&amp;nbsp;영수증&amp;nbsp;레이아웃&amp;nbsp;작성&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;해결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 하드웨어를 다루는 Wrapper 레이어의 접근 방식만 고쳐도 생산성에 엄청난 향상을 가져올 수 있을 것으로 보였다. 즉시 비동기 콜백을 동기로 묶어내는 작업과, 영수증 템플릿을 위한 Kotlin DSL 모듈 구축을 진행했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. CountDownLatch를 활용한 Blocking 처리 먼저 CB125BlockingPrinter 클래스를 만들어 비동기 콜백의 사슬을 끊었다. CountDownLatch를 사용해 콜백 응답이 올 때까지 스레드를 대기시키고, 결과를 반환하도록 만들었다.&lt;/p&gt;
&lt;pre id=&quot;code_1778036149322&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// CB125BlockingPrinter.kt 
fun getStatusDetail(): PrinterStatusType {
    var status: PrinterStatusType = PrinterStatusType.UNKNOWN
    val latch = CountDownLatch(1)

    // 비동기 콜백 호출
    printer?.printerStatus { result -&amp;gt;
        status = when (result) {
            POSConst.STS_NORMAL -&amp;gt; PrinterStatusType.NORMAL
            // ...
            else -&amp;gt; PrinterStatusType.UNKNOWN
        }
        latch.countDown() // 결과를 받았으므로 래치 해제
    }

    // 최대 2초 동안 콜백 대기, 이후 순차적 로직 진행
    latch.await(2, TimeUnit.SECONDS)
    return status
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2.&amp;nbsp;Kotlin&amp;nbsp;DSL(Type-Safe&amp;nbsp;Builders)&amp;nbsp;도입&amp;nbsp;영수증&amp;nbsp;템플릿&amp;nbsp;작성은&amp;nbsp;Kotlin의&amp;nbsp;+&amp;nbsp;연산자&amp;nbsp;오버로딩과&amp;nbsp;확장&amp;nbsp;함수를&amp;nbsp;활용해&amp;nbsp;완전히&amp;nbsp;새롭게&amp;nbsp;디자인했다.&lt;/p&gt;
&lt;pre id=&quot;code_1778036304920&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// PrinterTestUtil.kt
private val testComp = styledComponent&amp;lt;TestSheetProps&amp;gt; {
    val (printerType) = props
    +HorizontalLine(HorizontalLineProps())

    +&quot;영수증 프린터 출력 테스트 용지 입니다.&quot;
    +HorizontalLine(HorizontalLineProps())
    
    +(&quot;문자열 속성 테스트 : 굵게&quot; format TextComponent.Format(style = setOf(TextStyle.BOLD)))
    +(&quot;문자열 속성 테스트 : 사이즈 4배&quot; format TextComponent.Format(fontHeight = 4.0, fontWidth = 4.0))
    +(&quot;문자열 속성 테스트 : 우측 정렬, 밑줄&quot; format TextComponent.Format(
        alignment = Alignment.RIGHT,
        style = setOf(TextStyle.UNDERLINE)
    ))
    
    +HorizontalLine(HorizontalLineProps())
    +&quot;출력시간: ${LocalDateTime.now()}&quot;
    +cut
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;styledComponent는 내부의 프린터 영수증 템플릿 라이브러리에서 정의한 함수이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결과는&amp;nbsp;좋았다.&amp;nbsp;기존에&amp;nbsp;수십&amp;nbsp;줄에&amp;nbsp;달하던&amp;nbsp;알&amp;nbsp;수&amp;nbsp;없는&amp;nbsp;명령어들의&amp;nbsp;나열이,&amp;nbsp;실제&amp;nbsp;영수증&amp;nbsp;형태를&amp;nbsp;꼭&amp;nbsp;닮은&amp;nbsp;몇&amp;nbsp;줄의&amp;nbsp;코드로&amp;nbsp;압축되었다.&amp;nbsp;새로운&amp;nbsp;텍스트&amp;nbsp;포맷이나&amp;nbsp;바코드를&amp;nbsp;추가할&amp;nbsp;때도&amp;nbsp;기존&amp;nbsp;로직을&amp;nbsp;헤치지&amp;nbsp;않고&amp;nbsp;블록을&amp;nbsp;끼워&amp;nbsp;넣듯&amp;nbsp;손쉽게&amp;nbsp;작업할&amp;nbsp;수&amp;nbsp;있었다.&amp;nbsp;게다가&amp;nbsp;Epson이든&amp;nbsp;Sewoo든&amp;nbsp;하위&amp;nbsp;제조사&amp;nbsp;구현체만&amp;nbsp;갈아&amp;nbsp;끼우면&amp;nbsp;동일한&amp;nbsp;DSL&amp;nbsp;템플릿을&amp;nbsp;재사용할&amp;nbsp;수&amp;nbsp;있는&amp;nbsp;구조가&amp;nbsp;완성되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결론&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나는 이렇게 마주한 문제를&amp;nbsp;&lt;b&gt;언어의 기능을 활용한 적극적인 구조화&lt;/b&gt;를 통해 해결했다.&lt;br /&gt;&lt;br /&gt;처음에&amp;nbsp;작성했던&amp;nbsp;단순&amp;nbsp;SDK&amp;nbsp;함수&amp;nbsp;나열은&amp;nbsp;하드웨어&amp;nbsp;통신의&amp;nbsp;본질적인&amp;nbsp;복잡성을&amp;nbsp;전혀&amp;nbsp;캡슐화하지&amp;nbsp;않은&amp;nbsp;채&amp;nbsp;날것&amp;nbsp;그대로&amp;nbsp;비즈니스&amp;nbsp;로직에&amp;nbsp;노출시켰고,&amp;nbsp;그렇기에&amp;nbsp;실패할&amp;nbsp;수밖에&amp;nbsp;없었다.&amp;nbsp;하지만&amp;nbsp;CountDownLatch를&amp;nbsp;통해&amp;nbsp;동기적&amp;nbsp;흐름을&amp;nbsp;되찾고,&amp;nbsp;Kotlin&amp;nbsp;DSL을&amp;nbsp;통해&amp;nbsp;선언적&amp;nbsp;레이아웃을&amp;nbsp;구성하고&amp;nbsp;나니,&amp;nbsp;복잡했던&amp;nbsp;하드웨어&amp;nbsp;연동이&amp;nbsp;프론트엔드&amp;nbsp;UI를&amp;nbsp;짜는&amp;nbsp;것만큼&amp;nbsp;즐거운&amp;nbsp;경험으로&amp;nbsp;바뀌었다.&lt;br /&gt;&lt;br /&gt;물론 언어의 고급 기능을 섞어 구조를 재설계하고, 제조사마다 다른 명령어 규격(Epson의 ESC/POS 등)을 하나의 인터페이스로 추상화하는 일은 생각보다 훨씬 피곤하고 고생스러운 과정이다. 하지만 미래에 이 결제 모듈을 유지보수할 누군가(아마도 높은 확률로 나 자신)를 위해서는 결국 낡은 패턴을 직시하고 부숴내야만 한다.&lt;/p&gt;</description>
      <category>Java-Kotlin/Android</category>
      <category>Android</category>
      <category>Kotlin</category>
      <category>Trouble Shooting</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/57</guid>
      <comments>https://parkstate.tistory.com/57#entry57comment</comments>
      <pubDate>Wed, 6 May 2026 13:32:09 +0900</pubDate>
    </item>
    <item>
      <title>솔루션 영업의 속도와 개발자가 짊어지는 기술 부채</title>
      <link>https://parkstate.tistory.com/56</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 회사의 영업 호조로 다수의 POS 납품 계약이 성사되면서 잦은 오픈 일정을 소화하고 있다. 회사 입장에서는 매출이 늘어나니 분명 기쁜 일이겠지만, 실무를 담당하는 개발자인 나의 입장은 조금 다르다. 솔직히 말해, 지금처럼 영업이 잘 되고 오픈이 잦은 현재의 상황이 내게는 아슬아슬하고 위태로운 외줄 타기처럼 느껴진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 큰 위기감은 &lt;b&gt;자사 솔루션에 대한 영업팀의 이해도 부족&lt;/b&gt;에서 비롯된다. 자사 솔루션을 외부 고객에게 판매한다면, 현재 우리 시스템이 어떤 구조와 기준을 바탕으로 운영되는지 명확히 인지해야 한다. 솔루션 영업의 기본은 해당 시스템에서 &lt;b&gt;가능한 것&lt;/b&gt;과 &lt;b&gt;불가능한 것&lt;/b&gt;의 경계를 확실히 긋는 것이라고 생각한다. 하지만 현재 영업팀은 기존의 운영 형태나 시스템적 한계를 알지도, 굳이 알려고 하지도 않은 채 고객사 미팅에 나선다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 지원하지 않는 외부 시스템 연동이나 기존 정책과 정면으로 배치되는 요구사항이 들어와도, 개발/운영 파트와의 기술적 검토 없이 무조건 &lt;b&gt;가능하다&lt;/b&gt;고 약속해 버린다. 영업팀의 이러한 무리한 약속은 고스란히 개발과 운영 실무자들의 몫, 아니 희생으로 돌아온다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;A 고객사는 이런 형태로, B 고객사는 또 다른 형태로 예외 처리를 요구한다고 가정해 보자. 초기 한두 번은 시스템의 기준이 확립되어 가는 과정이라 여기며 대응할 수 있다. 하지만 이런 예외 케이스가 반복되면, 코드는 분기에 분기를 거듭하며 복잡하게 얽힌 &lt;b&gt;스파게티 코드&lt;/b&gt;가 되어버린다. 명확한 기준 없이 파편화된 분기 로직들은 점차 개발자의 기억 범위를 벗어나게 되고, 히스토리가 누락되는 순간 이는 십중팔구 치명적인 버그로 이어진다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백번 양보하여 기능 개발에 충분한 시간이 주어진다면, 다양한 변수를 고려하고 확장성을 갖춘 견고한 아키텍처를 설계하여 좋은 결과물을 낼 수도 있을 것이다. 하지만 현실은 당장 눈앞에 닥친 납품 일정을 맞추고 기능들을 쳐내기에 급급해 심도 있는 기획과 설계를 고민할 시간조차 턱없이 부족하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 영업 행태가 지속된다면 프로젝트의 복잡도는 기하급수적으로 증가할 것이고, 유지보수 비용 역시 눈덩이처럼 불어날 것이다. 시스템의 안정성을 담보할 자원은 한정되어 있는데 예외 케이스만 늘어나니 곳곳에서 문제가 터지는 것은 예견된 수순이다. 가장 우려되는 점은, 훗날 걷잡을 수 없이 터져 나오는 문제들의 원인이 무리한 영업 방식이 아닌 &lt;b&gt;개발팀의 역량 부족&lt;/b&gt;으로 추궁받을 가능성이 크다는 것이다. 진짜 원인은 시스템의 근간을 흔드는 무분별한 영업에 있는데도 말이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇다면 이런 답답한 상황 속에서 실무자인 나는 어떻게 대처해야 할까? 최근 들어 이 생각이 가장 큰 고민거리이다. 단순히 &lt;b&gt;영업팀이 문제다&lt;/b&gt;라고 불평만 하고 있을 수는 없기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술적인 관점에서는, 수많은 예외 케이스 속에서도 시스템이 무너지지 않도록 방어책을 세우는 것을 고민하게 된다. 얽히고설킨 분기 처리로 인해 발생하는 사이드 이펙트를 최소화하기 위해 테스트 자동화를 적극적으로 도입해야 할지, 아니면 POS 솔루션의 핵심 코어 로직과 고객사별 커스텀 로직을 아키텍처 단에서 완벽하게 분리하는 대대적인 리팩토링을 감행해야 할지 저울질하게 된다. 하지만 당장의 납품 일정에 쫓기는 현실 속에서 이런 구조적인 개선에 온전히 시간을 쏟기란 불가능에 가깝다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국 이 문제는 기술만으로 해결할 수 있는 영역을 넘어섰다. 개발팀과 영업팀 간의 업무 프로세스 자체가 개선되어야 한다. 내가 앞장서서 영업팀에게 자사 솔루션의 명확한 가이드라인과 &lt;b&gt;절대 타협할 수 없는 기술적 기준&lt;/b&gt;을 문서화하여 설득해야 할까? 아니면 타 부서의 업무 방식까지 관여하는 것이 내게 주어진 권한을 뛰어넘는 월권일 수 있으니, 그저 묵묵히 내게 주어진 개발과 최적화 업무에만 집중하며 버텨야 하는 것일까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안정적이고 확장성 있는 시스템을 구축하고자 하는 개발자로서의 이상과, 일단 팔고 봐야 하는 B2B 솔루션 비즈니스의 현실 사이에서 괴리감이 느껴진다. 팀이 지쳐 쓰러지기 전에, 그리고 우리가 만든 솔루션이 통제 불능의 상태에 빠지기 전에 양측이 납득할 수 있는 지속 가능한 합의점을 어떻게 찾아야 할지 깊은 고민이 필요한 시점이다.&lt;/p&gt;</description>
      <category>일상</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/56</guid>
      <comments>https://parkstate.tistory.com/56#entry56comment</comments>
      <pubDate>Wed, 29 Apr 2026 23:03:20 +0900</pubDate>
    </item>
    <item>
      <title>커스텀 Gradle Task를 작성해 S3에 자동으로 배포 파일 올리기</title>
      <link>https://parkstate.tistory.com/55</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사의 KMP기반 안드로이드 POS 프로젝트를 운영하면서, 릴리즈 배포 시 APK를 빌드하고 이를 서버나 S3에 업로드하는 과정은 필수적입니다. 저희 팀의 기존 배포 프로세스는 대략 다음과 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;안드로이드 앱 배포 과정&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;build.gradle.kts에 배포 될 버전 명 작성.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;i&gt; assembleRelease&lt;/i&gt;&lt;/b&gt; Task를 실행하여 Apk 빌드.&lt;/li&gt;
&lt;li&gt;AWS 웹 콘솔 접속 및 로그인.&lt;/li&gt;
&lt;li&gt;&lt;i&gt; output&lt;/i&gt; 폴더에 생성된 Apk 파일을 S3의 배포 버킷에 수동으로 업로드.&lt;/li&gt;
&lt;li&gt;자사 기기 관리 페이지에 업로드한 파일의 S3 url을 포함하여 패치 파일 정보 등록.&lt;/li&gt;
&lt;li&gt;기기 관리 앱에서 업데이트 적용.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중에서 단순 반복 작업인 2, 3, 4 과정을 배포 할 때마다 반복하다보니 번거로웠고, 이런 단순 반복 작업을 진행 할 때마다 시간 낭비가 심한 것 같아 개선하고싶었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위해 빌드 파이프라인 자동화를 고민했고, &lt;b data-index-in-node=&quot;31&quot; data-path-to-node=&quot;9,1,1,0&quot;&gt;Gradle 커스텀 Task를 만들어 릴리즈 빌드 완료 시 S3에 APK가 자동으로 업로드되고 URL을 반환하도록 구현&lt;/b&gt;했습니다. 오늘은 이 커스텀 Task 제작 과정을 공유해 보려 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;buildSrc 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;buildSrc&lt;/i&gt;&lt;/b&gt;는 Gradle 빌드시 가장 먼저 컴파일하는 특별한 디렉토리이다. 여기에 로직을 작성하면 복잡한 스크립트를 클래스 단위로 깔끔하게 분리할 수 있고, IDE에서 제공하는 자동 완성 기능도 잘 작동한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Gradle 프로젝트의 루트에 &lt;i&gt;buildSrc &lt;/i&gt;폴더를 만든다.&lt;/li&gt;
&lt;li&gt;&lt;i&gt;buildSrc/build.gradle.kts&amp;nbsp;&lt;/i&gt;파일을 생성하고 아래와 같이 작성한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1776860972864&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// buildSrc/build.gradle.kts

plugins {
    `kotlin-dsl`
}

repositories {
    mavenCentral()
}

dependencies {
    // S3 업로드를 위한 AWS SDK
    implementation(&quot;software.amazon.awssdk:s3:2.20.0&quot;)
    // 웹훅에 알람 요청을 위한 Ktor Client
    // (현 회사는 jandi 메신저를 사용하는데 jandi 같은 오피스용 메신저의 웹훅을 활용한 알람 기능을 위해 필요함.)
    implementation(&quot;io.ktor:ktor-client-cio:2.3.0&quot;)
    implementation(&quot;io.ktor:ktor-client-content-negotiation:2.3.0&quot;)
    implementation(&quot;io.ktor:ktor-serialization-jackson:2.3.0&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;S3 업로드 Task 정의&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;bulidSrc/src/main/kotiln&amp;nbsp;&lt;/i&gt;안에 실제 업로드를 진행하는 로직을 담당할 &lt;b&gt;DeployToS3Task&amp;nbsp;&lt;/b&gt;클래스를 작성한다.&lt;/p&gt;
&lt;pre id=&quot;code_1776862024914&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// buildSrc/src/main/kotlin/DeployToS3Task.kt

import org.gradle.api.DefaultTask
import org.gradle.api.GradleException
import org.gradle.api.file.RegularFileProperty
import org.gradle.api.provider.Property
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.InputFile
import org.gradle.api.tasks.TaskAction
import software.amazon.awssdk.regions.Region
import software.amazon.awssdk.services.s3.S3Client
import software.amazon.awssdk.services.s3.model.HeadObjectRequest
import software.amazon.awssdk.services.s3.model.NoSuchKeyException
import software.amazon.awssdk.services.s3.model.PutObjectRequest

abstract class DeployToS3Task : DefaultTask() {

	// AWS 키는 로컬 환경변수등으로 보안에 유의하여 관리
	private val awsCredentials =
        AwsBasicCredentials.create(AwsAccessCredential.awsAccessKey, AwsAccessCredential.awsSecretKey)

    // 업로드 할 파일 경로
    @get:InputFile
    abstract val deployFile: RegularFileProperty

    // S3 버킷 내부에 저장될 경로 (예: mdm/POS/1.0.3-app-release.apk)
    @get:Input
    abstract val key: Property&amp;lt;String&amp;gt;

    @TaskAction
    fun deployToS3() {
        val file = deployFile.get().asFile
        if (!file.exists()) {
            throw GradleException(&quot;업로드할 파일이 존재하지 않습니다: ${file.absolutePath}&quot;)
        }

        val s3 = S3Client.builder()
            .region(Region.AP_NORTHEAST_2)
            // AWS 키는 로컬 환경변수등으로 보안에 유의하여 관리
            .credentialsProvider(StaticCredentialsProvider.create(awsCredentials))
            .build()

        val bucketName = &quot;dist.update.my-domain.me&quot;
        val targetPath = key.get()

        try {
            // 이미 같은 이름의 파일이 올라가있는지 확인
            val headRequest = HeadObjectRequest.builder()
                .bucket(bucketName)
                .key(targetPath)
                .build()

            try {
                s3.headObject(headRequest)
                logger.quiet(&quot;이미 동일한 파일이 존재하여 S3 업로드를 건너뜁니다. ($targetPath)&quot;)
                return
            } catch (e: NoSuchKeyException) {
                // 파일이 없으므로 정상 업로드 진행
            }

            // 실제 파일 업로드 로직
            val putRequest = PutObjectRequest.builder()
                .bucket(bucketName)
                .key(targetPath)
                .build()

            logger.quiet(&quot;S3 Upload ${file.path} -&amp;gt; s3://${AwsAccessCredential.awsBucket}/${key.get()}&quot;)
            s3.putObject(putRequest, file.toPath())
			
            logger.quiet(&quot;S3 Upload succeeded: ${file.path}&quot;)
            logger.quiet(&quot;&quot;)
            logger.quiet(&quot;&quot;)
            // S3 File URL은 라이브러리에서 리턴하지 않으니 조합하여 제작
            logger.quiet(&quot;S3 File URL: &quot;)
            logger.quiet(&quot;https://s3.ap-northeast-2.amazonaws.com/${bucketName}/${key.get()}&quot;)
            logger.quiet(&quot;&quot;)
            logger.quiet(&quot;&quot;)
        } finally {
            s3.close()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 Task에서 주의해야 할 점은 AwsCredential의 관리에 유의해야 한다는 점이다. AwsCredential 정보가 유출되면 즉시 그 키를 버리고 새로 발급해야 할 정도로 중요한 정보이니 관리에 주의해야한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;커스텀 Task를 빌드 파이프라인에 연결&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에서 만든 커스텀 Task를 앱 Apk 빌드 이후에 실행을 따로 또 해주기 번거로우니 빌드와 S3 업로드를 엮은 &lt;b&gt;빌드&amp;amp;업로드 Task&lt;/b&gt;를 &lt;i&gt;app/build.gradle.kts&lt;/i&gt; 에 만든다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업로드할 파일의 로컬 경로를 넣고, S3에 저장될 경로를 넣어주고, &lt;i&gt;&lt;b&gt;dependsOn &lt;/b&gt;&lt;/i&gt;을 이용해 실행 순서를 맞춰주는 것이 핵심이다.&lt;/p&gt;
&lt;pre id=&quot;code_1776862615654&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// app/build.gradle.kts

...(기존 build.gradle.kts 내용)

tasks.register&amp;lt;DeployToS3Task&amp;gt;(&quot;uploadReleaseApkToS3&quot;) {
    // 반드시 Apk 빌드가 끝난 후에 실행되도록 강제
    dependsOn(&quot;assembleRelease&quot;)

    // 앱 빌드 결과물이 나오는 로컬 경로를 지정
    val apkPath = layout.buildDirectory.file(&quot;outputs/apk/release/app-release.apk&quot;)
    deployFile.set(apkPath)

    // S3에 저장될 대상 경로 지정
    key.set(&quot;mdm/POS/app-release.apk&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 만든 Task를 실행하면 Apk 빌드 및 S3 Upload까지 자동으로 진행이 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추가적으로 기능을 Custom Task나 로직을 추가하는 방식으로 파이프라인에 추가 할 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹훅을 달수도 있고 다른 배포 파이프라인에 엮을 수도 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마무리하며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 작은 커스텀 Task 하나 덕분에 기존에 이루어지던 귀찮은 &lt;b&gt;&lt;i&gt;빌드, S3 로그인, 업로드&lt;/i&gt; &lt;/b&gt;과정이 Task를 실행시키는 명령어 하나로 완전히 자동화되어 단축되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 자동화 환경을 세팅하는 데 아주 긴 시간이 걸린 것은 아니지만, 앞으로 아끼게 될 시간은 훨씬 길 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에 시스템을 만드는 과정이 조금 귀찮더라도, 결국 단순 반복 작업을 줄여주는 자동화는 나의 시간을 더 중요한 일에 쓸 수 있게 한다. 이 긁을 읽는 독자분들도 자동화 시스템 구축을 조금씩이라도 진행하여 시간을 좀 더 알차게 쓰길 바란다.&amp;nbsp;&lt;/p&gt;</description>
      <category>CI-CD</category>
      <category>AWS</category>
      <category>gradle</category>
      <category>s3</category>
      <category>자동화</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/55</guid>
      <comments>https://parkstate.tistory.com/55#entry55comment</comments>
      <pubDate>Wed, 22 Apr 2026 23:40:34 +0900</pubDate>
    </item>
    <item>
      <title>코드 개선은 감이 아니라 분석으로 하는거야</title>
      <link>https://parkstate.tistory.com/54</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사의&amp;nbsp;KMP&amp;nbsp;기반&amp;nbsp;안드로이드&amp;nbsp;POS&amp;nbsp;프로젝트에서,&amp;nbsp;대량의&amp;nbsp;주문&amp;nbsp;데이터를&amp;nbsp;안정적으로&amp;nbsp;처리하기&amp;nbsp;위해&amp;nbsp;비동기&amp;nbsp;큐(requestQueue)를&amp;nbsp;도입했다.&lt;br /&gt;&lt;br /&gt;도입&amp;nbsp;당시에는&amp;nbsp;'비동기&amp;nbsp;처리만&amp;nbsp;붙여도&amp;nbsp;속도&amp;nbsp;이슈가&amp;nbsp;조금은&amp;nbsp;나아지겠지'라는&amp;nbsp;기대가&amp;nbsp;있었다.&amp;nbsp;하지만&amp;nbsp;한편으로는&amp;nbsp;'과연&amp;nbsp;이것만으로&amp;nbsp;완벽히&amp;nbsp;해결될까?'&amp;nbsp;하는&amp;nbsp;막연한&amp;nbsp;불안감도&amp;nbsp;존재했다.&amp;nbsp;막상&amp;nbsp;코루틴을&amp;nbsp;이용해&amp;nbsp;비동기를&amp;nbsp;붙이고&amp;nbsp;테스트해&amp;nbsp;보니&amp;nbsp;슬픈&amp;nbsp;예감은&amp;nbsp;틀리지&amp;nbsp;않았다.&amp;nbsp;일반적인&amp;nbsp;상황에서는&amp;nbsp;속도가&amp;nbsp;미세하게&amp;nbsp;개선되었지만,&amp;nbsp;주문이&amp;nbsp;몰리는&amp;nbsp;가혹&amp;nbsp;환경에서는&amp;nbsp;기대와&amp;nbsp;다르게&amp;nbsp;CPU&amp;nbsp;사용량이&amp;nbsp;폭주하고&amp;nbsp;UI까지&amp;nbsp;심하게&amp;nbsp;버벅이며&amp;nbsp;오히려&amp;nbsp;이전보다&amp;nbsp;더&amp;nbsp;느려지는&amp;nbsp;현상이&amp;nbsp;발생했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스레드를 늘려도 보고 채널도 건드려 보았지만, 이런 것들은 현재 상황의 본질적인 원인이 아니었다. 어차피 속도와 최적화 이슈는 언젠가는 마주하게 되어있다. 이번 기회에 제대로 뿌리를 뽑아보자는 마음으로 Android Profiler를 연결하여 앱의 진짜 상태부터 파헤쳐 보았다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;7dbc5701-3a2d-4d05-b11d-2dfb45ba90db.jpeg&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;395&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/BGLOe/dJMcaiXerOe/6a4HRkyspLfbxKKqn3o6U0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/BGLOe/dJMcaiXerOe/6a4HRkyspLfbxKKqn3o6U0/img.jpg&quot; data-alt=&quot;Android Profiler CPU Trace Log&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/BGLOe/dJMcaiXerOe/6a4HRkyspLfbxKKqn3o6U0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FBGLOe%2FdJMcaiXerOe%2F6a4HRkyspLfbxKKqn3o6U0%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1024&quot; height=&quot;395&quot; data-filename=&quot;7dbc5701-3a2d-4d05-b11d-2dfb45ba90db.jpeg&quot; data-origin-width=&quot;1024&quot; data-origin-height=&quot;395&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Android Profiler CPU Trace Log&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를&amp;nbsp;분석한&amp;nbsp;결과,&amp;nbsp;미흡한&amp;nbsp;DB&amp;nbsp;트랜잭션&amp;nbsp;처리와&amp;nbsp;불필요하게&amp;nbsp;잦은&amp;nbsp;리렌더링이&amp;nbsp;속도&amp;nbsp;저하의&amp;nbsp;핵심&amp;nbsp;원인이라고&amp;nbsp;결론지었다.&lt;br /&gt;&lt;br /&gt;좀&amp;nbsp;더&amp;nbsp;정확히&amp;nbsp;말하자면,&amp;nbsp;코틀린과&amp;nbsp;SQLite,&amp;nbsp;그리고&amp;nbsp;Android&amp;nbsp;WebView가&amp;nbsp;연결되는&amp;nbsp;파이프라인&amp;nbsp;구조를&amp;nbsp;시스템&amp;nbsp;레벨에서&amp;nbsp;전혀&amp;nbsp;고려하지&amp;nbsp;못했던&amp;nbsp;것이다.&amp;nbsp;상황을&amp;nbsp;복기해&amp;nbsp;보면&amp;nbsp;다음과&amp;nbsp;같은&amp;nbsp;패착들이&amp;nbsp;있었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;비동기 큐에 대한 맹신&lt;br /&gt;백그라운드의 코루틴으로 작업을 넘겼으니, 메인 스레드는 자유로워져서 겉보기에는 당연히 빨라질 거라고만 생각했다.&lt;/li&gt;
&lt;li&gt;미흡한 트랜잭션 처리로 인한 무수한 DB I/O&lt;br /&gt;현재 Ktorm + SQLite 조합을 사용 중인데, 큐에서 데이터를 꺼내서 처리할 때마다 단건으로 insert나 update를 날렸다.&lt;br /&gt;데이터 한 건을 처리할 때마다 무수히 DB 파일을 열고 닫으며 JNI를 호출해 네이티브로 넘기니, 프로그램이 느려질 수 밖에 없었다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;정리하자면 이렇다.&amp;nbsp;&lt;br /&gt;&lt;br /&gt;&lt;b&gt;우리가&amp;nbsp;했던&amp;nbsp;것&amp;nbsp;(느렸던&amp;nbsp;방식)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마구잡이의 비동기 큐 사용&lt;/li&gt;
&lt;li&gt;단건 단위의 순차적인 SQLite Insert&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우리가&amp;nbsp;했어야&amp;nbsp;할&amp;nbsp;것&amp;nbsp;(이상적인&amp;nbsp;방식)&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;명시적인 DB 트랜잭션(Ktorm의 useTransaction)으로 묶어 파일 I/O 최소화&lt;/li&gt;
&lt;li&gt;데이터 폭주 같은 악조건까지 고려한 데이터 업데이트 주기 및 레이어 최적화&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제는 DB에 대한 접근 방식만 고쳐도 수십 배의 성능 향상을 가져올 수 있을 것으로 보였다. 즉시 개별 Insert 로직들을 Batch와 Transaction으로 묶어서 일괄 처리하도록 리팩토링을 진행했다. 추가로 프론트엔드로 넘어가는 데이터 업데이트 주기를 조절하여 리렌더링 부하도 함께 낮췄다.&lt;br /&gt;&lt;br /&gt;결과는&amp;nbsp;대성공이었다.&lt;br /&gt;기존에&amp;nbsp;주문과&amp;nbsp;결제가&amp;nbsp;일어날&amp;nbsp;때&amp;nbsp;수백&amp;nbsp;번씩&amp;nbsp;날아가던&amp;nbsp;DB&amp;nbsp;Commit이&amp;nbsp;단&amp;nbsp;몇&amp;nbsp;십&amp;nbsp;건&amp;nbsp;단위로&amp;nbsp;줄어들었고,&amp;nbsp;마감이나&amp;nbsp;개점&amp;nbsp;같은&amp;nbsp;무거운&amp;nbsp;작업에서도&amp;nbsp;눈에&amp;nbsp;띄는&amp;nbsp;속도&amp;nbsp;개선이&amp;nbsp;이루어졌다.&lt;br /&gt;&lt;br /&gt;가장&amp;nbsp;놀라운&amp;nbsp;체감&amp;nbsp;성능은&amp;nbsp;하드웨어&amp;nbsp;스펙에서&amp;nbsp;나타났다.&amp;nbsp;기존에는&amp;nbsp;Rockchip&amp;nbsp;rk3588&amp;nbsp;보드&amp;nbsp;정도의&amp;nbsp;고스펙&amp;nbsp;장비여야&amp;nbsp;쓸만하다고&amp;nbsp;느껴졌던&amp;nbsp;우리&amp;nbsp;POS&amp;nbsp;앱이,&amp;nbsp;이제는&amp;nbsp;한참&amp;nbsp;낮은&amp;nbsp;스펙인&amp;nbsp;Rockchip&amp;nbsp;rk3399&amp;nbsp;보드에서도&amp;nbsp;충분히&amp;nbsp;빠르다고&amp;nbsp;느껴질&amp;nbsp;정도로&amp;nbsp;비약적인&amp;nbsp;개선을&amp;nbsp;이루어냈다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국&amp;nbsp;우리&amp;nbsp;프로그램을&amp;nbsp;실제로&amp;nbsp;구원한&amp;nbsp;것은,&amp;nbsp;감에&amp;nbsp;의존해&amp;nbsp;맹목적으로&amp;nbsp;추가했던&amp;nbsp;비동기&amp;nbsp;처리가&amp;nbsp;아니라&amp;nbsp;'데이터&amp;nbsp;추적을&amp;nbsp;통한&amp;nbsp;시스템&amp;nbsp;분석과&amp;nbsp;구조적인&amp;nbsp;접근'이었다.&lt;br /&gt;&lt;br /&gt;처음에 진행했던 단순 비동기 처리는 현재 프로그램의 병목 상태를 전혀 고려하지 않은 채 '믿음'만으로 진행했고, 그렇기에 실패할 수밖에 없었다. 하지만 Android Profiler 등 객관적인 툴로 데이터를 추적하여 현재 상태를 명확히 파악하고 나니, 개선 근거가 뚜렷해졌고 정확한 조치를 통해 문제를 해결할 수 있었다.&lt;br /&gt;&lt;br /&gt;물론&amp;nbsp;데이터를&amp;nbsp;수집하고,&amp;nbsp;분석하고,&amp;nbsp;콜스택을&amp;nbsp;뒤져보는&amp;nbsp;일은&amp;nbsp;생각보다&amp;nbsp;훨씬&amp;nbsp;피곤하고&amp;nbsp;고생스러운&amp;nbsp;과정이다.&amp;nbsp;하지만&amp;nbsp;미래에&amp;nbsp;이&amp;nbsp;프로젝트를&amp;nbsp;유지보수할&amp;nbsp;누군가(아마도&amp;nbsp;높은&amp;nbsp;확률로&amp;nbsp;나&amp;nbsp;자신)를&amp;nbsp;위해서는&amp;nbsp;결국&amp;nbsp;시스템의&amp;nbsp;현재&amp;nbsp;상황을&amp;nbsp;정확히&amp;nbsp;직시해야만&amp;nbsp;한다.&lt;br /&gt;&lt;br /&gt;지금 이 순간에도 &quot;대체 왜 느린 거지?&quot; 라며 막막한 성능 이슈로 고통받고 있을 동료 개발자들에게 나는 이 사례를 말해주며 이 조연을 꼭 전하고 싶다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;i&gt;개선을 진행하기 전에 프로젝트의 현재 상태부터 분석하기.&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;</description>
      <category>일상/개발 관련 생각</category>
      <category>Android</category>
      <category>db</category>
      <category>Kotlin</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/54</guid>
      <comments>https://parkstate.tistory.com/54#entry54comment</comments>
      <pubDate>Wed, 15 Apr 2026 23:46:41 +0900</pubDate>
    </item>
    <item>
      <title>프로젝트 오픈은 왜 항상 고통스러울까?</title>
      <link>https://parkstate.tistory.com/53</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 반년 간 도맡아 준비했던 프로젝트를 드디어 오픈했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;준비하는 내내 '오픈만 하면 숨 좀 돌릴 수 있겠지'라는 마음이 컸다. 하지만 그건 그저 순진한 바람일 뿐이라는 걸 내심 알고 있었다. 막상 서비스가 라이브 환경에 배포되고 나면, 개발 환경에서는 전혀 예상치 못했던 기상천외한 예외 상황과 기획 수정 요청이 얽히고설켜 오히려 오픈 전보다 일이 더 많아지기 일쑤다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;이전 프로젝트 때도 그랬고, 이번에도 마찬가지다. 개발자로 일하는 이상 프로젝트 오픈은 숙명처럼 찾아온다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;나는 이런 숙명을 피할 수 없기에, &lt;b data-index-in-node=&quot;70&quot; data-path-to-node=&quot;10&quot;&gt;왜 매번 이런 고통스러운 상황이 반복되는지, 그리고 어떻게 이 굴레를 끊어낼 수 있을지&lt;/b&gt; 한 번쯤 깊게 고민해 볼 필요가 있었다. (물론 고민한다고 당장 모든 게 마법처럼 해결되진 않는다...)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;원인&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&quot;테스트할 시간과 자원의 부재&quot;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 내린 결론은 결국 '테스트 부족'이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좀 더 정확히 말하자면, 충분히 테스트할 수 있는 시간과 환경의 부재다. 상황을 복기해 보면 패턴은 늘 비슷하다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;13&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,0,0&quot;&gt;일정에 쫓기는 개발:&lt;/b&gt; 오픈 일정을 맞추기 위해 바쁘게 기능 구현만 치고 나간다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,1,0&quot;&gt;테스트의 우선순위 하락:&lt;/b&gt; 인력과 시간이 부족하다 보니 자연스럽게 테스트는 뒷전이 된다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,2,0&quot;&gt;정상적인 흐름 위주의 검증:&lt;/b&gt; 다양한 엣지 케이스를 고려하지 못하고, 가장 이상적이고 정상적인 흐름(Happy Path)에서만 기본적인 확인을 거친다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,3,0&quot;&gt;운영 환경의 변수:&lt;/b&gt; 하지만 실제 사용자들은 우리가 예상한 '정상적인 상황'으로만 서비스를 이용하지 않는다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;13,4,0&quot;&gt;무한 버그 수정의 굴레:&lt;/b&gt; 미처 고려하지 못했던 예외 상황들이 터져 나오며 끊임없는 핫픽스와 수정 작업이 발생한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-path-to-node=&quot;14&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14&quot;&gt;우리가 했던 것 (부족했던 테스트)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단편적인 유닛 테스트&lt;/li&gt;
&lt;li&gt;정상 상황에 대한 E2E 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;16&quot; data-ke-size=&quot;size16&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;16&quot;&gt;우리가 했어야 할 것 (이상적인 테스트)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;17&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;촘촘한 커버리지의 유닛 테스트&lt;/li&gt;
&lt;li&gt;모듈 간의 연결을 확인하는 통합 테스트&lt;/li&gt;
&lt;li&gt;정상뿐만 아니라 &lt;b data-index-in-node=&quot;9&quot; data-path-to-node=&quot;17,2,0&quot;&gt;비정상적인(예외) 상황까지 고려한&lt;/b&gt; E2E 테스트&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;해결 방안&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 악순환을 끊기 위한 해결 방안을 효과가 확실한 순서대로 고민해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;20&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;충분한 테스트 기간을 포함하여 현실적인 일정 산정하기&lt;/li&gt;
&lt;li&gt;QA 등 테스트 전문 인력과 자원 확대하기&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,2,0&quot;&gt;테스트 자동화 구축하기&lt;/b&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;20,2,0&quot;&gt;&lt;/b&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%; height: 68px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style12&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;해결 방안&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;원인 해결 정도&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;회사 입장 실현 비용&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;실무자 입장 실현 비용&lt;/b&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;테스트 기간 포함 일정 산정&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;높음&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;높음&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;낮음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;테스트 인력 및 자원 확대&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;중간&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;중간&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;중간&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 17px;&quot;&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;&lt;b&gt;테스트 자동화 구축&lt;/b&gt;&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;낮음&lt;/td&gt;
&lt;td style=&quot;width: 25%; height: 17px; text-align: center;&quot;&gt;높음&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;표로 정리해 보니 현실적인 해결책이 더욱 명확해진다. 가장 근본적인 해결책인 '충분한 일정 확보'는 회사의 사업 및 영업상 요구사항과 직결되므로 실현 가능성이 매우 낮다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;결국 당장 내가 통제할 수 있는 현실적인 대안은 &lt;b data-index-in-node=&quot;27&quot; data-path-to-node=&quot;24&quot;&gt;기존 실무 역량을 활용해 '테스트를 자동화'하는 것&lt;/b&gt;이다. 당장 코드를 짜기에도 바쁜 실무자 입장에서는 초기 구축 비용(시간과 노력)이 꽤 높게 들겠지만, 장기적인 평화를 위해서는 피할 수 없는 선택이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;24&quot; data-ke-size=&quot;size16&quot;&gt;(당장 코드를 짜기에도 바쁜 실무자는 나다..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;결론&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;결국에 답은 자동화다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당장 프레임워크를 설정하고 테스트 코드를 짜는 것이 고생스럽겠지만, 미래의 나를 구원하기 위해서는 결국 테스트 자동화 환경을 고려해야만 한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;27&quot; data-ke-size=&quot;size16&quot;&gt;나뿐만 아니라 수많은 회사에서 고군분투하는 개발자들이 비슷한 고민을 거쳤을 것이다. 먼저 이 길을 걸어간 선배 개발자들의 레퍼런스들을 참고하면서, 빠르진 않더라도 내 프로젝트에 맞는 테스트 자동화 방안을 하나씩 적용해 나가며 이 문제를 주도적으로 해결하여 편해져 보려 한다.&lt;/p&gt;</description>
      <category>일상/개발 관련 생각</category>
      <category>고민</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/53</guid>
      <comments>https://parkstate.tistory.com/53#entry53comment</comments>
      <pubDate>Wed, 25 Mar 2026 18:17:52 +0900</pubDate>
    </item>
    <item>
      <title>mac os에서 adb 응답 없음 &amp;amp; SDK 업데이트 &amp;quot;Stopping ADB...&amp;quot; 무한 로딩 해결</title>
      <link>https://parkstate.tistory.com/52</link>
      <description>&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;평소처럼 안드로이드 개발을 하던 중, 갑자기 터미널에서 adb 명령어가 아무런 응답을 하지 않기 시작했습니다. adb kill-server를 입력해도 커서만 깜빡일 뿐 진전이 없었고, 아무 반응이 없어서 Command+C로 탈출하였습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;8&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;8&quot;&gt;제가 시도해 본 방법&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 adb의 path가 잘못 잡혀있나 싶어서 확인해 보니 해당 경로는 잘 잡혀 있었습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;그래서 SDK Platform-Tools를 Android Studio의 SDK manager에서 업데이트 또는 재설치를 진행하기로 하였습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;SDK manager를 들어가보니 업데이트가 있어서 지웠다 깔기 대신 최신 버전으로 업데이트를 진행하였습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;하지만 업데이트 과정이&amp;nbsp;&lt;b&gt;&quot;Stopping ADB...&quot; &lt;/b&gt;로그만 뜨고 업데이트가 진행이 되지 않았습니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10&quot;&gt;원인&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;원인은 기존에 실행 중이던 adb 프로세스가 죽지 않고 교착상태에 빠졌기 때문이었습니다. SDK Manager는 업데이트를 위해 기존 adb를 종료시키려 하지만, 응답이 없는 프로세스가 종료 신호를 무시하면서 전체 설치 과정이 멈춰버린 것입니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;12&quot;&gt;해결&lt;/b&gt;&lt;/h3&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;이럴 때는 기다린다고 해결되지 않으며, OS 레벨에서 프로세스를 강제로 종료해야 합니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;저의 해결 단계는 이랬습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;1. 터미널을 열고 adb에 대한 killall 명령어&lt;/p&gt;
&lt;pre id=&quot;code_1773836947457&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;killall -9 adb&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. killall이 안먹어서 adb 프로세스를 찾아서 각각 하나하나 pid 입력하여 kill&lt;/p&gt;
&lt;pre id=&quot;code_1773837085442&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# adb 프로세스 찾기
ps aux | grep adb

# 출력된 결과의 PID 확인 후 번호를 넣어 실행
kill -9 [PID번호]&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2번 단계를 진행하자마자 SDK manager의 업데이트 창에 로그가 &lt;b&gt;&quot;Stopping ADB...&quot;&lt;/b&gt;에서 다음으로 진행되기 시작했고, 업데이트가 마무리되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;업데이트 이후,&lt;span style=&quot;background-color: #dddddd;&quot;&gt; adb devices&amp;nbsp;&lt;/span&gt; 명령어도 잘 작동하였습니다.&lt;/p&gt;</description>
      <category>Java-Kotlin/Android</category>
      <category>ADB</category>
      <category>Android</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/52</guid>
      <comments>https://parkstate.tistory.com/52#entry52comment</comments>
      <pubDate>Wed, 18 Mar 2026 21:36:55 +0900</pubDate>
    </item>
    <item>
      <title>SQLite VACUUM 명령어를 통하여 안전하게 DB 백업</title>
      <link>https://parkstate.tistory.com/51</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;Android POS/KIOSK를 개발하고 실제 운영하다보니 개발에서는 고려하지 못한 문제들이 한번씩 터지고, 그럴 때마다 데이터를 살리기 위한 DB 백업이 필요하다고 느꼈습니다.&amp;nbsp;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB 백업이 있으면 어떤 시점이후 데이터는 날아갈 수 있어도, 그 시점 이전의 데이터는 모두 확정적으로 살릴 수 있기 때문입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DB 백업이 없을 때의 데이터 복원:&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;모든 LOG를 수집&lt;/li&gt;
&lt;li&gt;LOG를 기반으로 모든 데이터를 수작업으로 복원&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;DB 백업이 있을 때의 데이터 복원:&lt;/h4&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;DB 직전 백업 시점까지의 데이터는 DB 백업을 복원&lt;/li&gt;
&lt;li&gt;나머지 DB 백업 시점 이후의 데이터는 LOG를 이용하여 복원&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제가 터졌을 때 LOG를 모두 수집하여 수작업으로 복원하는데에는 엄청난 시간도 걸리고, 중간중간 복원하는 사람이 빼먹거나 하면 데이터가 없어진다는 명확한 문제가 있습니다. 하지만 DB 백업을 어떤 단위로 진행하게 된다면 그 DB 백업의 마지막 이후의 데이터만 수작업으로 복원하면 되기 때문에 복원하는 시간도 획기적으로 줄고 백업 시점 이전의 데이터를 잃어버릴 일도 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이러한 명확한 장점이 있었기에 DB 백업 로직을 추가하기로 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;DB 백업시 고려해야 할 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 제가 개발하고 있는 POS/KIOSK 프로젝트는 Windows와 Android를 모두 지원하기 때문에 각각 운영체제의 DB로 &lt;b&gt;&lt;i&gt;MSSql&lt;/i&gt;&lt;/b&gt;과 &lt;b&gt;&lt;i&gt;SQLite&lt;/i&gt;&lt;/b&gt;를 지원합니다.&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot; data-ke-style=&quot;style4&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Windows&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;Android&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;MSSql&lt;/td&gt;
&lt;td style=&quot;width: 50%;&quot;&gt;SQLite&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 MSSql은 자동 백업 스케줄러와 트리거로 백업 로직을 구현하기로 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 SQLite는 자동 백업 스케줄러가 따로 없으므로 방법을 고민하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;SQLite DB 백업 방법에 대한 고민&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 그냥 단순히 File DB니까 DB File을 직접 복사해줄까 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 DB File을 그냥 복사하자니 SQLite에서 쓰기 작업을 하는 도중에 복사하면 파일이 복사되는 중에 쓰기 작업 때문에 데이터가 잘못될 수 있는 위험이 있을 수 있다는 생각이 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 SQLite에서 지원하는 방법이 없나 찾아보니 다른 DB에서는 없는&amp;nbsp;&lt;b&gt;VACUUM&amp;nbsp;&lt;/b&gt;이라는 명령어를 찾았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://sqlite.org/lang_vacuum.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://sqlite.org/lang_vacuum.html&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1772632319393&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;VACUUM&quot; data-og-description=&quot;1. Syntax vacuum-stmt: hide VACUUM schema-name INTO filename 2. Description The VACUUM command rebuilds the database file, repacking it into a minimal amount of disk space. There are several reasons an application might do this: Unless SQLite is running in&quot; data-og-host=&quot;sqlite.org&quot; data-og-source-url=&quot;https://sqlite.org/lang_vacuum.html&quot; data-og-url=&quot;https://sqlite.org/lang_vacuum.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://sqlite.org/lang_vacuum.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://sqlite.org/lang_vacuum.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;VACUUM&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;1. Syntax vacuum-stmt: hide VACUUM schema-name INTO filename 2. Description The VACUUM command rebuilds the database file, repacking it into a minimal amount of disk space. There are several reasons an application might do this: Unless SQLite is running in&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;sqlite.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;VACUUM&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 첨부한 SQLite doc에 따르면 VACUUM 명령어는 DB 내부의 빈 공간을 최적화하는 명령어입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;VACUUM 명령어는 기본적으로 DB File에서 많은 데이터가 삭제되면 빈 공간이 남아 파편화되는데, 이 빈 공간들을 제거하고, 데이터를 파일에 재배치 하는 명령어입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적인 VACUUM 명령어의 사용법 말고도 doc의 2.1을 보면, VACUUM에 INTO를 붙여 &lt;b&gt;VACUUM INTO '[PATH]' &lt;/b&gt;를 사용하면, 현재 사용 중인 DB를 스냅샷으로 찍어 새로운 파일로 저장합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;File 복사보다 VACUUM이 좋은 이유&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;File 복사에는 없는 VACUUM의 장점은 아래와 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,1,0,0&quot;&gt;Online Backup:&lt;/b&gt; 서비스 중단 없이 실시간 백업 가능.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,1,1,0&quot;&gt;Single File:&lt;/b&gt; WAL 파일 등을 신경 쓸 필요 없이 깔끔한 .db 파일 하나로 병합해줌.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;9,1,1,2,0&quot;&gt;Auto-Optimization:&lt;/b&gt; 백업과 동시에 파일 용량까지 최적화됨.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 저는 위와 같은 장점이 있기에 VACUUM을 사용하여 Android 환경에서 DB를 백업하기로 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;실제 적용 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 Android + Kotlin + Ktorm을 사용하고 있어서 해당 로직을 이런식으로 개발하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1772633095163&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * SQLite DB file 백업하는 로직.
 */
override suspend fun backup() {
    val rawUrl = database.url // jdbc:sqlite:/.../CROSS_POS_DB?journal_mode=WAL

    // SQLite DB 인지 먼저 체크
    if (!rawUrl.contains(&quot;jdbc:sqlite&quot;)) {
        logger.warn(&quot;Not a SQLite database. Skipping backup.&quot;)
        return
    }

    // 1. 순수 파일 경로 추출
    val purePath = rawUrl.removePrefix(&quot;jdbc:sqlite:&quot;).substringBefore(&quot;?&quot;)
    val originalDbFile = File(purePath)

    // 2. 백업 폴더 경로 설정 (원본 DB가 있는 폴더 아래 'backups' 폴더 생성)
    // 원본이 /Download/DB 라면 /Download/backups/ 가 됩니다.
    val backupDir = File(originalDbFile.parent, &quot;backups&quot;)

    // 3. 폴더가 없으면 생성
    if (!backupDir.exists()) {
        val created = backupDir.mkdirs()
        logger.info(&quot;Db Backup directory created: $created at ${backupDir.absolutePath}&quot;)
    }

    // 4. 백업 파일명 생성 (예: CROSS_POS_DB_20260304_153000.db)
    val timestamp = SimpleDateFormat(&quot;yyyyMMdd_HHmmss&quot;, Locale.getDefault()).format(Date())
    val backupFile = File(backupDir, &quot;${originalDbFile.name}_$timestamp.db&quot;)

    logger.info(&quot;DB BACKUP START! SOURCE=$purePath, TARGET=${backupFile.absolutePath}&quot;)

    // 5. 실행
    try {
        database.useConnection { connection -&amp;gt;
            if (backupFile.exists()) backupFile.delete()

            connection.prepareStatement(&quot;VACUUM INTO ?&quot;).use { stmt -&amp;gt;
                stmt.setString(1, backupFile.absolutePath)
                stmt.execute()
            }
        }
        logger.info(&quot;DB BACKUP SUCCESS: ${backupFile.absolutePath}&quot;)
    } catch (e: Exception) {
        logger.error(&quot;DB BACKUP FAILED&quot;, e)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java-Kotlin/Android</category>
      <category>Android</category>
      <category>Ktorm</category>
      <category>sqlite</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/51</guid>
      <comments>https://parkstate.tistory.com/51#entry51comment</comments>
      <pubDate>Wed, 4 Mar 2026 23:07:14 +0900</pubDate>
    </item>
    <item>
      <title>Android WebView에서 이어폰 연결 상태 감지하기 (JS Bridge)</title>
      <link>https://parkstate.tistory.com/49</link>
      <description>&lt;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;1. Android WebView에서 직접 이어폰 연결 상태를 감지할 수 없나요?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예. 안드로이드의 WebView에서 직접 안드로이드 기기에 오디오 기기를 꽂았다는 상태를 알 수 없습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;웹 표준(HTML5)만으로는 하드웨어 단의 연결 상태를 직접 알 수 없음.&lt;/li&gt;
&lt;li&gt;특히 일반 이어폰(3.5mm)과 달리 &lt;b data-index-in-node=&quot;21&quot; data-path-to-node=&quot;8,1,0&quot;&gt;USB-C 타입 오디오 장치&lt;/b&gt;는 시스템에서 TYPE_USB_HEADSET으로 별도 관리됨.&lt;/li&gt;
&lt;li&gt;일반 이어폰을 꽂는 동작은 &lt;b&gt;ACTION_HEADSET_PLUG&lt;/b&gt;으로 관리됨.&lt;/li&gt;
&lt;li&gt;이를 해결하기 위해 &lt;b data-index-in-node=&quot;11&quot; data-path-to-node=&quot;8,2,0&quot;&gt;Android 네이티브 코드에서 감지하고 안드로이드 WebView(JS)로 브릿지&lt;/b&gt;를 통해 신호를 주는 전략이 필요함.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;2. 구현&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 해당 기능을 구현 할 때 BroadcastReceiver와 AudioDeviceCallback 둘중 하나를 선택해서 구현해야 했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 중에서 저는 BroadcastReceiver를 선택하여 구현했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;BroadcastReceiver의 장점&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,0,0&quot;&gt;간결함:&lt;/b&gt; AudioManager를 뒤져서 오디오 디바이스 목록을 순회할 필요가 없습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,1,0&quot;&gt;낮은 API 레벨 호환성:&lt;/b&gt; 아주 오래된 안드로이드 버전부터 최신 버전까지 동일하게 작동합니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;10,2,0&quot;&gt;즉각성:&lt;/b&gt; 시스템이 하드웨어 변화를 감지하자마자 인텐트를 쏴주므로 반응 속도가 빠릅니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;코드&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1771469938933&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// 1. Receiver 정의
val receiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        if (intent?.action == Intent.ACTION_HEADSET_PLUG) {
            // state: 0 은 unplugged, 1 은 plugged
            val state = intent.getIntExtra(&quot;state&quot;, -1)
            val isPlugged = state == 1
            
            // 2. WebView의 JavaScript 함수 호출 (JS Bridge)
            val script = &quot;if (window.onHeadsetChange) { window.onHeadsetChange($isPlugged); }&quot;
            webView.post {
                webView.evaluateJavascript(script, null)
            }
        }
    }
}

// 3. Receiver 등록
val filter = IntentFilter(Intent.ACTION_HEADSET_PLUG)
currentContext.registerReceiver(receiver, filter)

// 4. 메모리 누수 방지를 위한 Dispose
onDispose {
    currentContext.unregisterReceiver(receiver)
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Java-Kotlin/Android</category>
      <category>Android</category>
      <category>Kotlin</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/49</guid>
      <comments>https://parkstate.tistory.com/49#entry49comment</comments>
      <pubDate>Thu, 19 Feb 2026 13:12:33 +0900</pubDate>
    </item>
    <item>
      <title>Android 14(API 34)에서 USB Serial 장치 연결 시 권한에 의한 Crash 트러블슈팅</title>
      <link>https://parkstate.tistory.com/48</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://parkstate.tistory.com/40&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;2025.12.03 - [Java-Kotlin/Android] - Android에서 USB Serial 장치 연결 시 USB 권한 문제 해결기&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1770818399358&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;Android에서 USB Serial 장치 연결 시 USB 권한 문제 해결기&quot; data-og-description=&quot;최근 안드로이드 프로젝트에서 POS장비에 USB Serial 포트 통신 기능을 구현하는 중에 예상치 못한 권한 문제를 마주쳤습니다.저는 이미 AndroidManifest.xml에 USB 관련 Permission을 다 줘놨는데도 권한 문&quot; data-og-host=&quot;parkstate.tistory.com&quot; data-og-source-url=&quot;https://parkstate.tistory.com/40&quot; data-og-url=&quot;https://parkstate.tistory.com/40&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bMkjbj/dJMb9lk29Mx/beVk3R9Nd4l4XsHYXjCew1/img.png?width=41&amp;amp;height=40&amp;amp;face=0_0_41_40,https://scrap.kakaocdn.net/dn/gX5fh/dJMb9cBD6ml/TlKDj9cxd4gKmnUwCSIl3K/img.png?width=41&amp;amp;height=40&amp;amp;face=0_0_41_40&quot;&gt;&lt;a href=&quot;https://parkstate.tistory.com/40&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://parkstate.tistory.com/40&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bMkjbj/dJMb9lk29Mx/beVk3R9Nd4l4XsHYXjCew1/img.png?width=41&amp;amp;height=40&amp;amp;face=0_0_41_40,https://scrap.kakaocdn.net/dn/gX5fh/dJMb9cBD6ml/TlKDj9cxd4gKmnUwCSIl3K/img.png?width=41&amp;amp;height=40&amp;amp;face=0_0_41_40');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Android에서 USB Serial 장치 연결 시 USB 권한 문제 해결기&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;최근 안드로이드 프로젝트에서 POS장비에 USB Serial 포트 통신 기능을 구현하는 중에 예상치 못한 권한 문제를 마주쳤습니다.저는 이미 AndroidManifest.xml에 USB 관련 Permission을 다 줘놨는데도 권한 문&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;parkstate.tistory.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이전에 Android에서 USB Serial 장치를 연결 한적이 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 코드를 이용하여 RFID Reader를 잘 사용 하고 있었습니다. 그러나 키오스크 타겟 장비가 기존보다 좀 더 높은 Android 버전을 가지게 되어 테스트 해보니 Reader를 연결할 때 크래시가 발생하며 앱이 죽는 이슈가 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글은 그 해결 과정을 기록한 트러블슈팅기 입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;배경&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;POS 장비와 연동되는 RFID Reader를 &lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;usb-serial-for-android&lt;/span&gt;&lt;/b&gt; 라이브러리를 사용해 연결하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 Android target 버전은 Android 12 였습니다. (API 31)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 장비가 타겟 장비가되어 Android target 버전을 Android 14 (API 34)까지 올렸습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android 버전을 올리자 RFID Reader를 연결시 앱이 죽는 이슈가 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Trouble 1: PendingIntent security 정책 위반&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 실행하고 RFID Reader를 연결하려 권한을 요청하면 앱이 죽는다는 사실을 발견했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Error Log&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;java.lang.IllegalArgumentException:&amp;nbsp;org.imtsoft.pos.android:&amp;nbsp;Targeting&amp;nbsp;U+&amp;nbsp;(version&amp;nbsp;34&amp;nbsp;and&amp;nbsp;above)&amp;nbsp;disallows&amp;nbsp;creating&amp;nbsp;or&amp;nbsp;retrieving&amp;nbsp;a&amp;nbsp;PendingIntent&amp;nbsp;with&amp;nbsp;FLAG_MUTABLE,&amp;nbsp;an&amp;nbsp;implicit&amp;nbsp;Intent&amp;nbsp;within&amp;nbsp;and&amp;nbsp;without&amp;nbsp;FLAG_NO_CREATE&amp;nbsp;and&amp;nbsp;FLAG_ALLOW_UNSAFE_IMPLICIT_INTENT&amp;nbsp;for&amp;nbsp;security&amp;nbsp;reasons.&amp;nbsp;...&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;원인 분석&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android 12 버전부터 &lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;PendingIntent&lt;/span&gt; &lt;/b&gt;생성시 Mutable, Immutable 등의 가변성을 명시해주었어야 했다는 것을 알고 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 Android 14 버전부터는 보안 정책이 더 강화되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Implicit Intent(암시 인텐트)를 사용 하는 PendingIntent는 반드시 &lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;FLAG_IMMUTABLE&lt;/span&gt; &lt;/b&gt;플래그로 설정해야 합니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저의 코드는 &lt;span style=&quot;background-color: #dddddd;&quot;&gt;ACTION_USB_PERMISSION&lt;/span&gt; 만 넣은 Implicit Intent였는데, 플래그를 &lt;b&gt;FLAG_MUTABLE &lt;/b&gt;로 설정해놓았었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 보안 정책 위반으로 앱에서 크래시를 띄운것이 앱이 계속 죽는 원인이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;USB 권한 요청 Intent는 내용이 수정될 일이 없으므로, &lt;span style=&quot;background-color: #dddddd;&quot;&gt;FLAG_MUTABLE&lt;/span&gt;&amp;nbsp; -&amp;gt; &lt;b&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;FLAG_IMMUTABLE&lt;/span&gt; &lt;/b&gt;로 변경하여 PendingIntent 정책 위반으로 인한 크래시를 해결하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Trouble 2: 권한 비동기 처리 미흡&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Trouble 1을 해결하니 권한 팝업이 뜨긴 했습니다. 그러나 &lt;b&gt;허용&amp;nbsp;&lt;/b&gt;버튼을 누르기 전에 앱이 죽었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Error Log&lt;/b&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;java.lang.IllegalArgumentException: Connection is null at com.hoho.android.usbserial.driver.CommonUsbSerialPort.open(CommonUsbSerialPort.java:119) ...&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;원인 분석&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱이 죽는 원인은 코드의 실행 순서였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #dddddd;&quot;&gt;&lt;b&gt;requestPermission&lt;/b&gt;&lt;/span&gt;은 비동기로 동작합니다. 그러니까, 사용자가 권한 허용 팝업에서 &lt;b&gt;허용&amp;nbsp;&lt;/b&gt;버튼을 누르기 전까지 코드가 멈춰있는게 아니고, &lt;b&gt;다음 줄을 바로 실행&lt;/b&gt;합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;문제가 된 코드 흐름&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;connect() 함수 호출&lt;/li&gt;
&lt;li&gt;권한 확인 -&amp;gt; 없음 -&amp;gt; requestPermission() 호출&lt;/li&gt;
&lt;li&gt;팝업 뜸&lt;/li&gt;
&lt;li&gt;(사용자가 권한 허용 버튼을 누르기 전에) 바로 그 다음 줄 코드 실행&lt;/li&gt;
&lt;li&gt;manager.openDevice(device) 호출 -&amp;gt; 권한이 없어서 &lt;b&gt;null&lt;/b&gt; 반환&lt;/li&gt;
&lt;li&gt;port.open(&lt;b&gt;null&lt;/b&gt;) 실행 -&amp;gt; 앱 죽음&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;b&gt;해결&lt;/b&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;연결 로직을 별도 함수로 분리하고, 권한이 있을 때와 권한을 획득했을 때의 Callback만 해당 함수를 호출하도록 구조를 수정했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style1&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최종 코드&lt;/b&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 문제들을 해결하고, 안정적으로 동작하는 최종 코드는 이렇게 되었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1770819973117&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;/**
 * RFID Reader 연결 어댑터
 */
fun connect() {
    // ... (드라이버 찾기 로직) ...

    rfidDrivers.forEach { driver -&amp;gt;
        if (manager.hasPermission(driver.device)) {
            // 이미 권한이 있는 경우: 즉시 연결
            val conn = manager.openDevice(driver.device)
            if (conn != null) {
                connectToPort(driver, conn)
            }
        } else {
            // 권한이 없는 경우: 요청 후 대기 (비동기)
            requestPermission(driver.device) { device -&amp;gt;
                // 사용자가 허용 버튼을 누르면 이 Callback이 실행
                val conn = manager.openDevice(device)
                if (conn != null) {
                    connectToPort(driver, conn)
                }
            }
            // 여기서 함수를 종료하여 권한 없이 port.open을 시도하지 않도록 함
            return 
        }
    }
}

// 실제 연결 로직 수행하는 함수 (Connection이 확실히 있을 때만 호출됨)
private fun connectToPort(driver: UsbSerialDriver, conn: UsbDeviceConnection) {
    try {
        val port = driver.ports[0]
        port.open(conn) // 여기서 conn은 절대 null이 아님
        port.setParameters(9600, 8, UsbSerialPort.STOPBITS_1, UsbSerialPort.PARITY_NONE)
        ports.add(port)
        logger.info(&quot;Connected successfully&quot;)
    } catch (e: Exception) {
        logger.error(&quot;Connection error&quot;, e)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;최종 요약&lt;/b&gt;&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Android 14에서 암시 인텐트 사용시 FLAG_IMMUTABLE 플래그를 꼭! 설정 해야 함.&lt;/li&gt;
&lt;li&gt;비동기 처리에 주의.&lt;/li&gt;
&lt;li&gt;Null 일때 포트를 오픈할 시 앱이 죽을 수 있으니 꼭 방어 코드를 작성.&lt;/li&gt;
&lt;/ol&gt;</description>
      <category>Java-Kotlin/Android</category>
      <category>Android</category>
      <category>Kotlin</category>
      <category>트러블슈팅</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/48</guid>
      <comments>https://parkstate.tistory.com/48#entry48comment</comments>
      <pubDate>Wed, 11 Feb 2026 23:29:16 +0900</pubDate>
    </item>
    <item>
      <title>[Ktorm] MSSQL &amp;quot;String or binary data would be truncated&amp;quot; 트러블 슈팅 (varchar vs text)</title>
      <link>https://parkstate.tistory.com/47</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2259&quot; data-origin-height=&quot;708&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bCEzKH/dJMcahwqF4U/Kkyi9DMGgd1UYyLk0c67kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bCEzKH/dJMcahwqF4U/Kkyi9DMGgd1UYyLk0c67kk/img.png&quot; data-alt=&quot;Ktorm docs 메인 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bCEzKH/dJMcahwqF4U/Kkyi9DMGgd1UYyLk0c67kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbCEzKH%2FdJMcahwqF4U%2FKkyi9DMGgd1UYyLk0c67kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2259&quot; height=&quot;708&quot; data-origin-width=&quot;2259&quot; data-origin-height=&quot;708&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Ktorm docs 메인 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-path-to-node=&quot;7&quot; data-ke-size=&quot;size23&quot;&gt;1. 문제 상황&lt;/h3&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin 기반의 ORM인 &lt;b data-index-in-node=&quot;16&quot; data-path-to-node=&quot;8&quot;&gt;Ktorm&lt;/b&gt;을 사용하여 MSSQL 데이터베이스 마이그레이션 기능을 구현하고 있었습니다. 마이그레이션이 수행될 때마다 실행된 SQL 스크립트 전문(DDL)을 이력 테이블(ddl_dsl_migration_schema)에 저장하는 로직이 있는데, 길이가 긴 &lt;b&gt;CREATE TABLE 스크립트&lt;/b&gt;를 저장하는 순간 아래와 같은 에러가 발생하며 프로세스가 중단되었습니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;2. 에러 로그&lt;/h3&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjZnsPO2JaSAxUAAAAAHQAAAAAQkAQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;com.microsoft.sqlserver.jdbc.SQLServerException: String or binary data would be truncated in table 'CROSS_POS_DB.dbo.ddl_dsl_migration_schema', column 'script'. 
Truncated value: 'BEGIN TRANSACTION; IF NOT EXISTS ( SELECT * FROM sys.objects ...'.&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;핵심 메시지:&lt;/b&gt; String or binary data would be truncated&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;의미:&lt;/b&gt; 테이블의 특정 컬럼(script)이 허용하는 길이보다 더 긴 데이터를 넣으려다 잘렸다는 의미입니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;3. 원인&lt;/h3&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;로그를 저장하는 테이블의 스키마 정의 코드를 살펴보니, 스크립트를 저장하는 컬럼이 &lt;b&gt;&lt;i&gt;varchar&lt;/i&gt;&lt;/b&gt;로 정의되어 있었습니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjZnsPO2JaSAxUAAAAAHQAAAAAQkQQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;(이 프로젝트의 경우에는 varchar에 length를 따로 안넣어주면 default length가 255이기에, 타입이 varchar(255)로 들어갑니다)&lt;br /&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// 문제가 된 코드
object MigrationSchema : Table&amp;lt;Nothing&amp;gt;(&quot;ddl_dsl_migration_schema&quot;) {
    // ...
    val script = varchar(&quot;script&quot;) // 보통 VARCHAR(255) 등으로 매핑됨
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;15&quot; data-ke-size=&quot;size16&quot;&gt;Ktorm에서 varchar(&quot;name&quot;) 함수를 사용하면 기본적으로 길이 제한이 있는 VARCHAR 타입으로 매핑됩니다. 하지만 제가 저장하려던 SQL 스크립트는 트랜잭션 구문과 DDL이 포함되어 있어 이 길이를 훨씬 초과했기 때문에 에러가 발생한 것입니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;16&quot; data-ke-size=&quot;size23&quot;&gt;4. 해결 방법&lt;/h3&gt;
&lt;p data-path-to-node=&quot;17&quot; data-ke-size=&quot;size16&quot;&gt;MSSQL의 VARCHAR(MAX) 또는 TEXT 타입처럼 대용량 문자열을 저장하기 위해서는 Ktorm의 &lt;b data-index-in-node=&quot;59&quot; data-path-to-node=&quot;17&quot;&gt;text()&lt;/b&gt; 함수를 사용해야 합니다.&lt;/p&gt;
&lt;h4 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size20&quot;&gt;1) Ktorm 스키마 코드 수정&lt;/h4&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;varchar를 text로 변경하여 해당 컬럼이 긴 문자열임을 명시했습니다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjZnsPO2JaSAxUAAAAAHQAAAAAQkgQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// 수정된 코드
object MigrationSchema : Table&amp;lt;Nothing&amp;gt;(&quot;ddl_dsl_migration_schema&quot;) {
    // ...
    // val script = varchar(&quot;script&quot;) -&amp;gt; text()로 변경
    val script = text(&quot;script&quot;) 
    // ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h4 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size20&quot;&gt;2) DB 컬럼 타입 변경&lt;/h4&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;코드를 수정해도 이미 생성된 DB 테이블은 자동으로 바뀌지 않습니다. DB 클라이언트에서 직접 컬럼 크기를 늘려주었습니다. (MSSQL 기준)&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahgKEwjZnsPO2JaSAxUAAAAAHQAAAAAQkwQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&lt;span&gt;SQL&lt;/span&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;sql&quot; data-ke-language=&quot;sql&quot;&gt;&lt;code&gt;ALTER TABLE [dbo].[ddl_dsl_migration_schema]
ALTER COLUMN [script] TEXT;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h3 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size23&quot;&gt;5. 결과 및 요약&lt;/h3&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;위와 같이 조치 후 다시 실행하니, 긴 SQL 스크립트도 잘림 없이 정상적으로 이력 테이블에 저장되었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;26&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,0,0&quot;&gt;Varchar&lt;/b&gt;: 이름, 코드 등 길이가 예측 가능한 짧은 문자열에 사용&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;26,1,0&quot;&gt;Text&lt;/b&gt;: 로그, 본문, JSON 덤프 등 길이가 긴 문자열에 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 좀 더 찾아보니 Text 타입은 항상 blob으로 저장되고, Varchar(max)는 8000바이트 이하면 행에 직접 저장, 그 이상이면 blob으로 저장(Text와 유사)되므로, 일반적으로 Text 타입 대신 Varchar(max)를 사용하는 것을 권장한다는 것을 알았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이후에 한번 더 ddl-dsl 부분을 만질 일이 생기면 Text타입에 대한 dialect에서의 매칭을 text() 대신 varchar(max)로 넣게 수정할 것입니다.&lt;/p&gt;</description>
      <category>Java-Kotlin</category>
      <category>Kotlin</category>
      <category>Ktorm</category>
      <category>MSsql</category>
      <category>트러블슈팅</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/47</guid>
      <comments>https://parkstate.tistory.com/47#entry47comment</comments>
      <pubDate>Wed, 21 Jan 2026 10:22:46 +0900</pubDate>
    </item>
    <item>
      <title>Git Tag를 활용한 시멘틱 버전 관리</title>
      <link>https://parkstate.tistory.com/46</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 개발 중인 프로젝트가 이제 실 운영에 들어가면서 버전 관리가 중요하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 Git Tag를 이용하여 버전 관리를 진행하기로 했다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;128&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cVbGtg/dJMcagxs9KK/Y9Nr2sCFmA3YGxYkgxrfOk/img.webp&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cVbGtg/dJMcagxs9KK/Y9Nr2sCFmA3YGxYkgxrfOk/img.webp&quot; data-alt=&quot;나무위키 시멘틱 버전 이미지&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cVbGtg/dJMcagxs9KK/Y9Nr2sCFmA3YGxYkgxrfOk/img.webp&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcVbGtg%2FdJMcagxs9KK%2FY9Nr2sCFmA3YGxYkgxrfOk%2Fimg.webp&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;128&quot; height=&quot;128&quot; data-origin-width=&quot;128&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;나무위키 시멘틱 버전 이미지&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전은 시멘틱 버저닝으로 매기기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시멘틱 버전은 앞에서부터 Major.minor.patch(1.1.1) 형식으로 이루어져 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 시멘틱 버전을 이용하여, 패치와 마이너, 메이저 업데이트를 구분할 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나무위키 피셜:&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-v-d866e47a=&quot;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;list-style-type: inherit;&quot; data-v-d866e47a=&quot;&quot;&gt;
&lt;div data-v-d866e47a=&quot;&quot;&gt;
&lt;ul style=&quot;list-style-type: disc; background-color: #ffffff; color: #212529; text-align: start;&quot; data-ke-list-type=&quot;disc&quot; data-v-d866e47a=&quot;&quot;&gt;
&lt;li style=&quot;list-style-type: inherit;&quot; data-v-d866e47a=&quot;&quot;&gt;
&lt;div data-v-d866e47a=&quot;&quot;&gt;메이저 버전:&lt;span&gt;&amp;nbsp;&lt;/span&gt;하위 호환성을 보장하지 않는 API 변경사항(breaking change)를 하나라도 포함한 버전에 해당한다. 가령 기존 API를 삭제하거나, 완전히 새로운 API로 통합하는 경우 등. 이러한 경우, 메이저 버전을 올려야 한다. 유일한 예외로, 메이저 버전이&lt;span&gt;&amp;nbsp;&lt;/span&gt;0인 동안은 어떤 불안정한 API 변경사항이 발생하더라도 꼭 메이저 버전을 올리지 않아도 된다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 시기를 개발 단계라고 하며, 최초의 메이저 버전(1.0.0)을 공개한 이후로는 일반적인 메이저 버전 원칙을 따른다.&lt;/div&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit;&quot; data-v-d866e47a=&quot;&quot;&gt;
&lt;div data-v-d866e47a=&quot;&quot;&gt;마이너 버전:&lt;span&gt;&amp;nbsp;&lt;/span&gt;하위 호환성이 있는 API 변경사항을 의미한다. 가령 기존 기능을 전부 남기고 새로운 기능을 추가하는 경우, 기존 API를&lt;span&gt;&amp;nbsp;&lt;/span&gt;deprecate하는 등의 경우,&lt;span&gt;&amp;nbsp;&lt;/span&gt;신규 버전을 기존 소프트웨어에서 그대로 사용할 수 있으므로 업그레이드를 해도 안전함이 보장된다. 이런 경우 마이너 버전을 올린다.&lt;/div&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit;&quot; data-v-d866e47a=&quot;&quot;&gt;
&lt;div data-v-d866e47a=&quot;&quot;&gt;패치 버전: 단순&lt;span&gt;&amp;nbsp;&lt;/span&gt;버그를 수정하는 경우나&lt;span&gt;&amp;nbsp;&lt;/span&gt;리팩토링&lt;span&gt;&amp;nbsp;&lt;/span&gt;등, 표면상의(public surface) API 변경사항이 없으면서 업그레이드가 권장되는 경우 등이 해당한다.&lt;/div&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit;&quot; data-v-d866e47a=&quot;&quot;&gt;
&lt;div data-v-d866e47a=&quot;&quot;&gt;사전 릴리즈: 각 버전을 정식으로 배포하기 전에 공개되는 버전으로, 일반적인 버전 뒤에&lt;span&gt;&amp;nbsp;&lt;/span&gt;-와 식별자를 붙여 나타낼 수 있다. 가령&lt;span&gt;&amp;nbsp;&lt;/span&gt;1.2.3-alpha.7의 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;-alpha.7이 사전 릴리즈 번호이다. 공백을 쓸 수 없기 때문에&lt;span&gt;&amp;nbsp;&lt;/span&gt;.으로 각 식별자를 구분하며,&lt;span&gt;&amp;nbsp;&lt;/span&gt;1.1.1-a.12.b.34와 같이&lt;span&gt;&amp;nbsp;&lt;/span&gt;.으로 여러 식별자를 원하는 만큼 연결할 수 있다. 이때 순서는 각각의 식별자를 앞에서부터 순서대로 비교하며, 각각의 식별자는 숫자로 이루어진 경우 일반적인 정수 순서를, 문자열일 경우 일반적인&lt;span&gt;&amp;nbsp;&lt;/span&gt;사전식 순서에 따른다. 가령&lt;span&gt;&amp;nbsp;&lt;/span&gt;1.0.0-pre.3은&lt;span&gt;&amp;nbsp;&lt;/span&gt;1.0.0-pre.0보다 신규 버전이다. 비슷하게&lt;span&gt;&amp;nbsp;&lt;/span&gt;1.0.0-rc.1은&lt;span&gt;&amp;nbsp;&lt;/span&gt;1.0.0-beta.9와 비교했을 때 문자&lt;span&gt;&amp;nbsp;&lt;/span&gt;r이&lt;span&gt;&amp;nbsp;&lt;/span&gt;b보다 크므로 신규 버전이다. 숫자와 문자열이 같은 위치에 있는 경우 항상 문자열이 더 큰 것으로 간주한다. 가령&lt;span&gt;&amp;nbsp;&lt;/span&gt;1.0.0-alpha.2.tp17이&lt;span&gt;&amp;nbsp;&lt;/span&gt;1.0.0-alpha.2.338보다 신규 버전이다.&lt;/div&gt;
&lt;/li&gt;
&lt;li style=&quot;list-style-type: inherit;&quot; data-v-d866e47a=&quot;&quot;&gt;
&lt;div data-v-d866e47a=&quot;&quot;&gt;빌드 번호: 버전 뒤에&lt;span&gt;&amp;nbsp;&lt;/span&gt;+&lt;span&gt;&amp;nbsp;&lt;/span&gt;기호와 함께 붙은&lt;span&gt;&amp;nbsp;&lt;/span&gt;문자열. 가령&lt;span&gt;&amp;nbsp;&lt;/span&gt;1.2.3+sha.8a4c9fd의 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;+sha.8a4c9fd&lt;span&gt;&amp;nbsp;&lt;/span&gt;부분이,&lt;span&gt;&amp;nbsp;&lt;/span&gt;4.11.4+20251008.0의 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;+20251008.0&lt;span&gt;&amp;nbsp;&lt;/span&gt;부분이 빌드 번호이다. 단순히 메타데이터를 넣기 위한 용도이기 때문에 버전 선택 등 과정에서 완전히 무시되며, 빌드 번호를 표시하는 것은 선택 사항이다. 이진 실행 파일인 경우&lt;span&gt;&amp;nbsp;&lt;/span&gt;명령어 집합도 이곳에 명시하기도 한다.&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://namu.wiki/w/%EC%8B%9C%EB%A7%A8%ED%8B%B1%20%EB%B2%84%EC%A0%84&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://namu.wiki/w/%EC%8B%9C%EB%A7%A8%ED%8B%B1%20%EB%B2%84%EC%A0%84&lt;/a&gt;&lt;/p&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 시멘틱 버전을 우리는 깃 특정 커밋의 해쉬값에 달아서 추후 운영 또는 개발에 롤백이나 기능 추가, 관리 등을 수월하게 진행할 수 있도록 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;141&quot; data-origin-height=&quot;179&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bYAJqu/dJMcaiPx1eo/jKeqS6o9uc78RFbSNMqPE0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bYAJqu/dJMcaiPx1eo/jKeqS6o9uc78RFbSNMqPE0/img.png&quot; data-alt=&quot;실제 git log&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bYAJqu/dJMcaiPx1eo/jKeqS6o9uc78RFbSNMqPE0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbYAJqu%2FdJMcaiPx1eo%2FjKeqS6o9uc78RFbSNMqPE0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;141&quot; height=&quot;179&quot; data-origin-width=&quot;141&quot; data-origin-height=&quot;179&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;실제 git log&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제 시멘틱 버저닝과 git Tag를 적용하니 운영 배포 시에 특정 버전으로 운영팀에 배포 파일 전달 및 설치가 깔끔하게 진행이 되는 것을 느꼈다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;언제나 개발보다 오픈과 운영이 어렵다는 것을 크게 느끼고 있는 요즘이다.&lt;/p&gt;</description>
      <category>Git</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/46</guid>
      <comments>https://parkstate.tistory.com/46#entry46comment</comments>
      <pubDate>Thu, 15 Jan 2026 00:00:00 +0900</pubDate>
    </item>
    <item>
      <title>Android DataStore 를 사용하여 파일에 간단한 정보 저장하기</title>
      <link>https://parkstate.tistory.com/45</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 제작하는 POS 프로그램의 정보(회사, 매장, 기기 번호)를 저장해 주어야 해서 Android에서 지원하는 두 개의 라이브러리를 비교해 보았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;SharedPreferences&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동기적으로 작동&lt;/li&gt;
&lt;li&gt;Atomic Read/Write 보장 안됨&lt;/li&gt;
&lt;li&gt;Key-Value 저장 지원&lt;/li&gt;
&lt;li&gt;파싱 에러 발생 시 RuntimeException Throw&lt;/li&gt;
&lt;li&gt;코드가 간단하여 러닝커브가 낮음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;b&gt;DataStore&lt;/b&gt;(Jetpack)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비동기적으로 작동&lt;/li&gt;
&lt;li&gt;Atomic Read/Write 보장 됨&lt;/li&gt;
&lt;li&gt;Key-Value, ProtoBuf(타입을 정의 및 직렬화 가능) 저장 지원&lt;/li&gt;
&lt;li&gt;오류시 IOException로 에러를 Throw&lt;/li&gt;
&lt;li&gt;코드가 SharedPreferences보다 복잡하여 러닝커브가 상대적으로 높음&lt;/li&gt;
&lt;li&gt;Kotlin Coroutine, Flow을 사용하도록 설계됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고민해 본 결과 &lt;b&gt;DataStore&lt;/b&gt;를 사용하여 개발하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DataStore를 선택한 가장 큰 이유는 이렇다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로그램 동작중 여러 쓰레드에서 Read/Write가 발생할 수 있음&lt;/li&gt;
&lt;li&gt;Flow를 사용하여 에러 핸들링이 상대적으로 간편함(Flow에는 catch 블록이 있음)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현할 기능: 간단한 읽기(Read) 쓰기(Write) 정도만 구현하기로 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재 프로젝트는 Kotlin Multiplatform에 Koin으로 di 하는 환경이기에 DataStore 인스턴스와 그것을 inject 받아서 생성되는 DataStoreRepository를 구현하여 읽기 쓰기 기능을 제작했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Gradle&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768396118850&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# libs.versions.toml

androidx-datastore-preferences-core = { group = &quot;androidx.datastore&quot;, name = &quot;datastore-preferences-core&quot;, version = &quot;1.2.0&quot; }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Koin DI&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768396722742&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# KoinModule.kt

single { DataStoreProvider.getInstance().getDataStore() }
single { DataStoreRepository(get()) }&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DataStoreProvider&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768398308992&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;actual class DataStoreProvider private actual constructor() {

    private val dataStoreDirectory: File = Paths.get(&quot;${System.getProperty(&quot;user.home&quot;)}/POSPersistentResource/&quot;).toFile()

    private val dataStore = createDataStoreWithDefaults(
        emptyList()
    ) {
        dataStoreDirectory.resolve(dataStoreFileName).absolutePath
    }

    actual fun getDataStore(): DataStore&amp;lt;Preferences&amp;gt; {
        return dataStore
    }

    actual companion object {
        @Volatile
        private var instance: DataStoreProvider? = null

        actual fun getInstance(): DataStoreProvider {
            return instance ?: synchronized(this) {
                instance ?: DataStoreProvider().also { instance = it }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;DataStoreRepository&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768398707991&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;//데이터 스토어는 파일 저장 상수 느낌으로 사용
//제너릭을 사용 할 경우 세이브에는 문제가 없지만 리드시 타입이 맞지 않을 경우 에러가 발생 할 수 있음
class DataStoreRepository(private val dataStore: DataStore&amp;lt;Preferences&amp;gt;) {
    companion object {
        val TIMESTAMP_KEY = longPreferencesKey(name = &quot;saved_timestamp&quot;)
        val COMPANY_CODE = stringPreferencesKey(name = &quot;COMPANY_CODE&quot;)
        val STORE_CODE = stringPreferencesKey(name = &quot;STORE_CODE1&quot;)
        val POS_NO = stringPreferencesKey(name = &quot;POS_NO&quot;)

    }

    suspend fun &amp;lt;T&amp;gt; saveValue(key: Preferences.Key&amp;lt;T&amp;gt;, value: T): Boolean = try {
        dataStore.edit { preferences -&amp;gt;
            preferences[key] = value
        }
        true
    } catch (e: Exception) {
        e.printStackTrace()
        false
    }

    fun &amp;lt;T&amp;gt; readValue(key: Preferences.Key&amp;lt;T&amp;gt;, default: T): Flow&amp;lt;T&amp;gt; =
        dataStore.data
            .catch { exception -&amp;gt;
                // 예외를 다시 던져서 호출 측에서 처리할 수 있도록 합니다.
                throw exception
            }
            .map { preferences -&amp;gt;
                preferences[key] ?: default // 키에 해당하는 값이 없으면 기본 값 반환
            }

    suspend fun &amp;lt;T&amp;gt; readValueOnce(key: Preferences.Key&amp;lt;T&amp;gt;, default: T): T {
        return readValue(key, default).first()
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;읽기/쓰기 예시&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1768400885593&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// File DataStore에 저장
dataStoreRepository.saveValue(DataStoreRepository.COMPANY_CODE, baseSetting.companyCode)
dataStoreRepository.saveValue(DataStoreRepository.STORE_CODE, baseSetting.storeCode)
dataStoreRepository.saveValue(DataStoreRepository.POS_NO, baseSetting.posNo)

// File DataStore에서 읽기
val companyCode = dataStoreRepository.readValueOnce(DataStoreRepository.COMPANY_CODE, &quot;&quot;)
val storeCode = dataStoreRepository.readValueOnce(DataStoreRepository.STORE_CODE, &quot;&quot;)
val posNo = dataStoreRepository.readValueOnce(DataStoreRepository.POS_NO, &quot;&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java-Kotlin/Android</category>
      <category>Android</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/45</guid>
      <comments>https://parkstate.tistory.com/45#entry45comment</comments>
      <pubDate>Wed, 14 Jan 2026 23:45:32 +0900</pubDate>
    </item>
    <item>
      <title>SCP-380CII 영수증 프린터 안드로이드 SDK 연동기</title>
      <link>https://parkstate.tistory.com/44</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;기존에 Sewoo 영수증 프린터와 Epson 영수증 프린터를 안드로이드 POS/KIOSK 앱에서 사용할 수 있도록 연동시켜 놓았었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 최근 영업팀이 SCP-380CII 프린터가 더 기기 값이 싸서 해당 프린터도 사용하고 싶다는 의견을 주셔서 해당 프린터도 자사 안드로이드 POS/KIOSK 앱에서 사용할 수 있도록 연동개발을 진행하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저 해당 기기의 안드로이드 개발 예시 프로젝트를 훑어보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 프로젝트에는 Sewoo에서 제공하는 Sewoo 영수증 프린터 안드로이드 연동 모듈을 사용하여 안드로이드 상에서 SCP-380CII 프린터를 제어하고있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 줄에 들어가는 최대 문자 수인 CPL만 다르지 나머지는 Sewoo의 영수증 프린터의 Android SDK 모듈을 사용하는 기기였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리하여 따로 추가적인 연동개발이 필요 없이 영수증 프린터에 보내는 데이터를 생성해주는 프린터 컴포넌트 모듈에 SCP-380CII로 영수증 프린터가 설정되어있을때의 CPL 값만 변경해주는 로직을 추가하여 연동 완료했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SCP-380CII 안드로이드 연동시에는 SEWOO Android SDK를 사용하면 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SEWOO 프린터 SDK 링크&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://miniprinter.com/page/?pid=customer_5_view&amp;amp;pr_id=102&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://miniprinter.com/page/?pid=customer_5_view&amp;amp;pr_id=102&lt;/a&gt;&lt;/p&gt;</description>
      <category>Java-Kotlin/Android</category>
      <category>안드로이드</category>
      <category>영수증 프린터</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/44</guid>
      <comments>https://parkstate.tistory.com/44#entry44comment</comments>
      <pubDate>Wed, 31 Dec 2025 15:45:09 +0900</pubDate>
    </item>
    <item>
      <title>jSerialComm사용해서 read시 윈도우 환경에서의 끊겨서 읽히는 현상 트러블 슈팅</title>
      <link>https://parkstate.tistory.com/43</link>
      <description>&lt;p data-path-to-node=&quot;3&quot; data-ke-size=&quot;size16&quot;&gt;최근 안드로이드와 RFID 리더기를 연동하는 프로젝트를 진행하면서 겪었던 시리얼 통신의 &lt;b data-index-in-node=&quot;49&quot; data-path-to-node=&quot;3&quot;&gt;데이터 파편화&lt;/b&gt;&amp;nbsp;문제와 그 해결 과정을 공유합니다.&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;4&quot; data-ke-size=&quot;size23&quot;&gt;1. 문제 상황&lt;/h3&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;저는 jSerialComm 라이브러리를 사용하여 RFID 태그를 시리얼 통신으로 Read하도록 개발하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;6&quot; data-ke-size=&quot;size16&quot;&gt;맥(Mac) 환경에서 테스트할 때는 RFID 태그 데이터가 한 번에 잘 들어왔는데, &lt;b data-index-in-node=&quot;47&quot; data-path-to-node=&quot;6&quot;&gt;윈도우 환경이나 특정 태블릿&lt;/b&gt;에 리더기를 연결하니 이상한 현상이 발생했습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-path-to-node=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,0,0&quot;&gt;기대값:&lt;/b&gt; 10~20바이트의 전체 태그 데이터 (예: ABC12345...)&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;7,1,0&quot;&gt;실제값:&lt;/b&gt; &lt;b data-index-in-node=&quot;5&quot; data-path-to-node=&quot;7,1,0&quot;&gt;단 2바이트&lt;/b&gt;만 읽히고 read() 함수가 종료됨 (예: AB)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;심지어 read() 함수에 타임아웃을 넉넉히 주었는데도, 데이터가 조금이라도 들어오는 순간 함수가 리턴되어 버리는 문제였습니다. 또한, 태그를 찍지 않았는데도 이전에 찍었던 데이터가 뒤늦게 들어오는 현상도 있었습니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;9&quot; data-ke-size=&quot;size23&quot;&gt;2. 원인 분석&lt;/h3&gt;
&lt;p data-path-to-node=&quot;10&quot; data-ke-size=&quot;size16&quot;&gt;원인은 크게 두 가지였습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;11&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,0,0&quot;&gt;시리얼 통신의 특성:&lt;/b&gt; 시리얼 통신은 패킷(Packet) 단위가 아니라 물이 흐르는 스트림(Stream) 방식입니다. 송신 측에서 &quot;Hello&quot;를 보냈다고 해서 수신 측이 반드시 한 번의 read로 &quot;Hello&quot;를 다 받는다는 보장이 없습니다. &quot;He&quot;, &quot;llo&quot; 이렇게 쪼개져서 들어올 수 있습니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;11,1,0&quot;&gt;OS 드라이버의 차이:&lt;/b&gt; read(buffer, timeout) 함수의 동작 방식은 &quot;타임아웃까지 기다려라&quot;가 아니라 &quot;데이터가 들어올 때까지 최대 타임아웃만큼 기다려라&quot;입니다. 윈도우나 일부 안드로이드 USB 드라이버는 데이터를 버퍼링하다가 아주 짧은 덩어리로 먼저 보내주기도 합니다. 앱 입장에서는 &quot;어? 데이터 왔네?&quot; 하고 2바이트만 읽고 루프를 빠져나가 버리는 것입니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;12&quot; data-ke-size=&quot;size23&quot;&gt;3. 해결 방법 (Solution)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;이 문제를 해결하기 위해 두 가지 로직을 적용했습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;14&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,0,0&quot;&gt;Drain 로직 추가:&lt;/b&gt; read 요청 직전에 남아있는 하드웨어/소프트웨어 버퍼 찌꺼기를 강제로 비웁니다.&lt;/li&gt;
&lt;li&gt;&lt;b data-index-in-node=&quot;0&quot; data-path-to-node=&quot;14,1,0&quot;&gt;모으기 로직 추가:&lt;/b&gt; 한 번의 read를 믿지 않고, 원하는 데이터가 다 찰 때까지 반복해서 읽습니다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 로직 2가지를 추가함으로서 문제 상황을 해결 할 수 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size23&quot;&gt;4. 결론&lt;/h3&gt;
&lt;p data-path-to-node=&quot;18&quot; data-ke-size=&quot;size16&quot;&gt;시리얼 통신을 다룰 때는 &lt;b&gt;&quot;한 번의 read 호출로 모든 데이터가 온다&quot;&lt;/b&gt;는 가정을 버려야 한다는 것을 알게 되었습니다. 특히 안드로이드의 다양한 제조사 단말기와 USB 컨트롤러 환경을 고려한다면, 반드시 &lt;b data-index-in-node=&quot;106&quot; data-path-to-node=&quot;18&quot;&gt;루프를 돌며 데이터를 누적&lt;/b&gt; 하여 데이터를 처리하도록 하는 로직은 필수적입니다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;또한, RFID처럼 태깅 시점이 불분명한 경우 read 직전에 &lt;b data-index-in-node=&quot;35&quot; data-path-to-node=&quot;19&quot;&gt;버퍼를 비워주는(Drain)&lt;/b&gt; 작업를 해야 &quot;이전에 찍은 데이터&quot;가 &quot;지금 찍은 데이터&quot;로 보이는 것을 막을 수 있습니다.&lt;/p&gt;</description>
      <category>Java-Kotlin</category>
      <category>Serial 통신</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/43</guid>
      <comments>https://parkstate.tistory.com/43#entry43comment</comments>
      <pubDate>Wed, 24 Dec 2025 23:57:37 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin Multiplatform(KMP) 윈도우 환경에서 File Lock을 이용한 중복 실행 방지</title>
      <link>https://parkstate.tistory.com/42</link>
      <description>&lt;p data-path-to-node=&quot;4&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin Multiplatform(KMP)으로 윈도우 데스크톱 애플리케이션을 개발하던 중, &lt;b&gt;&quot;프로그램이 이미 실행 중이라면 경고창을 띄우고 종료&quot;&lt;/b&gt;시켜야 하는 요구사항이 생겼다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;안드로이드와 달리 데스크톱 환경에서는 사용자가 빌드되어 설치된 exe 파일을 더블 클릭할 때마다 새로운 프로세스가 생성된다. 이를 방지하고 &lt;b&gt;Single Instance Application&lt;/b&gt;을 구현하는 과정과, 그 과정에서 겪었던 IOException 트러블 슈팅 경험을 공유한다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;5&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;6&quot; data-ke-size=&quot;size26&quot;&gt;1. 중복 실행 방지 로직에 대한 접근 방법: File Lock (파일 잠금)&lt;/h2&gt;
&lt;p data-path-to-node=&quot;7&quot; data-ke-size=&quot;size16&quot;&gt;윈도우에서 &lt;b&gt;프로세스 간 통신(IPC)&lt;/b&gt;이나 &lt;b&gt;Mutex&lt;/b&gt;를 사용할 수도 있지만, JVM 환경에서 가장 간편하고 확실한 방법은 &lt;b&gt;파일 락(File Lock)&lt;/b&gt;을 사용하는 것이다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;8&quot; data-ke-size=&quot;size16&quot;&gt;원리는 간단하다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-path-to-node=&quot;9&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;앱이 시작될 때 특정 경로의 파일에 락(FileChannel.tryLock())을 건다.&lt;/li&gt;
&lt;li&gt;락 획득에 성공하면 앱을 실행한다.&lt;/li&gt;
&lt;li&gt;락 획득에 실패하면(이미 다른 프로세스가 락을 걸고 있다면) 중복 실행으로 간주하고 종료한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;10&quot; data-ke-size=&quot;size26&quot;&gt;2. 트러블 슈팅: IOException과 디렉터리 생성&lt;/h2&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;처음에는 단순히 앱 경로 아래에 temp폴더 아래에 락 파일을 생성하려고 했다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-path-to-node=&quot;11&quot; data-ke-size=&quot;size16&quot;&gt;초기 코드:&amp;nbsp;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj2tI_5vMSRAxUAAAAAHQAAAAAQYA&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;// 초기 코드 (문제 발생)
val tempFolder = &quot;temp&quot;
val file = File(tempFolder, &quot;application.applock&quot;)

if (!file.exists()) {
    file.createNewFile() // throws IOException.
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;하지만 위 초기 코드로 실행 시 &lt;b&gt;java.io.IOException: 지정된 경로를 찾을 수 없습니다&lt;/b&gt; 에러가 발생하며 앱이 죽어버렸다. 권한 문제인가 싶어 관리자 권한으로 실행해 봤지만 증상은 동일했다.&lt;/p&gt;
&lt;p data-path-to-node=&quot;13&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-path-to-node=&quot;14&quot; data-ke-style=&quot;style2&quot;&gt;원인: Java/Kotlin의 File.createNewFile() 메서드는 파일을 생성할 뿐, 그 파일이 위치할 부모 디렉터리까지 자동으로 만들어주지 않는다. 즉, .my_app이라는 폴더가 없는 상태에서 그 안에 파일을 만들라고 하니 에러가 난 것이다.&lt;/blockquote&gt;
&lt;blockquote data-path-to-node=&quot;15&quot; data-ke-style=&quot;style2&quot;&gt;해결: 파일 생성 전, 반드시 mkdirs()를 통해 디렉터리의 존재 여부를 확인하고 생성해 주는 로직을 추가하여 해결했다.&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해결 한 코드:&amp;nbsp;&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj2tI_5vMSRAxUAAAAAHQAAAAAQYQ&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;val tempFolder = &quot;temp&quot;
val file = File(tempFolder, &quot;application.applock&quot;)

if(!File(tempFolder).exists()) { // 폴더 위치까지 존재하는지 체크
	File(tempFolder).mkdirs()
}
if (!file.exists()) {
    file.createNewFile()
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;17&quot; data-ke-size=&quot;size26&quot;&gt;3. 최종 구현 코드&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;18&quot; data-ke-size=&quot;size23&quot;&gt;3-1. AppLock 유틸리티 (Lock 관리)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;19&quot; data-ke-size=&quot;size16&quot;&gt;desktopMain 소스셋에 작성한다. 락 파일은 프로세스가 종료되면 OS에 의해 자동으로 해제되지만, 명시적으로 &lt;b&gt;deleteOnExit()&lt;/b&gt;를 호출하여 파일 자체도 정리되도록 했다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj2tI_5vMSRAxUAAAAAHQAAAAAQYg&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;완성 코드: ApplicationLock.kt&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;import java.io.File
import java.io.RandomAccessFile
import java.nio.channels.FileChannel
import java.nio.channels.FileLock

object ApplicationLock {
    private const val APP_DIR_NAME = &quot;temp&quot; 
    private const val LOCK_FILE_NAME = &quot;application.applock&quot;
    
    private var lockFile: RandomAccessFile? = null
    private var lock: FileLock? = null

    fun acquireLock(): Boolean {
        try {
            val lockFileDir = File(APP_DIR_NAME, LOCK_FILE_NAME)

            if(!File(APP_DIR_NAME).exists()) { // 폴더 위치까지 존재하는지 체크
                File(APP_DIR_NAME).mkdirs()
            }
            if (!lockFileDir.exists()) {
                lockFileDir.createNewFile()
            }

            // 파일 락 시도
            lockFile = RandomAccessFile(file, &quot;rw&quot;)
            val channel = lockFile!!.channel
            lock = channel.tryLock()

            if (lock == null) {
                // 이미 실행 중이라 락 획득 실패
                closeLock()
                return false
            }
            
            file.deleteOnExit()
            return true

        } catch (e: Exception) {
            e.printStackTrace()
            closeLock()
            return false
        }
    }

    private fun closeLock() {
        try {
            lock?.release()
            lockFile?.close()
        } catch (e: Exception) { /* 무시 */ }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-path-to-node=&quot;21&quot; data-ke-size=&quot;size23&quot;&gt;3-2. Main 진입점 (Swing 팝업 연동)&lt;/h3&gt;
&lt;p data-path-to-node=&quot;22&quot; data-ke-size=&quot;size16&quot;&gt;ComposeUI의 Window를 띄우기 전에 검사를 수행한다. 이미 실행 중이라면 무거운 Compose UI를 로딩하는 대신, 가벼운 &lt;b&gt;Swing&lt;/b&gt;의&lt;b&gt; JOptionPane &lt;/b&gt;을 사용하여 알림 팝업을 띄우고 프로세스를 종료한다.&lt;/p&gt;
&lt;div data-ved=&quot;0CAAQhtANahcKEwj2tI_5vMSRAxUAAAAAHQAAAAAQYw&quot; data-hveid=&quot;0&quot;&gt;
&lt;div&gt;&amp;nbsp;&lt;/div&gt;
&lt;div&gt;완성 코드: main.kt&lt;/div&gt;
&lt;div&gt;
&lt;div&gt;
&lt;pre class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot;&gt;&lt;code&gt;import androidx.compose.ui.window.Window
import androidx.compose.ui.window.application
import javax.swing.JOptionPane
import kotlin.system.exitProcess

fun main() {
    // 락 검사
    if (!AppLock.acquireLock()) {
        JOptionPane.showMessageDialog(
            null, 
            &quot;프로그램이 이미 실행 중입니다.&quot;, 
            &quot;알림&quot;, 
            JOptionPane.WARNING_MESSAGE
        )
        exitProcess(0) // 강제 종료
    }

    // 정상 실행
    application {
        Window(onCloseRequest = ::exitApplication, title = &quot;My App&quot;) {
            App()
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-path-to-node=&quot;24&quot; data-ke-size=&quot;size26&quot;&gt;4. 결론&lt;/h2&gt;
&lt;p data-path-to-node=&quot;25&quot; data-ke-size=&quot;size16&quot;&gt;Kotlin Multiplatform(KMP)에서 데스크톱 프로그램 개발 시 파일 시스템에 접근할 때는 항상 &lt;b&gt;해당 경로의 폴더가 실제로 존재하는가? &lt;/b&gt;를 먼저 체크해야 한다는 것을 다시 한번 상기했다. &lt;b&gt;FileLock&lt;/b&gt;과 &lt;b&gt;Swing의 JOptionPane&lt;/b&gt;의 조합은 별도의 복잡한 라이브러리 없이 JVM에 기본적으로 있는 기능만으로 깔끔하게 중복 실행 방지 로직을 구현할 수 있는 좋은 방법이다.&lt;/p&gt;</description>
      <category>Java-Kotlin/Android</category>
      <category>Kotlin</category>
      <category>Kotlin Multiplatform</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/42</guid>
      <comments>https://parkstate.tistory.com/42#entry42comment</comments>
      <pubDate>Wed, 17 Dec 2025 20:25:17 +0900</pubDate>
    </item>
    <item>
      <title>Kotlin에서 RfidReader와 USB Serial로 통신 기능 구현</title>
      <link>https://parkstate.tistory.com/41</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;사내 프로젝트에서 RfidReader와 USB Serial 통신을 통해 값을 읽어오는 기능을 구현해야 했습니다. 이를 위해 안정적이고 널리 사용되는 com.fazecast:jSerialComm 라이브러리를 사용하여 Serial Port 통신 어댑터를 개발한 과정을 공유합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;해당 기능은 &lt;/i&gt;&lt;b&gt;&lt;i&gt;com.fazecast:jSerialComm&lt;/i&gt;&lt;/b&gt; 라이브러리를 사용하여 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 라이브러리의 &lt;b&gt;SerialPort.getCommPort(&quot;PortName&quot;)&lt;/b&gt; 함수에 포트 이름을 넣고 호출하면 &lt;b&gt;SerialPort객체&lt;/b&gt;를 획득할 수 있습니다. (파라미터로 준 포트 명의 연결된 시리얼 포트가 없으면 null을 반환합니다)&lt;/p&gt;
&lt;pre id=&quot;code_1765370390351&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;val commPort: SerialPort? = SerialPort.getCommPort(&quot;PortName&quot;)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 SerialPort 객체에 &lt;b&gt;openPort()&lt;/b&gt; 함수를 호출하면 해당 시리얼 포트를 읽기 쓰기 할 수 있도록 열어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765370987262&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// SerialPort.openPort()

 /**
 * 먼저 SerialPort.getCommPort(&quot;PortName&quot;)를 통해 제어할 포트 객체를 가져옵니다. 
 * 그 후 openPort()를 호출하여 통신 라인을 엽니다. 
 * 참고로 openPort()는 포트 열기에 성공하면 true, 실패하면 false를 반환하므로 
 * 이에 대한 예외 처리가 필요합니다.
 */
public final boolean openPort()
{
	return openPort(0);
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 포트를 열어준 뒤, 열린 해당 commPort에서 &lt;b&gt;baudRate,&amp;nbsp;dataBits, stopBits, parity와 timeout&lt;/b&gt;까지 설정해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 RfidReader에서 Rfid 값을 읽어오기 위해서 &lt;b&gt;inputStream&lt;/b&gt;을 가져와야 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;inputStream을 commPort에서 가져오는 함수&lt;/b&gt;를 만들어줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1765371734357&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun inputStream(timeout: Int?): InputStream {
    if (inputStream == null) {
        commPort?.baudRate = baudRate
        commPort?.numDataBits = dataBits
        commPort?.numStopBits = stopBits
        commPort?.parity = parityBits
        commPort?.setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, timeout!!, 0)
        inputStream = commPort?.inputStream
    }
    return inputStream ?: throw IOException(&quot;inputStream을 가져올 수 없습니다.&quot;)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 제 read함수에는 해당 inputStream에 넘겨줘서 값을 받아올 buffer와 위 commPort의 timeout을 설정할 숫자를 파라미터를 받아서 inputStream에서 buffer로 값을 읽어오면 됩니다. (읽어온 read함수가 return 하는 값의 length를 체크해서 buffer의 해당 길이까지 값을 반환)&lt;/p&gt;
&lt;pre id=&quot;code_1765372033927&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;fun read(buffer: ByteArray, timeout: Int?): ByteArray {
    var len = -1
    val inputStream = inputStream(timeout)
    len = inputStream.read(buffer)

    return buffer.copyOfRange(0, len)
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 해당 프로그램이 최초 실행될 때 connect() 함수 실행하고 프로그램이 종료될 때 disconnect()를 한 번씩만 실행해 주고, 중간에 읽기 할 때마다 read() 함수를 실행하면 정상적으로 rfid 값을 읽어 올 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;&lt;b&gt;전체 코드&lt;/b&gt;&lt;/i&gt;&lt;/p&gt;
&lt;div data-ke-type=&quot;moreLess&quot; data-text-more=&quot;더보기&quot; data-text-less=&quot;닫기&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;&lt;a class=&quot;btn-toggle-moreless&quot;&gt;더보기&lt;/a&gt;
&lt;div class=&quot;moreless-content&quot;&gt;
&lt;pre id=&quot;code_1765373151585&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class RfidSerialAdapter(
    private val portName: String,
    private val baudRate: Int,
) {
    private var commPort: SerialPort? = null
    private var inputStream: InputStream? = null

    // 1. 연결 및 설정 (한 번만 수행)
    fun connect() {
        commPort = SerialPort.getCommPort(portName)
        
        // 포트가 실제로 열리는지 확인
        val isOpened = commPort?.openPort() ?: false
        if (!isOpened) {
            throw IOException(&quot;$portName 포트를 열 수 없습니다.&quot;)
        }

        commPort?.apply {
            this.baudRate = this@RfidSerialAdapter.baudRate
            this.numDataBits = 8
            this.numStopBits = SerialPort.ONE_STOP_BIT
            this.parity = SerialPort.NO_PARITY
            // 타임아웃 설정 (Read는 Blocking 모드로 동작하되 timeout만큼 대기)
            setComPortTimeouts(SerialPort.TIMEOUT_READ_SEMI_BLOCKING, 1000, 0)
        }
        
        inputStream = commPort?.inputStream
    }

    // 2. 데이터 읽기 (연결된 상태에서 스트림만 읽음)
    fun read(buffer: ByteArray): ByteArray {
        val stream = inputStream ?: throw IOException(&quot;연결이 되어있지 않습니다.&quot;)
        
        // read는 읽은 바이트 수를 반환, 실패/종료 시 -1
        val len = stream.read(buffer)
        
        if (len &amp;gt; 0) {
            return buffer.copyOfRange(0, len)
        } else {
            return ByteArray(0) 
        }
    }

    // 3. 자원 해제 (앱 종료 시 호출)
    fun disconnect() {
        try {
            inputStream?.close()
            commPort?.closePort()
        } catch (e: Exception) {
            // 로그 처리
        } finally {
            commPort = null
            inputStream = null
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구현하면서 마주친 이슈들은 이 정도가 있었습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설정 시점 이슈
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제가 처음 기능을 구현할 때 &lt;b&gt;read 할&lt;/b&gt; 때마다 포트를 열고 닫았는데, 오버헤드가 커서 열고 닫는 &lt;b&gt;connect&lt;/b&gt;와 &lt;b&gt;read&lt;/b&gt;를 분리했습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Timeout 설정 이슈
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;commPort에 timeout설정을 명시적으로 안 하면 &lt;b&gt;read&lt;/b&gt;함수에서 값이 들어올 때까지 무한 대기에 빠지는 이슈가 있어서, &lt;b&gt;&lt;i&gt;TIMEOUT_READ_SEMI_BLOCKING&lt;/i&gt; &lt;/b&gt;모드를 사용하여 타임아웃 시간을 정해주게 설정하였습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Java-Kotlin</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/41</guid>
      <comments>https://parkstate.tistory.com/41#entry41comment</comments>
      <pubDate>Wed, 10 Dec 2025 22:33:29 +0900</pubDate>
    </item>
    <item>
      <title>Android에서 USB Serial 장치 연결 시 USB 권한 문제 해결기</title>
      <link>https://parkstate.tistory.com/40</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;최근 안드로이드 프로젝트에서 POS장비에 USB Serial 포트 통신 기능을 구현하는 중에 예상치 못한 권한 문제를 마주쳤습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이미 &lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;AndroidManifest.xml에 USB 관련 Permission을 다 줘놨는데도 권한 문제를 만나게 되어 놀랐습니다..&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&lt;span style=&quot;background-color: #ffffff; color: #000000; text-align: left;&quot;&gt;마주친 에러로그&lt;/span&gt;&lt;/b&gt;&lt;/h4&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;exception in UsbManager.openDevice java.lang.SecurityException: User has not given 10161/org.example.android.pos permission to access device /dev/bus/usb/001/004 at android.os.Parcel.createExceptionOrNull(Parcel.java:3011) at android.hardware.usb.UsbManager.openDevice(UsbManager.java:755)&lt;br /&gt;...&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 자세히 읽어보았습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;... &lt;br /&gt;&lt;b&gt;User has not given 10161/org.example.android.pos permission to access device&lt;/b&gt;&lt;br /&gt;(사용자가 이 장치에 접근할 권한을 주지 않았습니다..)&lt;br /&gt;...&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;해결 방법&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 일단 안드로이드 개발문서를 찾아보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안드로이드 공식문서에 Usb 호스트 부분을 보니 UsbManager에 .hasPermission으로만 권한을 체크할게 아니라, requestPermission을 호출하고, &lt;b&gt;BroadCastReceiver&lt;/b&gt;로 권한 요청 결과를 받아야 한다는 것을 알았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://developer.android.com/develop/connectivity/usb/host?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://developer.android.com/develop/connectivity/usb/host?hl=ko&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1764770788940&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;USB 호스트 개요 &amp;nbsp;|&amp;nbsp; Connectivity &amp;nbsp;|&amp;nbsp; Android Developers&quot; data-og-description=&quot;이 페이지는 Cloud Translation API를 통해 번역되었습니다. USB 호스트 개요 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Android 지원 기기가 USB 호스트 모드일 &quot; data-og-host=&quot;developer.android.com&quot; data-og-source-url=&quot;https://developer.android.com/develop/connectivity/usb/host?hl=ko&quot; data-og-url=&quot;https://developer.android.com/develop/connectivity/usb/host?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/tWaed/hyZO0luoI7/T5Prn3NOxyi2Ke7THwdOPk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676&quot;&gt;&lt;a href=&quot;https://developer.android.com/develop/connectivity/usb/host?hl=ko&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://developer.android.com/develop/connectivity/usb/host?hl=ko&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/tWaed/hyZO0luoI7/T5Prn3NOxyi2Ke7THwdOPk/img.png?width=1201&amp;amp;height=676&amp;amp;face=0_0_1201_676');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;USB 호스트 개요 &amp;nbsp;|&amp;nbsp; Connectivity &amp;nbsp;|&amp;nbsp; Android Developers&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;이 페이지는 Cloud Translation API를 통해 번역되었습니다. USB 호스트 개요 컬렉션을 사용해 정리하기 내 환경설정을 기준으로 콘텐츠를 저장하고 분류하세요. Android 지원 기기가 USB 호스트 모드일&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;developer.android.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;핵심은 이렇습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;PendingIntent&lt;/b&gt;로 권한 요청 결과 기다리기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;BroadcastReceiver&lt;/b&gt;로 인텐트 이벤트 리스닝하여 알려주기&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 사실을 알게되고 추가하게 된 저의 &lt;b&gt;UsbPermissionHelper입니다.&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1764771313878&quot; class=&quot;kotlin&quot; data-ke-language=&quot;kotlin&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class UsbPermissionHelper(private val context: Context) {

    private val usbManager = context.getSystemService(Context.USB_SERVICE) as UsbManager
    private val ACTION_USB_PERMISSION = &quot;${context.packageName}.USB_PERMISSION&quot;

    // 브로드캐스트 리시버
    private val permissionReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context, intent: Intent) {
            if (ACTION_USB_PERMISSION == intent.action) {
                synchronized(this) {
                    val device: UsbDevice? = if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.TIRAMISU) {
                        intent.getParcelableExtra(UsbManager.EXTRA_DEVICE, UsbDevice::class.java)
                    } else {
                        @Suppress(&quot;DEPRECATION&quot;)
                        intent.getParcelableExtra(UsbManager.EXTRA_DEVICE)
                    }

                    if (intent.getBooleanExtra(UsbManager.EXTRA_PERMISSION_GRANTED, false)) {
                        device?.let {
                            Log.d(&quot;USB&quot;, &quot;권한 허용: ${device.deviceName}&quot;)
                            // 실제 USB 연결 로직 호출
                            connectPort()
                        }
                    } else {
                        Log.d(&quot;USB&quot;, &quot;권한 거부: ${device?.deviceName}&quot;)
                    }
                }
                context.unregisterReceiver(this)
            }
        }
    }

    fun connectWithPermissionCheck(device: UsbDevice) {
        if (usbManager.hasPermission(device)) {
            Log.d(&quot;USB&quot;, &quot;이미 권한이 있습니다. 연결합니다.&quot;)
        } else {
            Log.d(&quot;USB&quot;, &quot;권한이 없습니다. 요청합니다.&quot;)
            requestPermission(device)
        }
    }

    private fun requestPermission(device: UsbDevice) {
        // receiver
        ContextCompat.registerReceiver(
            context, 
            permissionReceiver, 
            IntentFilter(ACTION_USB_PERMISSION), 
            ContextCompat.RECEIVER_NOT_EXPORTED
        )

	// pendingIntent
        val flags = if (Build.VERSION.SDK_INT &amp;gt;= Build.VERSION_CODES.S) {
            PendingIntent.FLAG_MUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        } else {
            PendingIntent.FLAG_UPDATE_CURRENT
        }
        
        val permissionIntent = PendingIntent.getBroadcast(context, 0, Intent(ACTION_USB_PERMISSION), flags)

        // 권한 요청
        usbManager.requestPermission(device, permissionIntent)
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 이번에도 공식문서를 제대로 안 읽은 저는 일을 두 번 하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러분도 웬만하면 공식문서를 제대로 읽고 개발합시다...&lt;/p&gt;</description>
      <category>Java-Kotlin/Android</category>
      <category>Android</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/40</guid>
      <comments>https://parkstate.tistory.com/40#entry40comment</comments>
      <pubDate>Wed, 3 Dec 2025 23:20:11 +0900</pubDate>
    </item>
    <item>
      <title>Gitlab CI/CD에 Gitlab Runner 직접 붙여서 빌드하기</title>
      <link>https://parkstate.tistory.com/39</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;지난번엔 gitlab-ci.yml에 빌드 파이프라인을 구축했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이젠 미뤄둔 Windows 설치파일인 msi 파일을 빌드하기 위해서 &lt;b&gt;Windows Gitlab Runner&lt;/b&gt; 를 등록해서 msi 빌드를 할 때가 됐다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. Gitlab Runner 등록 창으로 이동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트에서 Settings&amp;gt;CI/CD 를 누르면 CI/CD Setting들이 나오는데 이중에서 Runners를 열면 현재 해당 프로젝트에 등록된 Gitlab Runner가 보인다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 위치에서 Available Runners 오른쪽에 작게 있는 'Create project runner' 버튼을 누르면 등록 화면으로 이동한다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;877&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bKpoR3/dJMcadAoWuN/kLNTCKkkCSiVAfgWGwSXek/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bKpoR3/dJMcadAoWuN/kLNTCKkkCSiVAfgWGwSXek/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bKpoR3/dJMcadAoWuN/kLNTCKkkCSiVAfgWGwSXek/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbKpoR3%2FdJMcadAoWuN%2FkLNTCKkkCSiVAfgWGwSXek%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;877&quot; data-origin-width=&quot;480&quot; data-origin-height=&quot;877&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;526&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/5Nf0U/dJMcacatpB1/to35j8mV4uNlYQTwBSt2OK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/5Nf0U/dJMcacatpB1/to35j8mV4uNlYQTwBSt2OK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/5Nf0U/dJMcacatpB1/to35j8mV4uNlYQTwBSt2OK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F5Nf0U%2FdJMcacatpB1%2Fto35j8mV4uNlYQTwBSt2OK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1308&quot; height=&quot;526&quot; data-origin-width=&quot;1308&quot; data-origin-height=&quot;526&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 등록할 Gitlab Runner의 정보 입력&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;등록할 Gitlab Runner의 정보를 입력해주어야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;입력할 정보는 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Tags
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;gitlab-ci.yml에 작성하는 job에 이 job은 특정 태그의 Runner에서 실행 하도록 할수 있음.&lt;/li&gt;
&lt;li&gt;예시) 'windows' (Windows 환경 빌드용 태그)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #18171d; text-align: left;&quot;&gt;Runner description&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #18171d; text-align: left;&quot;&gt;Gitlab CI/CD Settings의 Runners 또는 Jobs에서 현재 실행되고 있는 Runner에 보일 설명.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #18171d; text-align: left;&quot;&gt;이름과, 환경을 적어놓음.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #18171d; text-align: left;&quot;&gt;예시) 'windows11 runner'&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #18171d; text-align: left;&quot;&gt;Maximum job timeout&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #18171d; text-align: left;&quot;&gt;실행되는 job의 최대 실행시간.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #18171d; text-align: left;&quot;&gt;이것을 짧게 넣으면 job이 실행되다가 timeout이 나서 실패 할수도 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #18171d; text-align: left;&quot;&gt;이 외의 여러개의 옵션들이 있는데, 자신에 맞게 설정하고 'Create runner' 버튼을 누르면 된다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #18171d; text-align: left;&quot;&gt;3. Gitlab Runner 설치&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #18171d; text-align: left;&quot;&gt;이젠 Gitlab Runner가 될 장치의 플랫폼을 정해주고 gitlab-runner 프로그램을 설치해야 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #18171d; text-align: left;&quot;&gt;화면은 아래와 같은데, 운영체제나 플랫폼에 따라 설치 방법이 달라진다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #18171d; text-align: left;&quot;&gt;플랫폼을 정하고, 'How do I install GitLab Runner?'를 누르면 아키텍처와 설치 방법이 나온다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #18171d; text-align: left;&quot;&gt;해당 방법을 따라하면 설치까진 완료된다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;537&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwl3Ow/dJMcacByigr/6B4k570UkbMKZLXDUObYik/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwl3Ow/dJMcacByigr/6B4k570UkbMKZLXDUObYik/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwl3Ow/dJMcacByigr/6B4k570UkbMKZLXDUObYik/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcwl3Ow%2FdJMcacByigr%2F6B4k570UkbMKZLXDUObYik%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1164&quot; height=&quot;537&quot; data-origin-width=&quot;1164&quot; data-origin-height=&quot;537&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;448&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqOqAl/dJMcagqprd1/CBX40e2l4ina4xfk1whTaK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqOqAl/dJMcagqprd1/CBX40e2l4ina4xfk1whTaK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqOqAl/dJMcagqprd1/CBX40e2l4ina4xfk1whTaK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqOqAl%2FdJMcagqprd1%2FCBX40e2l4ina4xfk1whTaK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;394&quot; height=&quot;448&quot; data-origin-width=&quot;394&quot; data-origin-height=&quot;448&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. Gitlab Runner Register&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'Platform' 아래를 보면 Step이 여러개 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;차례로 shell에서 실행시키면 된다. (powershell, bash, zsh 등)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;gitlab-runner.exe register&lt;/b&gt;을 실행시키면 어떤 환경으로 runner를 실행할지 입력해야하는데, 나는 그냥 커맨드라인을 실행할 것이라 'shell'을 입력했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마지막으로 &lt;b&gt;gitlab-runner run &lt;/b&gt;을 실행하면 runner 등록이 완료 되었다고 뜬다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 다시 위의 &lt;b&gt;Gitlab CI/CD Settings&lt;/b&gt;의 &lt;b&gt;Runners&lt;/b&gt;를 보면 해당 runner를 확인할수 있다&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;445&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/KTPbC/dJMcaaRgI8p/3l8Iq0CKNuLYJmqkuoAhs1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/KTPbC/dJMcaaRgI8p/3l8Iq0CKNuLYJmqkuoAhs1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/KTPbC/dJMcaaRgI8p/3l8Iq0CKNuLYJmqkuoAhs1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FKTPbC%2FdJMcaaRgI8p%2F3l8Iq0CKNuLYJmqkuoAhs1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1179&quot; height=&quot;445&quot; data-origin-width=&quot;1179&quot; data-origin-height=&quot;445&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 빌드 환경 세팅&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;android를 shell로 빌드 할 것이기 때문에 windows에 빌드가 가능한 환경을 만들어 주어야 build job이 실패 하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;환경변수를 신경쓸 환경은 다음과 같다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;git
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;git이 깔려있어야 프로젝트를 clone받음.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;android sdk manager
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;android studio를 설치 해놨으면 깔려 있지만, android studio를 설치 안한 컴퓨터의 경우에는 안깔려있다. android studio 설치 페이지를 보면 android 커맨드 라인 툴만 따로 설치가 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;java
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;gradle이 실행이 되고, 빌드가 되려면 설치가 되어있어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 환경을 모두 설정해주고, build job을 &lt;b&gt;windows&amp;nbsp;&lt;/b&gt;태그에서 실행하면 msi 파일이 빌드가 된다.&lt;/p&gt;</description>
      <category>CI-CD</category>
      <category>ci/cd</category>
      <category>gitlab ci</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/39</guid>
      <comments>https://parkstate.tistory.com/39#entry39comment</comments>
      <pubDate>Wed, 26 Nov 2025 22:02:21 +0900</pubDate>
    </item>
    <item>
      <title>Android Gitlab CI/CD 파이프라인 구축기</title>
      <link>https://parkstate.tistory.com/38</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;현재 회사에서 Kotlin Multiplatform으로 안드로이드와 윈도우에서 작동하는 포스 앱을 만들고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지금까지는 이 앱의 apk, msi 빌드를 직접 제가 하여 포스 기기에 깔아왔는데, 지난 연휴에 문제점을 확인했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존에는 제가 로컬 환경에서 직접 빌드하여 APK와 MSI 파일을 관리했습니다. 그러다 보니 지난 버전의 배포 파일이 체계적으로 아카이빙되지 않았고, 제가 부재중일 때는 현장 기기에 프로그램을 설치하거나 업데이트할 수 없는 버스 팩터 문제가 발생했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또한 새로운 기능을 만들어도 빌드를 하지 못해서 기기에 업데이트를 할 수 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기존의 작업 흐름은 이랬습니다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;프로젝트 수정 및 개발사항 deploy 브랜치에 직접 push.&lt;/li&gt;
&lt;li&gt;저한테 빌드 요청. (스스로 개발한 경우엔 스스로 빌드 요청)&lt;/li&gt;
&lt;li&gt;android/windows 배포 파일 빌드.&lt;/li&gt;
&lt;li&gt;해당 배포 파일 전달.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단순한 빌드 작업 때문에 업무 병목이 발생하고 일정이 지연되는 일이 잦았습니다. 또한, 로컬 빌드 특성상 빌드 환경 차이로 인한 정합성 문제나, 수동 작업에 따르는 휴먼 에러의 위험도 항상 존재했습니다. 이러한 불확실성을 제거하고 배포 프로세스의 일관성을 확보할 필요가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 병목과 환경 이슈와 휴먼 에러들을 최소화 하면 실수와 문제가 더 줄어들고 앞으로도 일관적인 배포 작업이 가능해 보였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 휴먼 에러와 배포 실수를 줄이는 방법으로 생각한게 Gitlab CI/CD를 활용하여 빌드 및 릴리즈에 파일을 아카이빙 해놓는 방법을 생각하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 방법을 사용했을 때 기대되는 효과는 다음과 같았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;작업 흐름을 개선 했을 때의 기대효과&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;의존성 제거:&lt;/b&gt; 특정 개발자(나)에게 몰리던 빌드 요청 병목 해소.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일관성 확보:&lt;/b&gt; 사람이 개입하지 않는 격리된 환경(Runner)에서 빌드하여 환경 변수 통제.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;자산 관리:&lt;/b&gt; 릴리즈 페이지를 통해 버전별 산출물 자동 아카이빙 및 팀 내 공유 용이.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;보안 강화:&lt;/b&gt; Signing Key 등 민감 정보를 CI/CD Variables로 중앙 관리하여 유출 위험 감소.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 많은 기대효과가 있었기에 아래와 같이 파이프라인을 구축하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;CI/CD로 자동화를 추가한 배포 과정의 전체적인 작업 흐름&lt;/b&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;deploy&amp;nbsp;브랜치에&amp;nbsp;merge&amp;nbsp;request를&amp;nbsp;생성.&lt;/li&gt;
&lt;li&gt;checklist를&amp;nbsp;확인&amp;nbsp;후&amp;nbsp;변경&amp;nbsp;세부사항을&amp;nbsp;작성.&lt;/li&gt;
&lt;li&gt;프로젝트&amp;nbsp;담당자의&amp;nbsp;approve를&amp;nbsp;받고,&amp;nbsp;merge.&lt;/li&gt;
&lt;li&gt;CI 파이프라인이 libs.versions.toml 파일에 명시된 app-version 값을 파싱하여 자동으로 Git Tag를 생성합니다. (예: 1.1.1 &amp;rarr; v1.1.1)&lt;/li&gt;
&lt;li&gt;해당 tag가 생성되면 해당 버전으로 apk와 msi를 gitlab-ci가 build.&lt;/li&gt;
&lt;li&gt;해당&amp;nbsp;빌드된&amp;nbsp;배포파일을&amp;nbsp;gitlab-ci가&amp;nbsp;Release에&amp;nbsp;자동으로&amp;nbsp;올림.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포 파이프라인에서 사람이 직접 deploy 브랜치에 push를 못하게 Protected Branch로 지정하여 &lt;b&gt;Allowed to push and merge&lt;/b&gt;를 &lt;b&gt;No one&lt;/b&gt;으로 설정하였습니다. 왜냐면 만약에 사람이 push를 마구잡이로 하게 된다면 개발계의 코드와 운영계의 코드가 섞일 수 있고, 저는 개발계의 코드와 운영계의 코드가 섞일 여지를 줄이고 싶었기 때문입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2412&quot; data-origin-height=&quot;500&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bukwCP/dJMcacBu67S/krWEWfcavtT4H4f0hWdXtk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bukwCP/dJMcacBu67S/krWEWfcavtT4H4f0hWdXtk/img.png&quot; data-alt=&quot;Protected Branches&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bukwCP/dJMcacBu67S/krWEWfcavtT4H4f0hWdXtk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbukwCP%2FdJMcacBu67S%2FkrWEWfcavtT4H4f0hWdXtk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2412&quot; height=&quot;500&quot; data-origin-width=&quot;2412&quot; data-origin-height=&quot;500&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Protected Branches&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;.gitlab-ci.yml&lt;/b&gt;&lt;/p&gt;
&lt;div style=&quot;background-color: #191a1c; color: #bcbec4;&quot;&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;before_script:
  - export GRADLE_USER_HOME=$(pwd)/.gradle
  - chmod +x ./gradlew

cache:
  key: ${CI_PROJECT_ID}
  paths:
    - .gradle/

variables:
  GIT_SUBMODULE_STRATEGY: normal

workflow:
  rules:
#    # 1. 'deploy' 브랜치로 푸시될 때 파이프라인을 실행합니다.
    - if: $CI_COMMIT_BRANCH == &quot;deploy&quot;
    # 2. '태그'가 푸시될 때 파이프라인을 실행합니다.
    - if: $CI_COMMIT_TAG

stages:          # List of stages for jobs, and their order of execution
  - auto-tag # 태그를 자동으로 달아줌.
  - test
  - build
  - deploy


create-tag-job:
  stage: auto-tag
  image: alpine:latest
  rules:
    - if: $CI_COMMIT_BRANCH == &quot;deploy&quot;

  before_script:
    - apk add --no-cache git grep
    - git config --global user.email &quot;ci-bot@gitlab.com&quot;
    - git config --global user.name &quot;GitLab CI Bot&quot;
    # Settings &amp;gt; CI/CD &amp;gt; Variables에 'GIT_PUSH_TOKEN'이 있어야 합니다.
    - git remote set-url origin &quot;https://oauth2:${GIT_PUSH_TOKEN}@gitlab.com/${CI_PROJECT_PATH}.git&quot;
    - git fetch --tags

  script:
    - echo &quot;Checking version from build.gradle.kts...&quot;
    # composeApp/build.gradle.kts에서 versionName 추출 (예: 1.0.3)
    - VERSION_NAME=$(grep 'app-version' gradle/libs.versions.toml | sed -E 's/.*=[[:space:]]*[&quot;'\'']?([^&quot;'\'']+)[&quot;'\'']?.*/\1/')
    - TAG_NAME=&quot;v$VERSION_NAME&quot;
    - echo &quot;Detected Version- $VERSION_NAME -&amp;gt; Tag- $TAG_NAME&quot;

    # 태그가 없을 때만 생성 후 푸시
    - |
      if git rev-parse &quot;$TAG_NAME&quot; &amp;gt;/dev/null 2&amp;gt;&amp;amp;1; then
        echo &quot;⚠️ Tag $TAG_NAME already exists. Skipping.&quot;
      else
        echo &quot;  Creating and Pushing tag: $TAG_NAME&quot;
        git tag $TAG_NAME
        git push origin $TAG_NAME
      fi


android-build-job:       # This job runs in the build stage, which runs first.
  stage: build
  image: cimg/android:2023.12.1-node
  rules:
    # '태그'가 달렸을 때만 실행!
    - if: $CI_COMMIT_TAG

  before_script: # &amp;lt;-- 이 섹션을 추가합니다
    - echo &quot;Decoding Keystore file...&quot;
    # 1. B64 변수를 읽어 base64 디코딩 후, 임시 파일로 저장합니다.
    - echo &quot;$SIGNING_STORE_FILE_B64&quot; | base64 -d &amp;gt; ./keystore-from-ci.jks
    # 2. 이 파일의 전체 경로를 새 환경 변수로 만듭니다.
    - export ANDROID_KEYSTORE_FILE_PATH=$(pwd)/keystore-from-ci.jks

  script:
    - echo &quot;Building Android APK...&quot;
    # :androidApp 모듈의 릴리즈 APK를 빌드합니다.
    # 위에서 설정한 CI/CD 변수(ANDROID_KEYSTORE_FILE 등)를 Gradle이 자동으로 읽어 서명합니다.
    - sh ./gradlew :composeApp:assembleRelease
    - echo &quot;BUILD_JOB_ID=$CI_JOB_ID&quot; &amp;gt;&amp;gt; build.env

  artifacts:
    # 빌드 성공 시 생성된 APK 파일을 30일간 보관합니다.
    name: &quot;android-apk-$CI_COMMIT_SHORT_SHA&quot;
    expire_in: 30 days
    paths:
      # 생성된 APK 파일의 경로
      - composeApp/build/outputs/apk/release/composeApp-release.apk
    reports:
      dotenv: build.env

windows-build-job:
  stage: build
  # 이 작업은 Windows Runner에서만 실행되도록 'tags'를 지정합니다.
  tags:
    - windows
  rules:
    # '태그'가 달렸을 때만 실행!
    - if: $CI_COMMIT_TAG
  script:
    - echo &quot;Building Windows MSI...&quot;
    # Windows는 gradlew.bat 사용
    - .\gradlew.bat :desktopApp:packageReleaseMsi
  artifacts:
    # 빌드 성공 시 생성된 MSI 파일을 30일간 보관합니다.
    name: &quot;windows-msi-$CI_COMMIT_SHORT_SHA&quot;
    expire_in: 30 days
    paths:
      # 생성된 MSI 파일의 경로 (프로젝트 설정에 따라 다를 수 있음)
      - composeApp/build/compose/binaries/main/msi/*.msi

deploy_release:
  stage: deploy
  image: registry.gitlab.com/gitlab-org/release-cli:latest
  # deploy 단계가 build 단계의 결과물을 확실히 가져오도록 의존성을 명시합니다.
  needs:
    - job: android-build-job
      artifacts: true
  rules:
    # 이 작업(job)은 Git 'tag'가 푸시될 때만 실행됩니다. (예: git push origin v1.2.0)
    - if: $CI_COMMIT_TAG
  script:
    - echo &quot;Creating GitLab Release for tag $CI_COMMIT_TAG&quot;
    - echo &quot;Build Job ID is $BUILD_JOB_ID&quot;

  release:
    name: &quot;Release $CI_COMMIT_TAG&quot;
    tag_name: &quot;$CI_COMMIT_TAG&quot;
    description: &quot;New release for $CI_COMMIT_TAG&quot;
    ref: '$CI_COMMIT_SHA'
    assets:
      links:
        - name: &quot;Android APK&quot;
          url: &quot;${CI_PROJECT_URL}/-/jobs/${BUILD_JOB_ID}/artifacts/raw/composeApp/build/outputs/apk/release/composeApp-release.apk?job=android-build-job&quot;
        - name: &quot;Windows MSI&quot;
          url: &quot;${CI_PROJECT_URL}/-/jobs/${BUILD_JOB_ID}/artifacts/raw/desktopApp/build/compose/binaries/main/msi/imtsoft-pos-1.0.0.msi?job=build_windows&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위의 .gitlab-ci.yml을 작성하고 해당 프로젝트에 CI/CD Settings에 Variables에 필요한 몇개의 변수를 등록해주어 파이프라인의 구축을 완료했습니다. &lt;b&gt;&lt;i&gt;CI/CD Variables 등록 시에는 Mask variable 옵션을 켜야 로그에 해당 Variable의 값이 노출되지 않습니다.&lt;/i&gt;&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Android는 Docker 이미지가 많아서 프로젝트의 JVM과 동일한 이미지를 골라서 사용하면 됐지만, Windows msi 빌드를 위해선 Windows Runner를 직접 세팅하거나 호스트 머신에 연결해야 했습니다. (없으면 대기상태로 유지됩니다)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CI/CD Settings / Variables&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;GIT_PUSH_TOKEN&lt;/span&gt;&lt;br /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;자동으로 tag 생성하고 Remote에 올리기 위해 필요&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;SIGNING_KEY_ALIAS&lt;/span&gt;&lt;/span&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;Android apk에 Sign하기 위해 필요&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;SIGNING_KEY_PASSWORD&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;Android apk에 Sign하기 위해 필요&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;SIGNING_STORE_FILE_B64&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;Android apk에 Sign하기 위해 필요&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;SIGNING_STORE_PASSWORD&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;&lt;span style=&quot;background-color: #fbfafd; color: #3a383f; text-align: left;&quot;&gt;Android apk에 Sign하기 위해 필요&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CI-CD</category>
      <category>CICD</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/38</guid>
      <comments>https://parkstate.tistory.com/38#entry38comment</comments>
      <pubDate>Wed, 19 Nov 2025 10:12:34 +0900</pubDate>
    </item>
    <item>
      <title>2025년 끝자락에 적는 2024년 회고 (2023년 하반기를 곁들인)</title>
      <link>https://parkstate.tistory.com/37</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;어느덧 2025년 11월이 되었습니다. 2025년을 보낼 준비를 해야 할 때입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사실 회고를 꾸준히 상반기 하반기에 한 번씩은 적으려 했는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약해빠진 몸과 나약한 정신이 자꾸 방해해서 이제야 2024년의 회고를 1년 퉁쳐서 적게 되었네요..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2024년에 대한 이야기를 하기 전에 2023년 하반기에 대한 이야기를 먼저 적고 싶어서 대략적으로만 적겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;큰 맥락으로 적고 넘어가는 2023년 하반기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 2023년 하반기가 시작되자마자 안성팜랜드 관리 페이지의 개발에 투입되어 현금 매출 조회 API와 화면을 만들게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;화면을 만들고, 문제가 없는지, 원하는 대로 작동하는지 테스트하는 한 달의 반복이었습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 7월은 흘렀습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 어느덧 8월, 인프콘 2023을 갔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프콘에 가서 행사 부스도 구경하고, 강연들도 들으며 유익한 시간을 보냈습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러고 인프랩 개발자분이랑 데브챗을 할 수 있는 기회가 생겨 인프랩의 백엔드 개발자 분이랑 잠시 한 20분 정도 대화를 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때의 저는 수습 느낌이 아닌 어엿한 한 명의 백엔드 개발자가 되고 싶었습니다. 그래서 인프랩의 백엔드 기술 스택과, DB 등의 구조를 여쭤봤고, 제 명함을 드리고 이메일을 교환했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;막판에는 네트워킹 행사도 있어서 처음 보는 개발자분들과 대화도 많이 하고, 링크드인, 깃허브도 교환했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시간은 또 흘러 9월.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023년 상반기부터 개발하던 삼성전자 급식 시스템의 2차 개발이 시작되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때부터는 회사에 있는 시간보다 남대문 주변에 있는 에스원 건물에 들어가서 개발하고 테스트했던 날이 더 많아졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출근 - 개발 - 에스원 이동(과천 지정타에서부터 약 한 시간 소요) - 테스트 - 수정 - 테스트 - 퇴근의 반복이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때는 저희 회사 팀원보다 외부 팀의 팀원분들을 보는 일이 더 많았어요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 그 사이 중간에 틈을 내서 SQLD자격증을 취득했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;12월 말까지도 에스원 왔다 갔다 했습니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 2023년 하반기는 에스원 왔다 갔다 하면서 삼성전자 급식 시스템 개발 및 테스트하느라 정신없이 흘렀습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;드디어 2024년 상반기.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2024년 1월이 되자마자 저는 일본여행으로 심신을 안정시켰습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 여행으로 인해 산업기능요원 신청이 일주일정도 늦어서 결국 2024년 1월 15일부터 1035일의 산업기능요원이 시작되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023년 11월쯤 저는 나중에 어떻게 될지 모르니 방송대 학사라도 갖고 있고 싶어서 방송대를 신청하였고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2024년에 1학년으로 방송대 4년의 여정도 시작되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;20대 중반으로 향하는 두 개의 긴 여정이 2024년 초에 시작되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2월에는 써브웨이 물류를 받아오는 배치의 DB를 변경하는 작업이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2월 말에는 길었던 에스원에서의 테스트 작업에서 철수하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인프런 퇴근길 밋업을 갔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3월. 봄이 되었고, 조금씩 따뜻해졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사무실에서 써브웨이 물류 배치의 전문을 수정하고 테스트했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러던 중 회사에서 신 사업으로 안드로이드 테이블오더를 추진한다 하여 자사 관리 시스템에서 테이블오더에 맞는 옵션형식을 추가하는 작업을 진행했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4월부터는 레거시 자사 관리 시스템을 삼성에 썼던 것처럼 React + Spring Boot 환경으로 개편해 보자고 해서, 삼성 때처럼 프로젝트를 세팅하고, 팀원들한테 해당 시스템을 설명해 드렸습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;DB도 새로 파고, 팀원들이 관리 메뉴를 만들기 위해 필요한 프로그램관리 기능이 있는 메뉴,&amp;nbsp; 메뉴관리 기능이 있는 메뉴, 사용자 관리 기능이 있는 메뉴를 빠르게 구성했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 새로운 시스템으로 개편하던 중 배포에 대한 부분을 맞닥뜨렸고, 서버 측과 클라이언트 측 소스를 git의 deploy branch에 올려놓고, jenkins에서 해당 브랜치의 소스를 배포하게 되는 간단한 파이프라인을 구성하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때까진 운영계 없이, 개발계만 배포할 것이기 때문에 가장 간단하고 어렵지 않은 배포 파이프라인으로 구성했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 구성하게 되고, 개발계가 저 없이도 개발이 진행이 되기 시작한 게 2024년 6월 초입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 7월부터는 안성팜랜드의 카드 매출에 대한 부분을 KCP에서 FTP로 데이터 파일을 끌어와서 해당 데이터를 미리 정의된 전문으로 파싱 해서 테이블에 저장하고, 해당 테이블의 데이터를 더존 ERP에 넣는 배치 개발 프로젝트에 투입되어,&amp;nbsp; 전문에 맞게. dat파일을 파싱 하고, ERP API를 호출하여 데이터를 넘기는 배치 모듈을 처음으로 개발하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전문에는 몇 번째부터 몇 자리가 어떤 데이터이고, 어디부터 어디가 어떤 데이터인지 정의가 되어있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 식으로 데이터를 받아오고 파싱 하는 것이 처음이라 정말 신기했어요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 받아오고, 파싱 하고, 넘겨주고 하는 게 다인줄 알았지만 나중에 운영이 들어가게 되니 해당 배치 프로그램이 정해진 시간 이외에도 고객사가 데이터가 필요한 순간에 바로 받아 올 수 있는 수동 배치가 필요해졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 해당 로직이 있는 서비스를 실행시키는 trigger api를 컨트롤러로 따로 빼서 자사 관리 시스템에 버튼을 누르면 해당 api를 호출하는 기능을 추가로 개발하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이때까지만 해도 자사 관리 시스템의 개편이 진행 중이었으나,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테이블오더가 캘리스코 사보텐에 나가며 잘 구동되고, 마진도 많이 남는 것을 확인한 회사의 높은 분들이 앞으로는 기기 값이 싼 안드로이드 기기에&amp;nbsp; 자사의 포스와 키오스크 등을 넣자는 의지가 강해져서 자사 관리 시스템 개편이 잠정 중단되게 되었습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 되어 2024년 상반기에 절반 이상을 개발한 자사 관리 시스템 개편 프로젝트는 우선순위가 가장 낮게 변경되고, 현재 글을 적고 있는 2025년 말에도 진행이 되지 않고 있습니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(현재도 자사 관리 시스템의 DB만 살짝 변경한 프로그램을 사용하고 있습니다.. 하지만 미뤄둔 React + Spring Boot 3.x대로 개편하자는 이야기가 현재는 조금씩 나오고 있습니다. 내년에는 진짜 진짜 진행할 수도 있겠네요..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2024년 상반기는 이렇게 진행하던 프로젝트가 엎어지며 마무리가 됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이제는 2024년 하반기.&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난번에 개발한 테이블오더가 잘되며 안드로이드 포스, 키오스크 시장에 진출하고자 마음을 먹은 회사는 안드로이드 포스와 키오스크 개발에 돌입하기 전에 포스와 키오스크에 연동되어 돌아가는 기기들을 관리하는 프로그램을 먼저 개발할 필요성을 느끼게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;포스와 키오스크에 연동되어 돌아가는 기기나 솔루션:&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주방 프린터
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;포스나 키오스크나 테이블오더에서 주문을 넣으면 주방에 주문서를 뽑아 알리기 위함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;DID(Digital Information Display)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주문이 들어가고, 완료되면 고객을 호출하기 위함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;KDS(Kitchen Display System)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주문이 들어가면, 현재 주방에서 만들어야 할 주문이 어떤 어떤 게 있는지 확인하면서 조리하기 위함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;외부 배달 프로그램
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배달 주문을 받기 위함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;라벨 프린터
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;라벨 프린터를 사용하는 매장들이 사용하기 위함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;선주문 프로그램
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;앱에서 선주문을 하게 되면 포스와 주문을 통합하기 위함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 적힌 것처럼 연동해야 할 기기와 솔루션이 아주 많았기 때문에 추후를 생각해서 회사는 연동 프로그램을 먼저 개발하길 원했고, 저의 다음 투입 될 프로젝트는 연동 프로그램이 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요구사항은 위에 적힌 프로그램 및 기기 및 솔루션이 모두 이 연동 프로그램을 바라보고 주문을 관리하는 Android와 Windows에서 돌아갈 수 있는 프로그램이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;멀티 플랫폼으로 개발해야 한다는 게 저에게는 가장 크게 다가왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SQLite를 써야 할 것이 예상되어 추후 복잡해질수록 관리가 어려워질 React Native는 안중에도 없었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 멀티 플랫폼으로 개발하기 위해 Flutter와 한창 뜨고 있던 Kotlin Multiplatform 중에 선택해야 했고, 결국 익숙하지 않은 언어인 Dart를 사용하는 Flutter 대신 그나마 Java를 사용해 왔던 저에게 더 익숙한 Kotlin Multiplatform(이하 KMP)를 선택하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물론 자료 같은 거는 Flutter가 더 많았지만 그래도 내가 잘 아는 gradle로 프로젝트 의존성을 관리하고, 실행되는 환경도 그나마 더 잘 알고 있는 KMP가 낫겠다 싶었고, 기존에 키오스크를 개발한 언어가 Java이기에 만약에 영수증 프린터 같은 외부 기기를 연동하게 된다 하더라도 영수증 Component 같은 기존에 만들어 놓았던 포맷 모듈도 공통으로 사용할 수 있기 때문이었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 KMP로 안드로이드, 윈도우 앱을 개발하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 외부 기기나 외부 솔루션과 원활한&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;해당 KMP에서 Ktor을 활용하여 netty로 안드로이드 환경에서도 was를 띄워 Rest API를 통해 연결할 수 있도록 구성하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 연동 프로그램을 개발하며 의존성 관리와, 어느 플랫폼 또는 모듈에 종속되지 않는 구조를 잡고 유지하며 개발하는 것에 가장 심혈을 기울였던 것 같습니다. 아키텍처에 대한 고민이 많았고, 안드로이드 환경에 대한 이해도가 높아졌습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발기를 모두 적기에는 글이 너무 길어질 것 같으니, 이 연동 프로그램에 대한 부분은 추후에 글로 더 적겠습니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 어느덧 연동 프로그램을 개발하다보니 10월,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;10월10일부터 31일까지 3주간 산업기능요원 기본교육 받으러 논산에 훈련소를 다녀왔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3주의 길지 않은 시간이었지만 아직도 기억에 남네요. 아침에 자다가 들려오는 나팔소리, 점호...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그렇게 훈련소를 다녀오고 나서 11월,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;훈련소를 3주간 다녀와서 회사로 복귀하고 나니 실장님이 저를 불러서 새로운 프로젝트를 급하게 해야한다고 말씀하셨습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 새로 시작하게 된 프로젝트는 롯데의 사직구장에 들어가게 될 QR Order 프로젝트(이하 QR오더). 기간은 약 4달. (11월 시작 2월 오픈 목표)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전체적인 프로젝트의 구상은 사직구장의 각 F&amp;amp;B 매장에 QR오더 페이지로 들어가게 되는 QR을 배치하고, 관람석에도 QR을 배치해서 QR로 매장에 주문을 넣고, 매장에서는 조리가 완료되면 해당 QR오더를 이용한 고객을 카카오톡 알림톡으로 호출하는 구조였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이 프로젝트에 투입할 인원이 없었기에 저 혼자 프론트 페이지와 백엔드 서버를 모두 담당하게 되어 부담감이 컸습니다. 저 혼자 진행하는 이런 대형 프로젝트는 처음인데다가 고객사가 롯데라니.. 그래도 마음을 다잡고 차근차근 프로젝트를 진행해나갔습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;QR오더 프론트는 React와 Scss로 개발했고, Zustand로 상태를 관리했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 서버는 Spring Boot 2.7, DB는 기존에 자사 관리 시스템과 공유해서 사용하기 위해 MSSql에 Jpa + QueryDsl 사용했습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;추후 저희 회사의 다른 개발자분들이 붙으며 프론트에는 react-query, 백엔드에는 mybatis를 추가적으로 의존하여 사용하게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배포는 jenkins로 프로젝트에 미리 만들어둔 배포 sh 파일 실행해서 war로 말고, 웹은 React build해서 ec2서버의 Tomcat과 Nginx로 scp를 통해 올림으로서 배포를 진행하였습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 이렇게 개발계에서 배포하면 좋겠지 했는데 나중에는 파이프라인 짤 시간도 없고 해서 그냥 운영계에도 가져다가 사용했습니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 그렸던 운영계의 배포 파이프라인은 이러했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컴퓨터에서 버전 올려서 deploy 브랜치에 pr을 올려서 merge되면 gitlab action 이 버전이 올라갔다는걸 검증하고 docker로 컨테이너로 말아서 AWS ECR에 올리고, ECR에 올라가면 ECS에 바로 배포 하는 파이프라인.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 꿈꿨던 파이프라인은 이랬으나, 시간적 여유가 없었기에 실현되지 못하고 넘어갔습니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 12월이 끝나며 2024년 21살의 박종연은 &lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;QR오더를 개발하며&lt;span&gt;&amp;nbsp;마무리를 짓게 되었습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;실제 QR 오더 작동 화면&lt;/span&gt;&lt;/span&gt;&lt;/b&gt;&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 51.7442%; height: 506px;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr style=&quot;height: 348px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 348px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20251107_213425459_02.jpg&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;2181&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/btIoyc/dJMcake9Jk2/CyO7Um94mXWzCCPGrYBKTk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/btIoyc/dJMcake9Jk2/CyO7Um94mXWzCCPGrYBKTk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/btIoyc/dJMcake9Jk2/CyO7Um94mXWzCCPGrYBKTk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbtIoyc%2FdJMcake9Jk2%2FCyO7Um94mXWzCCPGrYBKTk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1206&quot; height=&quot;2181&quot; data-filename=&quot;KakaoTalk_20251107_213425459_02.jpg&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;2181&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 348px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20251107_213425459_01.jpg&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;2186&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eyKBT3/dJMcaeze54h/sTC3YtokKV5LDfhiR0Vh41/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eyKBT3/dJMcaeze54h/sTC3YtokKV5LDfhiR0Vh41/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eyKBT3/dJMcaeze54h/sTC3YtokKV5LDfhiR0Vh41/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FeyKBT3%2FdJMcaeze54h%2FsTC3YtokKV5LDfhiR0Vh41%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1206&quot; height=&quot;2186&quot; data-filename=&quot;KakaoTalk_20251107_213425459_01.jpg&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;2186&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 158px;&quot;&gt;
&lt;td style=&quot;width: 50%; height: 158px;&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20251107_213425459.jpg&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;2122&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/471Ye/dJMcaeze54k/c8CTJuXpF6RM8mFRyTBwBK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/471Ye/dJMcaeze54k/c8CTJuXpF6RM8mFRyTBwBK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/471Ye/dJMcaeze54k/c8CTJuXpF6RM8mFRyTBwBK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F471Ye%2FdJMcaeze54k%2Fc8CTJuXpF6RM8mFRyTBwBK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1206&quot; height=&quot;2122&quot; data-filename=&quot;KakaoTalk_20251107_213425459.jpg&quot; data-origin-width=&quot;1206&quot; data-origin-height=&quot;2122&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/td&gt;
&lt;td style=&quot;width: 50%; height: 158px;&quot;&gt;&amp;nbsp;&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;생각보다 큰 프로젝트들을 많이 진행했던 한 해네요.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;2025년에 2024년과 2023년 막바지를 적으니까 뒤죽박죽인 것 같은데, 나중에 가능하다면 연동 프로그램이랑 QR오더 프로젝트에 대해서도 따로 적어보겠습니다.&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333; text-align: start;&quot;&gt;&lt;span&gt;긴 글 읽어주셔서 감사합니다~!&lt;/span&gt;&lt;/span&gt;&lt;/p&gt;</description>
      <category>일상/회고</category>
      <category>회고</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/37</guid>
      <comments>https://parkstate.tistory.com/37#entry37comment</comments>
      <pubDate>Fri, 7 Nov 2025 21:41:59 +0900</pubDate>
    </item>
    <item>
      <title>프로세스 스케줄링(Process Scheduling)과 스케줄링 알고리즘</title>
      <link>https://parkstate.tistory.com/36</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_DALL&amp;amp;middot;E 2025-03-30 01.58.47 - A modern, minimalistic icon representing process scheduling. The icon should feature a stylized flowchart or interconnected nodes with arrows, symboli.webp&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kXYl5/btsM16d39lS/wpoqxsoeMkvVjTbRcHRoXk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kXYl5/btsM16d39lS/wpoqxsoeMkvVjTbRcHRoXk/img.png&quot; data-alt=&quot;Process Scheduling icon by DALL-E&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kXYl5/btsM16d39lS/wpoqxsoeMkvVjTbRcHRoXk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkXYl5%2FbtsM16d39lS%2FwpoqxsoeMkvVjTbRcHRoXk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;200&quot; height=&quot;200&quot; data-filename=&quot;edited_DALL&amp;middot;E 2025-03-30 01.58.47 - A modern, minimalistic icon representing process scheduling. The icon should feature a stylized flowchart or interconnected nodes with arrows, symboli.webp&quot; data-origin-width=&quot;200&quot; data-origin-height=&quot;200&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Process Scheduling icon by DALL-E&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로세스 스케줄링&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 처리를 효율적으로 하기 위해서는 주어진 프로세스가 하나가 아닐 경우 운영체제는 적절하게 어떤 순서대로 프로세스를 처리해야 할지 결정하는 과정을 거치는데, 이것을 &lt;b&gt;프로세스 스케줄링&lt;/b&gt;이라고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프로세스 스케줄링의 목표&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 스케줄링의 주요 목표는 여러가지가 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로세스 스케줄링의 주요 목표&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li style=&quot;text-align: start;&quot;&gt;&lt;b&gt;균형성&lt;/b&gt;: 시스템 자원을 효율적으로 사용&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot;&gt;&lt;b&gt;공정성&lt;/b&gt;: 여러 프로세스가 공정하게 실행될 수 있도록 함&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot;&gt;&lt;b&gt;처리량 극대화&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot;&gt;&lt;b&gt;응답 시간 최소화&lt;/b&gt;&lt;/li&gt;
&lt;li style=&quot;text-align: start;&quot;&gt;&lt;b&gt;대기 시간 최소화&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위와 같은 목표를 달성하기 위해 운영체제에는 여러 가지 스케줄링 정책과 스케줄링 정책에 따른 다양한 스케줄링 알고리즘이 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;스케줄링 정책&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 스케줄링 정책은 크게 &lt;b&gt;선점형&lt;/b&gt;과 &lt;b&gt;비선점형&lt;/b&gt;으로 나뉩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;프로세스 스케줄링 정책&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;선점(Preemptive)&lt;/b&gt; 스케줄링 정책&lt;/li&gt;
&lt;li&gt;&lt;b&gt;비선점(Non-Preemptive)&lt;/b&gt; 스케줄링 정책&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선점 스케줄링 정책은 운영체제가 &lt;b&gt;실행 중인 프로세스를 강제로 중단&lt;/b&gt;하고 다른 프로세스에 CPU 할당 할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선점 스케줄링 정책은 높은 우선순위의 프로세스를 먼저 처리해야 하는 경우에 유용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;선점 스케줄링 정책은 &lt;b&gt;문맥 교환(Context switching)&lt;/b&gt;에 따른 오버헤드가 발생합니다. 이에 따라 운영체제는 문맥 교환이 매우 빠르게 실행되도록 만들어져야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;i&gt;문맥은 CPU의 모든 레지스터와 운영체제에 따라 요구되는 프로세스의 기타&amp;nbsp; 상태입니다.&lt;/i&gt;&lt;/li&gt;
&lt;li&gt;&lt;i&gt;문맥 교환(Context switching)은 CPU가 현재 실행하고 있는 프로세스의 문맥을 PCB에 저장하고, 다른 프로세스의 PCB로부터 문맥을 복원하는 작업입니다.&lt;/i&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비선점 스케줄링 정책은 프로세스에 CPU를 할당하여 실행을 시킨 &lt;b&gt;프로세스가 자발적으로 CPU를 반납할 때까지 실행&lt;/b&gt;을 계속합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;비선점 스케줄링 정책은 강제적인 문맥 교환이 없어서 오버헤드가 발생하지 않습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 프로세스가 실행 중이라면 짧은 프로세스가 오래 기다리게 되는 경우가 생깁니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;스케줄링 알고리즘&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스케줄링 알고리즘은 여러가지가 있으며, 각 알고리즘은 특정 환경이나 목적에 따라 장단점이 있어서 시스템 특성에 알맞게 선택하여 사용됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;비선점형 스케줄링 알고리즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;FCFS(First-Come,&amp;nbsp;First-Served)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;도착&amp;nbsp;순서대로&amp;nbsp;처리&lt;/li&gt;
&lt;li&gt;단순하지만&amp;nbsp;convoy&amp;nbsp;effect&amp;nbsp;발생&amp;nbsp;가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SJF(Shortest Job First)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실행 시간이 가장 짧은 작업 우선&lt;/li&gt;
&lt;li&gt;평균 대기 시간 최소화&lt;/li&gt;
&lt;li&gt;실행&amp;nbsp;시간&amp;nbsp;예측&amp;nbsp;어려움&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;HRN(Highest Response Ratio Next)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;응답률이 높은 프로세스 우선&lt;/li&gt;
&lt;li&gt;대기&amp;nbsp;시간과&amp;nbsp;서비스&amp;nbsp;시간&amp;nbsp;모두&amp;nbsp;고려&lt;/li&gt;
&lt;/ul&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;선점형 스케줄링 알고리즘&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;SRTF(Shortest Remaining Time First)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SJF의 선점형 버전&lt;/li&gt;
&lt;li&gt;남은&amp;nbsp;실행&amp;nbsp;시간이&amp;nbsp;가장&amp;nbsp;짧은&amp;nbsp;프로세스&amp;nbsp;선택&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;&lt;b&gt;RR(Round Robin)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 프로세스에 동일한 시간 할당(타임 퀀텀)&lt;/li&gt;
&lt;li&gt;공정성 보장, 응답 시간 향상&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다단계 큐(Multilevel Queue)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스를 여러 그룹으로 분류하여 각기 다른 큐에 배치&lt;/li&gt;
&lt;li&gt;각 큐마다 다른 스케줄링 알고리즘 적용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;다단계 피드백 큐(Multilevel Feedback Queue)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다단계 큐의 확장형&lt;/li&gt;
&lt;li&gt;프로세스가 큐 사이를 이동 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;우선순위 스케줄링(Priority Scheduling)&lt;/b&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;우선순위가 높은 프로세스 먼저 실행&lt;/li&gt;
&lt;li&gt;기아 상태(starvation) 발생 가능&lt;br /&gt;&lt;br /&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;참고자료&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;&lt;a href=&quot;https://80000coding.oopy.io/b271bc98-65be-4722-8299-a087dc014f3c#d84948a3-797f-4c9c-91f6-61c630b4b78d&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://80000coding.oopy.io/b271bc98-65be-4722-8299-a087dc014f3c#d84948a3-797f-4c9c-91f6-61c630b4b78d&lt;/a&gt;&lt;/b&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1743267225824&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;프로세스 스케줄링을 이해해보자&quot; data-og-description=&quot;개요&quot; data-og-host=&quot;80000coding.oopy.io&quot; data-og-source-url=&quot;https://80000coding.oopy.io/b271bc98-65be-4722-8299-a087dc014f3c#d84948a3-797f-4c9c-91f6-61c630b4b78d&quot; data-og-url=&quot;https://80000coding.oopy.io/b271bc98-65be-4722-8299-a087dc014f3c&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/w7nnU/hyYxJkO3YD/V1uIz0UptLDIIBTQvPUNt0/img.png?width=253&amp;amp;height=297&amp;amp;face=0_0_253_297,https://scrap.kakaocdn.net/dn/nvRtY/hyYyJ5xXDc/0noBgaUiL2VqOzbvvyd6T0/img.png?width=253&amp;amp;height=297&amp;amp;face=0_0_253_297,https://scrap.kakaocdn.net/dn/b85s7r/hyYvnQEiPA/QHUAL3MmgoUBEXsl6ag9wk/img.png?width=1147&amp;amp;height=911&amp;amp;face=0_0_1147_911&quot;&gt;&lt;a href=&quot;https://80000coding.oopy.io/b271bc98-65be-4722-8299-a087dc014f3c#d84948a3-797f-4c9c-91f6-61c630b4b78d&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://80000coding.oopy.io/b271bc98-65be-4722-8299-a087dc014f3c#d84948a3-797f-4c9c-91f6-61c630b4b78d&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/w7nnU/hyYxJkO3YD/V1uIz0UptLDIIBTQvPUNt0/img.png?width=253&amp;amp;height=297&amp;amp;face=0_0_253_297,https://scrap.kakaocdn.net/dn/nvRtY/hyYyJ5xXDc/0noBgaUiL2VqOzbvvyd6T0/img.png?width=253&amp;amp;height=297&amp;amp;face=0_0_253_297,https://scrap.kakaocdn.net/dn/b85s7r/hyYvnQEiPA/QHUAL3MmgoUBEXsl6ag9wk/img.png?width=1147&amp;amp;height=911&amp;amp;face=0_0_1147_911');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;프로세스 스케줄링을 이해해보자&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;개요&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;80000coding.oopy.io&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://rebro.kr/175&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://rebro.kr/175&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1743267234400&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;article&quot; data-og-title=&quot;[운영체제(OS)] 5. 프로세스 스케줄링(Process Scheduling)&quot; data-og-description=&quot;[목차] 1. CPU Scheduling 2. Scheduling Criteria 3. Scheduling Algorithm 4. Multiple-Processor Scheduling 참고) - https://parksb.github.io/article/9.html - KOCW 공개강의 (2014-1. 이화여자대학교 - 반효경) - Sogang Univ. Operating System Lec&quot; data-og-host=&quot;rebro.kr&quot; data-og-source-url=&quot;https://rebro.kr/175&quot; data-og-url=&quot;https://rebro.kr/175&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/ceqCSx/hyYxP6q8Qj/kkMJjVv57Eyj58OU8Jqr0k/img.png?width=800&amp;amp;height=537&amp;amp;face=0_0_800_537,https://scrap.kakaocdn.net/dn/eDB3Lk/hyYyRidPcI/vNiC8v4hddQsJKzI8loOj0/img.png?width=800&amp;amp;height=537&amp;amp;face=0_0_800_537,https://scrap.kakaocdn.net/dn/bxqv0u/hyYxLwcBBq/Tw8zHgvWabgEZcgd4Kdgp1/img.png?width=1301&amp;amp;height=446&amp;amp;face=0_0_1301_446&quot;&gt;&lt;a href=&quot;https://rebro.kr/175&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://rebro.kr/175&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/ceqCSx/hyYxP6q8Qj/kkMJjVv57Eyj58OU8Jqr0k/img.png?width=800&amp;amp;height=537&amp;amp;face=0_0_800_537,https://scrap.kakaocdn.net/dn/eDB3Lk/hyYyRidPcI/vNiC8v4hddQsJKzI8loOj0/img.png?width=800&amp;amp;height=537&amp;amp;face=0_0_800_537,https://scrap.kakaocdn.net/dn/bxqv0u/hyYxLwcBBq/Tw8zHgvWabgEZcgd4Kdgp1/img.png?width=1301&amp;amp;height=446&amp;amp;face=0_0_1301_446');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;[운영체제(OS)] 5. 프로세스 스케줄링(Process Scheduling)&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;[목차] 1. CPU Scheduling 2. Scheduling Criteria 3. Scheduling Algorithm 4. Multiple-Processor Scheduling 참고) - https://parksb.github.io/article/9.html - KOCW 공개강의 (2014-1. 이화여자대학교 - 반효경) - Sogang Univ. Operating System Lec&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;rebro.kr&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한국방송통신대학교 운영체제(김진욱 교수, 이인복 교수) 강의&lt;/p&gt;</description>
      <category>Computer Science</category>
      <category>Computer science</category>
      <category>운영체제</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/36</guid>
      <comments>https://parkstate.tistory.com/36#entry36comment</comments>
      <pubDate>Sun, 30 Mar 2025 02:00:19 +0900</pubDate>
    </item>
    <item>
      <title>프로세스와 쓰레드</title>
      <link>https://parkstate.tistory.com/35</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;프로세스(Process)&lt;/span&gt;&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스란 실행중인 프로그램을 의미합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스는 운영체제(OS)로 부터 자원을 할당 받아서 동작합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;프로그램(Program): 동작을 하지 않는 정적, 수동적인 개체&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;프로세스(Process): 동작하는 능동적인 개체&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;자원(Resource): CPU, Memory, IO, File, Network 등 컴퓨팅 자원&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;동작: 처리장치가 프로세스의 명령을 수행하는 것&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;프로세스 관리자는 프로세스를 생성 및 종료하고, 프로세스를 실행시키기 위해 CPU를 할당하는 등의 스케줄링 작업, 프로세스의 상태관리 등의 작업을 합니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;프로세스의 구성&lt;/span&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;메모리 구조&lt;/span&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #333333;&quot;&gt;프로그램 실행에 직접적으로 필요한 코드와 데이터입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스의 메모리 구조에 있는 영역들은 다음과 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;코드 영역: 어셈블리 형태의 프로그램 그 자체&lt;/li&gt;
&lt;li&gt;데이터 영역: 프로그램이 실행될 때 필요한 데이터(상수 또는 변수의 값, 서브 프로그램의 호출 상태 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터 영역은 사용되는 방식에 따라 세부적으로 또 나뉩니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;정적 데이터 영역: 상수 또는 전역 변수처럼 프로그램 시작부터 끝까지 관리되어야 하는 데이터 보관&lt;/li&gt;
&lt;li&gt;스택 영역: 서브 프로그램에 대한 정보, 지역 변수 등의 데이터 보관&lt;/li&gt;
&lt;li&gt;힙 영역: 동적 변수 중에서 사용자가 할당하는 데이터 보관&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요소들에 따라 서로 데이터가 저장되고 관리되는 데이터 영역이 다릅니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;프로세스 제어 블록&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스 제어 블록(Process Control Block: PCB)은 운영체제가 프로세스를 제어하기 위해 필요한 운영체제에 있어서 중요한 정보들을 가지고 있는 블록입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래와 같은 정보들을 관리합니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로세스 번호(PID): 해당 프로세스를 구분할 수 있는 프로세스마다 고유한 식별자&lt;/li&gt;
&lt;li&gt;프로세스 상태: 생성, 준비, 실행, 대기, 완료(종료) 등의 프로세스의 상태&lt;/li&gt;
&lt;li&gt;프로세스 카운터(PC): 현재 실행중인 명령어의 바로 다음 명령어의 위치를 가지고 있음. (위치를 보고 제어의 흐름을 확인 가능)&lt;/li&gt;
&lt;li&gt;레지스터: CPU가 사용하고 있는 여러가지 레지스터 값(값을 보관하고 있다가 다시 실행시 해당 값으로 이어서 처리)&lt;/li&gt;
&lt;li&gt;메모리 관리 정보: 메모리의 주소나, 가상 메모리의 맵핑 정보 등&lt;/li&gt;
&lt;li&gt;프로세스 우선순위: 프로세스 스케줄링에 사용되는 프로세스 우선순위 정보&lt;/li&gt;
&lt;li&gt;등등 ...&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전통적인 프로세스는 위와 같은 구조를 가지고 있어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로세스는 하나의 프로그램을 실행하기 위한 기본적인 단위, 자원 소유의 단위, 디스패칭의 단위였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그러나 PCB에 PC를 하나만 갖게 되기 때문에 프로세스 내에서 다중처리가 불가능 하다는 단점이 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 나온 개념이 쓰레드(Thread)입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;쓰레드(Thread)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드는 프로세스 내에서 다중처리를 위해 실행의 개념만 분리하여 제안된 개념입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드는 하나의 프로그램을 실행하기 위한 기본적인 단위이며, 디스패칭의 단위입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;process.png&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;352&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c5I4XW/btsMWCKW8Zy/BeuZHKUl92Zw2K4Ph5f7B1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c5I4XW/btsMWCKW8Zy/BeuZHKUl92Zw2K4Ph5f7B1/img.png&quot; data-alt=&quot;쓰레드가 여러 개 있는 프로세스&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c5I4XW/btsMWCKW8Zy/BeuZHKUl92Zw2K4Ph5f7B1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc5I4XW%2FbtsMWCKW8Zy%2FBeuZHKUl92Zw2K4Ph5f7B1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;342&quot; height=&quot;352&quot; data-filename=&quot;process.png&quot; data-origin-width=&quot;342&quot; data-origin-height=&quot;352&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;쓰레드가 여러 개 있는 프로세스&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드는 기존에 프로세스의 PCB에서 관리하던 PC와 레지스터, 상태 등의 정보를 가지고 있고, 각각의 쓰레드마다 스택영역이 나뉘어서 따로따로 제어를 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;쓰레드들 마다 프로그램 코드의 다른 위치를 PC에 담고 처리가 가능하기에 다중처리가 가능합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Computer Science</category>
      <category>Computer science</category>
      <category>운영체제</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/35</guid>
      <comments>https://parkstate.tistory.com/35#entry35comment</comments>
      <pubDate>Tue, 25 Mar 2025 19:36:23 +0900</pubDate>
    </item>
    <item>
      <title>내 코드가 그렇게 이상한가요? 독후감</title>
      <link>https://parkstate.tistory.com/34</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;11월 초부터 11월 30일까지 거의 한달간 조금씩 읽던 책을 드디어 다 봐서, 다 본 기념으로 독후감 쓰려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 진짜 개인적인 감상문 적어놓은것이니 읽기 힘들어도 너른 마음으로 이해 부탁드립니다 ㅎ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;654&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/4C6HK/btsBiSEYu6Y/B4ID4ZkYkodKUvbJBZVcCk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/4C6HK/btsBiSEYu6Y/B4ID4ZkYkodKUvbJBZVcCk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/4C6HK/btsBiSEYu6Y/B4ID4ZkYkodKUvbJBZVcCk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F4C6HK%2FbtsBiSEYu6Y%2FB4ID4ZkYkodKUvbJBZVcCk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;500&quot; height=&quot;654&quot; data-origin-width=&quot;500&quot; data-origin-height=&quot;654&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이 책을 읽게된 계기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;'내 코드가 그렇게 이상한가요' 라는 책을 접하게 된건 배민에서 진행하는 우아한 스터디입니다. 우연히 배민 기술 블로그를 보다가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우아한 스터디 공지가 올라와있어서 내용을 하나하나 읽어보는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 이 책을 발견하고 스터디가 아니라도 혼자서 꾸준히 조금씩 읽어보자는 생각이 들어서 바로 주문해서 읽기 시작했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 개발만 할 줄 알지, 설계는 사실 그렇게 크게 신경쓰며 개발하지 않았기 때문에 더 읽어보고싶었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;감상평&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이 책은 다른 소프트웨어 설계를 다룬 책들에 비해 코드로 된 예제가 굉장히 많아서 읽으며 이해가 쉽고 빨랐습니다.&lt;/li&gt;
&lt;li&gt;저처럼 설계에 입문하는 초보 개발자들이 읽어보면 도움이 될듯합니다.&lt;/li&gt;
&lt;li&gt;개인적으로 실무에 큰 도움까진 아니고 중간중간 소소하게 도움이 되었습니다.&lt;/li&gt;
&lt;li&gt;이 책을 읽고나서 어떤 코드들이 읽기 쉽고, 변경하기 용이한 코드인지 알 수 있게 되었습니다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;솔직히 어떤 코드들이 변경하기 쉬운 코드인지, 어떻게 해야 변경하기 쉬운 코드를 짜는지 몰랐는데, 이 책을 읽고나선 어느정도 감이 잡힙니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체지향의 설계와 도메인 주도 설계에 더 많은 관심이 생겨버림.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;설계에 입문하는 책을 읽었을 뿐인데 좀 더 제가 보기에도 이쁜 코드가 나와서, 설계에 더 큰 관심이 생겼습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;실제로 뭐가 문제인지 모르고 전임 개발자가 개발한대로 개발 하는 것과 어떤 것이 문제인지 인지하고 전임 개발자가 개발한대로 개발하다가 이상한 부분들을 효율적으로 리팩터링 하며 개발하는 것은 하늘과 땅 차이라는 것을 알게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;초보 개발자라면 이 책을 읽고 다시 본인이 짠 코드들을 보게 되면 바꾸고 싶은 부분이 하나둘이 아닐듯 합니다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 저부터 그랬습니다 ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>일상/독후감</category>
      <category>독후감</category>
      <category>설계</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/34</guid>
      <comments>https://parkstate.tistory.com/34#entry34comment</comments>
      <pubDate>Fri, 1 Dec 2023 22:04:51 +0900</pubDate>
    </item>
    <item>
      <title>Git 브랜치 전략(feat. Git Flow, Github Flow, Gitlab Flow)</title>
      <link>https://parkstate.tistory.com/33</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;깃 브랜치 전략(Git Branch Strategy)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git에서는 동시에 여러 작업을 할 수 있게 Branch를 사용합니다. 작업 영역을 분리하여 수정하고 관리하고 원래 버전과 합칠 수도 있습니다. 이런 Git의 Branch를 관리하는 전략들을 Git Branch Strategy(깃 브랜치 전략)이라고 합니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Git Branch 전략의 종류&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Git Flow&lt;/li&gt;
&lt;li&gt;Github Flow&lt;/li&gt;
&lt;li&gt;Gitlab Flow&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Git Flow&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;깃 브랜치 전략이라고 하면 가장 먼저 떠오르는 굉장히 많은 회사와 팀에서 사용하고 있는 전략입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;용도에 맞게 브랜치를 분리해서 사용(feature &amp;gt; develop &amp;gt; release &amp;gt; hotfix &amp;gt; master)
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;병합 순서는 앞에서부터 뒤로 병합&lt;/li&gt;
&lt;li&gt;develop과 master 브랜치는 중심이 되는 브랜치라서 무조건 있어야 함&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;명확한 릴리즈 버전 관리를 위한 브랜치를 따로 관리하기 때문에 한 버전에 대한 유지보수가 용이함&lt;/li&gt;
&lt;li&gt;기능 개발 단위 사이사이의 conflict를 최소화 할 수 있음&lt;/li&gt;
&lt;li&gt;명확한 배포 기간과 주기적인 버전이 정해진 프로젝트에 적합&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;간단 브랜치 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git Flow에는 5가지 종류의 브랜치가 있습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;master
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제품으로 출시될 수 있는 브랜치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;develop
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;다음 출시 버전을 개발하는 브랜치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;feature
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단위 기능을 개발하는 브랜치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;release
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이번 출시 버전을 준비하는 브랜치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;hotfix
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;출시 버전에서 발생한 크리티컬한 버그를 긴급 수정하는 브랜치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;branch를 merge 할 때는 항상 &amp;mdash;no-ff 옵션을 붙여서 branch에 대한 기록이 사라지는 것을 방지하는 것이 원칙&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;과정 설명&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;master에서 develop을 분기&lt;/li&gt;
&lt;li&gt;개발자들은 develop 브랜치에 자유롭게 커밋&lt;/li&gt;
&lt;li&gt;기능 구현이 있는 경우 develop에서 feature-* 브랜치를 분기&lt;/li&gt;
&lt;li&gt;배포를 준비하기 위해 develop에서 release-* 브랜치를 분기&lt;/li&gt;
&lt;li&gt;테스트를 진행하면서 발생하는 버그 수정은 release-* 브랜치에 직접 수정 및 반영&lt;/li&gt;
&lt;li&gt;테스트가 완료되면 release 브랜치를 master와 develop에 merge&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;1524&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ryaD9/btsmllOVhqD/CGAcAyEGZQ3gxC8cDOsAc1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ryaD9/btsmllOVhqD/CGAcAyEGZQ3gxC8cDOsAc1/img.png&quot; data-alt=&quot;Git flow 흐름도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ryaD9/btsmllOVhqD/CGAcAyEGZQ3gxC8cDOsAc1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FryaD9%2FbtsmllOVhqD%2FCGAcAyEGZQ3gxC8cDOsAc1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Git flow 흐름도&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;795&quot; data-origin-width=&quot;1150&quot; data-origin-height=&quot;1524&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Git flow 흐름도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Github Flow&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Git Flow가 Github에서 사용하기에 너무 복잡하고 절차가 많다 해서 고안된 브랜치 전략입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;Git Flow에 비해 굉장히 간단함&lt;/li&gt;
&lt;li&gt;master 브랜치와 pull request 방식을 활용하여 굉장히 단순한 형태&lt;/li&gt;
&lt;li&gt;Git Flow와는 다르게 hotfix 브랜치나 feature, release 브랜치를 구분하지 않음 (우선순위는 존재)&lt;/li&gt;
&lt;li&gt;수시로 배포가 일어나고, CI/CD 파이프라인이 구성되어 있는 프로젝트에 유용&lt;/li&gt;
&lt;/ol&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;간단 브랜치 설명&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;master
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프로젝트의 기둥&lt;/li&gt;
&lt;li&gt;프로젝트를 product에 배포할 때 사용되는 브랜치&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;새로운 브랜치
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;새로운 일을 시작하기 위해 만드는 브랜치&lt;/li&gt;
&lt;li&gt;항상 master에서 분기&lt;/li&gt;
&lt;li&gt;Git Flow와는 다르게 feature, release, develop 등을 나누지 않음&lt;/li&gt;
&lt;li&gt;자세하게 어떤 일을 하고 있는지에 대해 알수있도록, 이름을 명확히 작성해야 함&lt;/li&gt;
&lt;li&gt;커밋 메시지를 명확하게 작성해야 함&lt;/li&gt;
&lt;li&gt;merge 준비가 완료되면 Pull Request를 생성하여 코드를 공유하고 리뷰함&lt;/li&gt;
&lt;li&gt;Pull Request에서 리뷰가 완료되었다면 master 브랜치로 반영을 요구함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;과정 설명&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;master에서 새로운 브랜치를 분기&lt;/li&gt;
&lt;li&gt;기능 개발 완료 시 master에 Pull Request 생성&lt;/li&gt;
&lt;li&gt;Pull Request에서 코드 리뷰&lt;/li&gt;
&lt;li&gt;리뷰 및 테스트 완료 시 master 브랜치로 merge&lt;/li&gt;
&lt;li&gt;master 브랜치에서 배포(자동 배포를 사용 시 더욱 편함)&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;214&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dl7bnH/btsmhguHwUV/U8PeCJfchZs25qkjIb674k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dl7bnH/btsmhguHwUV/U8PeCJfchZs25qkjIb674k/img.png&quot; data-alt=&quot;Github flow&amp;amp;amp;nbsp;흐름도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dl7bnH/btsmhguHwUV/U8PeCJfchZs25qkjIb674k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdl7bnH%2FbtsmhguHwUV%2FU8PeCJfchZs25qkjIb674k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Github flow 흐름도&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;214&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;214&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Github flow&amp;amp;nbsp;흐름도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Gitlab Flow&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 Git Flow와 너무 간단한 Github Flow의 절충안&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;특징&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Production 브랜치가 존재(Git Flow의 master 브랜치 역할과 같음)&lt;/li&gt;
&lt;li&gt;Gitlab Flow의 master 브랜치는 production 브랜치로 병합됨&lt;/li&gt;
&lt;li&gt;pre-production 브랜치는 production 브랜치로 넘어가기 전에 테스트를 진행하는 브랜치&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;간단 브랜치 설명&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;master&lt;/li&gt;
&lt;li&gt;pre-prodution
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;production으로 넘어가기 전의 브랜치&lt;/li&gt;
&lt;li&gt;테스트 등을 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;production
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;배포만을 담당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;새로운 브랜치
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;github flow의 새로운 브랜치처럼 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;과정 설명&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Github Flow에서 배포만을 위해 추가된 Production 브랜치와 테스트를 위한 Pre-production 브랜치 빼고 동일합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;618&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bRwFXJ/btsmj8JV53t/7jZVW3jlVcQGpr03mYFElK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bRwFXJ/btsmj8JV53t/7jZVW3jlVcQGpr03mYFElK/img.png&quot; data-alt=&quot;Gitlab flow 흐름도&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bRwFXJ/btsmj8JV53t/7jZVW3jlVcQGpr03mYFElK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbRwFXJ%2Fbtsmj8JV53t%2F7jZVW3jlVcQGpr03mYFElK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;Gitlab flow 흐름도&quot; loading=&quot;lazy&quot; width=&quot;560&quot; height=&quot;618&quot; data-origin-width=&quot;560&quot; data-origin-height=&quot;618&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Gitlab flow 흐름도&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;참고&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;우아한테크블로그 Git flow&quot; href=&quot;https://techblog.woowahan.com/2553/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://techblog.woowahan.com/2553/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;git flow를 이해해보자&quot; href=&quot;https://blog.gangnamunni.com/post/understanding_git_flow/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://blog.gangnamunni.com/post/understanding_git_flow/&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;git branch 전략 자료&quot; href=&quot;https://velog.io/@kw2577/Git-branch-전략&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://velog.io/@kw2577/Git-branch-전략&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;브랜치 전략&quot; href=&quot;https://sungjk.github.io/2023/02/20/branch-strategy.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sungjk.github.io/2023/02/20/branch-strategy.html&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;깃랩플로우란?&quot; href=&quot;https://about.gitlab.com/topics/version-control/what-is-gitlab-flow/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://about.gitlab.com/topics/version-control/what-is-gitlab-flow/&lt;/a&gt;&lt;/p&gt;</description>
      <category>Git</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/33</guid>
      <comments>https://parkstate.tistory.com/33#entry33comment</comments>
      <pubDate>Tue, 4 Jul 2023 23:07:34 +0900</pubDate>
    </item>
    <item>
      <title>2023년 상반기 회고. 근데 2022년 하반기를 곁들인..</title>
      <link>https://parkstate.tistory.com/32</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;해가 바뀐 지 얼마 되지도 않은 것 같은데, 벌써 2023년의 절반이 지나갔습니다. 최근 들어서 한 해가 굉장히 빠르게 지나간다는 생각을 하게 되네요.. 마침 2023년이 절반이나 지났겠다, 지난 반년을 회고해 보며 글을 작성해 볼까 하는 생각이 들었습니다. 그래서 지난날을 다시 되돌아보는데 2022년 연말에도 회고하고 글을 작성하려 했다는 사실이 기억났습니다. 게으름 때문인지 아니면 20살이 되고 들떠서 술을 마시러 다니느라 바쁜 탓이었는지는 모르겠지만 결국 아무것도 하지 못하고 2023년 1월이 지나고, 2월이 지나가더니 쏜살같이 반년이 삭제되었습니다 ㅋㅋ&amp;hellip; 그래서 2023년 상반기 회고 하는 김에 많은 일과 변화가 있었던 2022년 하반기도 함께 회고하고 회고록으로 기록하고자 합니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;학생에서 직장인(진)이 된 개발자의 2022년 여름방학&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 크게 보면 2022년 하반기의 가장 큰 변화는 신분의 변화였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;평범한 특성화고에 재학중인 고3 학생이었던 저는 6월 말에 면접을 통과한 두 개의 회사 중 하나를 선택하여 계약을 완료했고, 2022년 9월 5일에 첫 출근을 앞두고 설렘 반 두려움 반을 가진 상태였습니다. 고등학교의 이런 상태로 마지막 여름방학을 맞게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;고등학교의 마지막 여름방학은 제대로 즐겨야겠다는 마음을 갖고있었지만 지금 확인해 보니 이 여름방학은 동아리 MT를 빼면 게임과 백엔드 공부밖에 없네요.. 회사 출근에 대한 두려움 때문인지 살짝 부족했던 스프링 인강을 많이 들었던 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;첫 회사, 첫 출근&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여름방학이 끝나고 기다리던 9월 5일이 되어 드디어 첫 회사로 첫 출근하게 되었습니다. (첫 출근이였는데 비도 왔어요..) 이때는 사무실이 강남의 네이버 D2 스타트업 팩토리에 있었기 때문에 설레는 마음을 갖고 강남역으로 향하는 버스를 탔습니다. 사무실이 있는 건물에 도착하여 경영지원팀 매니저님께 전화드렸고, 드디어 사무실로 들어가서 출입카드키와 웰컴키트를 받고 맥북을 세팅하며 업무교육을 받았습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdD4vS/btsl3jyxnsk/G4TwZM7tURKMw3q4EiQE50/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdD4vS/btsl3jyxnsk/G4TwZM7tURKMw3q4EiQE50/img.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; width=&quot;300&quot; style=&quot;width: 56.4633%; margin-right: 10px;&quot; data-widthpercent=&quot;57.13&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdD4vS/btsl3jyxnsk/G4TwZM7tURKMw3q4EiQE50/img.png&quot; alt=&quot;네이버 d2 팩토리 웰컴키트&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdD4vS%2Fbtsl3jyxnsk%2FG4TwZM7tURKMw3q4EiQE50%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b1Pq3b/btsmj9tZZbF/VUHD7HYWyxO6ISjmGtcN31/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b1Pq3b/btsmj9tZZbF/VUHD7HYWyxO6ISjmGtcN31/img.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;533&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; data-widthpercent=&quot;42.87&quot; style=&quot;width: 42.3739%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b1Pq3b/btsmj9tZZbF/VUHD7HYWyxO6ISjmGtcN31/img.png&quot; alt=&quot;네이버 d2 팩토리 웰컴키트&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb1Pq3b%2Fbtsmj9tZZbF%2FVUHD7HYWyxO6ISjmGtcN31%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;533&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;네이버 d2 팩토리 웰컴키트&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아이유 골든아워 콘서트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;와 태어나서 처음으로 콘서트 가보는건데 그것도 잠실 종합경기장에서 했던 엄청난 규모의 콘서트&amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 생각해보니 사람도 엄청 많았고, 9월 중순인데 엄청나게 더운 날씨였음에도 작년에 놀러 갔던 것들 중 가장 강렬한 기억 중 하나네요!(가서 떼창도 열심히 하고, 열기구도 보고, 드론쇼도 보고, 폭죽도 보고, 아이유 님도 보고..)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;업무&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;두 번째 출근날 첫 업무를 받게 되었습니다. 저는 블루니라는 챗봇을 개발하는 팀에 있었는데, 이 회사는 블루니라는 챗봇 외에도 많은 AI모델을 학습시키고 서빙하여 여러 서비스가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이중 AI모델을 테스트해볼 수 있는 사이트 &amp;lsquo;튜니브리지&amp;rsquo;라는 웹페이지와, 회사의 홈페이지를 인계받아 담당하게 되었습니다. 튜니브리지는 React, StyledComponents, Redux등의 기술 스택을 가지고있었습니다. 홈페이지는 바닐라 HTML, CSS, JS와 jQuery로 이루어져 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;또, 블루니 챗봇의 약관을 기존에는 페이스북 메신저의 채팅을 보내고, 채팅 밑에 있는 버블 버튼을 누르면 완료되는 형식으로 처리했었는데 이걸 웹사이트에서 약관 동의와 개인정보를 수입하는 방식으로 변경하는 개발을 하게되었습니다. React와 StyledComponents, Axios를 사용하여 약관 동의 페이지를 제작하고, Spring Boot를 사용하여 약관 동의 정보와 개인정보를 받기 위한 서버 측 부분을 개발하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 이후로 이 첫 회사에서는 거의 프론트 7, 백엔드 3 정도의 업무를 처리했습니다. (수상할정도로 프론트 개발을 많이 하는 백엔드 개발자)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 업무를 맡은 것 중에는 기존에 Spring Boot로 개발된 블루니의 서버를 fastApi로 마이그레이션 하는 프로젝트의 fastApi와 DB(PostgreSql)의 연결하는 부분을 맡아서 제작하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;첫 회사에서 배운 것&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 업무도 많았고, 첫 회사다 보니 많은 것을 배웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 기술에서는 Spring Boot와 MySQL, PostgreSQL 실전 경험, FastAPI와 SQLAlchemy, Asyncpg 등을 배웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트는 기존에 알고 있던 것들을 더 많이 사용할 기회가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;가장 많은 배움이 있었던 분야는 CI/CD와 인프라인 것 같네요&amp;hellip;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 이 첫 회사에서 Github의 issue, pr, action 등을 처음 사용해 보았습니다. 또, Github Action과 Docker를 사용하여 자동으로 배포하는 것도 처음 보고 사용했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;배우거나 성장한 기술 정리&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;백엔드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Boot&lt;/li&gt;
&lt;li&gt;MySQL&lt;/li&gt;
&lt;li&gt;PostgreSQL&lt;/li&gt;
&lt;li&gt;FastAPI&lt;/li&gt;
&lt;li&gt;SQLAlchemy&lt;/li&gt;
&lt;li&gt;Asyncpg&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;프론트
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Redux&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인프라
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;Github Action&lt;/li&gt;
&lt;li&gt;Docker&lt;/li&gt;
&lt;li&gt;AWS
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EC2&lt;/li&gt;
&lt;li&gt;Cloudfront&lt;/li&gt;
&lt;li&gt;S3&lt;/li&gt;
&lt;li&gt;RDS&lt;/li&gt;
&lt;li&gt;Route53&lt;/li&gt;
&lt;li&gt;Lightsail&lt;/li&gt;
&lt;li&gt;ECR&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;놀다가 생애 첫 골절&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2022년 10월 29일 친구들이랑 밤에 공원에서 뛰놀며 놀다가 다른 친구가 실수로 저를 미는 바람에 넘어져서 굴렀는데, 어깨 라인이 맞지 않아서 어깨 탈골인줄 알았지만 응급실을 가서 엑스레이를 찍어보니 쇄골이 골절된 거라 수술하고 일주일간 입원해 있었습니다. 이 와중에도 수술 다음날부터 노트북 들고 와서 재택근무를 했던 게 기억에 남습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imagegridblock&quot;&gt;
  &lt;div class=&quot;image-container&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XEZST/btsmiGyPX2N/djkQWulXbezA9W8KR3IoTK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XEZST/btsmiGyPX2N/djkQWulXbezA9W8KR3IoTK/img.png&quot; data-origin-width=&quot;3024&quot; data-origin-height=&quot;4032&quot; data-is-animation=&quot;false&quot; width=&quot;300&quot; height=&quot;400&quot; style=&quot;width: 49.4186%; margin-right: 10px;&quot; data-widthpercent=&quot;50&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XEZST/btsmiGyPX2N/djkQWulXbezA9W8KR3IoTK/img.png&quot; alt=&quot;골절 엑스레이&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXEZST%2FbtsmiGyPX2N%2FdjkQWulXbezA9W8KR3IoTK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;3024&quot; height=&quot;4032&quot;/&gt;&lt;/span&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/oOI5u/btsl6L2aHQo/JQlIJ3R6JBPh7IG7XOfsPk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/oOI5u/btsl6L2aHQo/JQlIJ3R6JBPh7IG7XOfsPk/img.png&quot; data-origin-width=&quot;300&quot; data-origin-height=&quot;400&quot; data-is-animation=&quot;false&quot; data-filename=&quot;blob&quot; data-widthpercent=&quot;50&quot; style=&quot;width: 49.4186%;&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oOI5u/btsl6L2aHQo/JQlIJ3R6JBPh7IG7XOfsPk/img.png&quot; alt=&quot;당시 본인 상황 사진&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FoOI5u%2Fbtsl6L2aHQo%2FJQlIJ3R6JBPh7IG7XOfsPk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/div&gt;
  &lt;figcaption&gt;첫 골절..&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;놀랍게도 퇴원하고 한 달간은 팔 보호대를 차야했지만 한 2주 차고 너무 불편해서 병원 갈 때만 착용했습니다 ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;친구와 꿈같았던 일본 오사카 여행&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2022년 12월 22일 아침에 인천공항으로 가서 오전엔 원격 근무를 하고, 오후에 비행기를 타고 일본 간사이 공항으로 이동했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일본을 태어나서 한 번도 안 가본 것은 아니었지만 친구랑 해외를 같이 놀러 가는 건 처음이었기에 굉장히 설렜습니다!(게다가 코로나 터진 이후로 해외 처음 나감) 오사카 곳곳을 돌아다니며 엄청 많이 걸어 다녔습니다 ㅋㅋㅋ&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;1404&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/b3que6/btsmj8op0SW/OwKoXI96n6gvQgQ6zs093K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b3que6/btsmj8op0SW/OwKoXI96n6gvQgQ6zs093K/img.png&quot; data-alt=&quot;당시 걸음수&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b3que6/btsmj8op0SW/OwKoXI96n6gvQgQ6zs093K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb3que6%2Fbtsmj8op0SW%2FOwKoXI96n6gvQgQ6zs093K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;당시 걸음수&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;374&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;1404&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;당시 걸음수&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중간에 크리스마스도 겹쳐서 어쩌다 보니 남정네 둘이서 한국도 아닌 일본에서 크리스마스이브와 크리스마스를 보내고 왔네요 ㅎㅎ&amp;hellip; 혼자 갔더라도 기억에 남을 여행이었는데, 굉장히 오래 알고 지낸 친구랑 함께 가서 더 좋았던 여행이었습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2022년의 끝과 새해의 시작&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2022년의 생일을 조촐하게 집에서 보내고, 어느새 1월 1일 새벽이 되어서 친구들과 동네에서 모여서 인생 첫 술과 마주했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;이렇게 2022년을 보내고 2023년을 맞이했습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cPAfec/btsmhfg55Am/4RE12w6cNZI8uFOJoOkgNk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cPAfec/btsmhfg55Am/4RE12w6cNZI8uFOJoOkgNk/img.png&quot; data-alt=&quot;2022년 커밋 기록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cPAfec/btsmhfg55Am/4RE12w6cNZI8uFOJoOkgNk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcPAfec%2Fbtsmhfg55Am%2F4RE12w6cNZI8uFOJoOkgNk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;2022년 커밋 기록&quot; loading=&quot;lazy&quot; width=&quot;1542&quot; height=&quot;398&quot; data-origin-width=&quot;1542&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2022년 커밋 기록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;많은 경험을 했던 고등학교 졸업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 선택해서 진학했던 특성화고등학교를 졸업했습니다. 고등학교를 다니는 기간 동안 코로나로 원격수업도 겪고, 전공 동아리도 들어가서 활동하고 부장으로도 활동했던 고등학교를 벌써 졸업하게 되었습니다. 앞으로는 진짜 학생이 아닌 직장인이겠네요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;511&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfxzye/btsl2KwmwK1/iS6OHYJkQ2kvSut93tsENK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfxzye/btsl2KwmwK1/iS6OHYJkQ2kvSut93tsENK/img.png&quot; data-alt=&quot;졸업때 찍은 사진&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfxzye/btsl2KwmwK1/iS6OHYJkQ2kvSut93tsENK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdfxzye%2Fbtsl2KwmwK1%2FiS6OHYJkQ2kvSut93tsENK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;졸업때 찍은 사진&quot; loading=&quot;lazy&quot; width=&quot;600&quot; height=&quot;338&quot; data-origin-width=&quot;908&quot; data-origin-height=&quot;511&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;졸업때 찍은 사진&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;운전면허증 취득&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 달가량 운전면허 필기도 보고 실기도 본 결과 운전면허증을 취득했습니다!&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/0ymnP/btsl9vEWqtr/FgQ9M1ajImK5wOMjsXYkd0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/0ymnP/btsl9vEWqtr/FgQ9M1ajImK5wOMjsXYkd0/img.png&quot; data-alt=&quot;모바일 운전면허증&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/0ymnP/btsl9vEWqtr/FgQ9M1ajImK5wOMjsXYkd0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F0ymnP%2Fbtsl9vEWqtr%2FFgQ9M1ajImK5wOMjsXYkd0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;모바일 운전면허증&quot; loading=&quot;lazy&quot; width=&quot;300&quot; height=&quot;650&quot; data-origin-width=&quot;1125&quot; data-origin-height=&quot;2436&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;모바일 운전면허증&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2023년 3월 6일 첫 회사에서 퇴사&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2023년 3월 6일 처음으로 취직한 회사에서 계약 문제로 퇴사하게 되었습니다. 회사를 다니며 백엔드지만 프론트 개발 일을 더 많이 한 저로서는 이번엔 다른 회사에서 진짜 백엔드 위주로 일 할 기회를 찾을 수 있어서 좋다고 생각하며 퇴사하게 되었습니다! 퇴사를 하고 새로 몸담게 될 회사를 알아보며 취직 준비를 진행하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회사 재 취업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;한 달 반가량 이력서도 넣고, 면접도 본 결과 새로운 회사에 들어가게 되었습니다! 이번 회사는 전 회사보다도 집에서 더 가깝고, 대우도 좋아서 속으로 되게 만족했습니다 ㅎㅎ.. 그리고 이번엔 진짜 프론트일이 적고 백엔드 개발이 주로 하게 될 직무라서 더 맘에 들었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 면접도 통과하고, 이제 출근까지 2주 정도의 시간이 남게 되어서 전부터 가보고 싶던 부산을 혼자서 여행 가보기로 맘먹고 숙소랑 교통편을 예약했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;부산 여행&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 부산으로 바로 가서 여행을 즐기려 했지만, 중간에 대전에서 자취하며 대학교를 다니고 있는 친구가 있어서, 그 친구 집으로 가서 하루 놀고 부산에 가기로 정하고 서울역에서 KTX를 타고 대전으로 갔습니다. 대전에서 친구를 만나서 치킨과 맥주를 마시며 놀고 방바닥에 누워서 가방을 베고 자고 일어났습니다. 일어나서 날씨를 확인해 오니 비가 와서 편의점에서 우산 하나를 사서 쓰고서 대전역까지 가서 또 KTX를 타고 부산으로 향했습니다. 부산에 도착하니 역시 부산도 비가 왔어요ㅠㅠㅠ 부산에 도착해서 여기저기 관광 다니고 첫 회사에서 만난 분도 부산에서 연락해서 만나고 굉장히 알찼던 부산 여행이었습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;새 회사로 출근&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;어느덧 2주가 흘러서 새 회사로 출근하게 되었습니다. 이 회사에서는 백엔드 개발자로 일하게 되었습니다. 신입 교육도 받고, 이것저것 일도 하나씩 함께 해보며 일에 익숙해지고 있습니다! 현재까지도 쭉 이어지고 있는 부분입니다 ㅋㅋㅋ 이 회사에 들어가서는 다이어리에 노트하는 게 습관이 되어서 나중에도 이때가 궁금하다 싶으면 다이어리를 찾아보면 돼서 간편할 것 같네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;color: #9d9d9d;&quot;&gt;아무래도 회사에서 깃허브를 사용하지 않고 깃랩을 사용하니 깃허브 커밋 기록도 좀 많이 적어졌네요!&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;396&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bkLtTf/btsl3izFuZZ/NKbWFmKkFS6jGsPmSp5KVk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkLtTf/btsl3izFuZZ/NKbWFmKkFS6jGsPmSp5KVk/img.png&quot; data-alt=&quot;2023년 커밋 기록&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkLtTf/btsl3izFuZZ/NKbWFmKkFS6jGsPmSp5KVk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkLtTf%2Fbtsl3izFuZZ%2FNKbWFmKkFS6jGsPmSp5KVk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;2023년 커밋 기록&quot; loading=&quot;lazy&quot; width=&quot;1524&quot; height=&quot;396&quot; data-origin-width=&quot;1524&quot; data-origin-height=&quot;396&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;2023년 커밋 기록&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 2023년 상반기는 회사가 바뀐 것 빼고는 사실 특별 할 것 없이 지나간 기간입니다. 하지만 새 회사로 옮기고 또 배우고 있는 게 굉장히 많아서, 메모장에 나중에 블로그에 적어야지 하고 기록해 놓은 글감이 좀 있어서 앞으로 그런 글감을 좀 더 깊숙이 배워서 제 것으로 만든 뒤 블로그에도 글로 적어보겠습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 글 읽어주셔서 감사드리고, 남은 하반기도 잘 부탁드립니다!&lt;/p&gt;</description>
      <category>일상/회고</category>
      <category>회고</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/32</guid>
      <comments>https://parkstate.tistory.com/32#entry32comment</comments>
      <pubDate>Tue, 4 Jul 2023 01:26:32 +0900</pubDate>
    </item>
    <item>
      <title>Spring Boot - SLF4J로 Log남기기</title>
      <link>https://parkstate.tistory.com/31</link>
      <description>&lt;h1&gt;SLF4J란?&lt;/h1&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;SLF4J_72dpi.png&quot; data-origin-width=&quot;443&quot; data-origin-height=&quot;128&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dFZ7Jr/btslCI0eNOF/lUe9nTbElo0cqrxzjxGgjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dFZ7Jr/btslCI0eNOF/lUe9nTbElo0cqrxzjxGgjk/img.png&quot; data-alt=&quot;SLF4J 로고&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dFZ7Jr/btslCI0eNOF/lUe9nTbElo0cqrxzjxGgjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdFZ7Jr%2FbtslCI0eNOF%2FlUe9nTbElo0cqrxzjxGgjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; alt=&quot;SLF4J 로고&quot; loading=&quot;lazy&quot; width=&quot;443&quot; height=&quot;128&quot; data-filename=&quot;SLF4J_72dpi.png&quot; data-origin-width=&quot;443&quot; data-origin-height=&quot;128&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;SLF4J 로고&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;S&lt;/b&gt;imple &lt;b&gt;L&lt;/b&gt;ogging &lt;b&gt;F&lt;/b&gt;acade &lt;b&gt;4 J&lt;/b&gt;ava(간단한 자바를 위한 로깅 파사드)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SLF4J&lt;/b&gt;는 다양한 로깅 프레임워크에 대한 인터페이스 모음입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SLF4J&lt;/b&gt;는 많은 로깅 프레임워크를 하나의 방식으로 사용할 수 있는 방법을 제공해 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;SLF4J&lt;/b&gt;는 인터페이스 프레임워크이기 때문에 단독으로 사용하지 않고, 로깅 프레임워크와 함께 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;스프링 부트에서는 기본적으로 &lt;b&gt;SLF4J&lt;/b&gt;와 &lt;b&gt;Logback&lt;/b&gt;을 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a title=&quot;SLF4J 공식 문서&quot; href=&quot;https://www.slf4j.org/manual.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;SLF4J 공식 docs&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1687934725176&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;SLF4J Manual&quot; data-og-description=&quot;SLF4J user manual The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks, such as java.util.logging, log4j 1.x, reload4j and logback. SLF4J allows the end-user to plug in the desired logging frame&quot; data-og-host=&quot;www.slf4j.org&quot; data-og-source-url=&quot;https://www.slf4j.org/manual.html&quot; data-og-url=&quot;https://www.slf4j.org/manual.html&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://www.slf4j.org/manual.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.slf4j.org/manual.html&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;SLF4J Manual&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;SLF4J user manual The Simple Logging Facade for Java (SLF4J) serves as a simple facade or abstraction for various logging frameworks, such as java.util.logging, log4j 1.x, reload4j and logback. SLF4J allows the end-user to plug in the desired logging frame&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.slf4j.org&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;로그 레벨&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그의 레벨은 5개입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;출력 레벨의 설정에 따라 설정 레벨과 그보다 레벨이 높은 로그를 출력합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;아래로 내려갈수록 높은 레벨입니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. trace: debug레벨보다 더 상세한 정보를 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. debug: 디버깅을 위한 정보를 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. info: 정보성 로그를 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. warn: 시스템 에러의 원인이 될 수 있는 경고성 메시지를 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. error: 요청을 처리하는 중 오류가 발생한 경우 표시&lt;/p&gt;
&lt;p data-ke-size=&quot;size14&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;로그 레벨 설정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;logback설정 또는 스프링 설정(application.properties or yml)에서 레벨 설정이 가능합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687929338872&quot; class=&quot;stylus&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;application.properties 설정

전체 레벨 설정
logging.level.root=info

패키지별 레벨 설정
logging.level.org.springframework=info
logging.level.org.hugopark.test=debug&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;로그 찍기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Logger 객체&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Logger객체를 불러와야 로그를 찍을 수 있다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Logger객체를 불러오는 방법은 LoggerFactory와 어노테이션 @Slf4j 가 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687933229846&quot; class=&quot;reasonml&quot; style=&quot;background-color: #f8f8f8; color: #383a42; text-align: start;&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// LoggerFactory
private final Logger log = LoggerFactory.getLogger(getClass())&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1687933303386&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;// @Slf4j 어노테이션
@Slf4j
public class ExampleService {

}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Logger 사용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;logger는 기본적으로 치환문자를 지원합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주의할 점)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;로그를 찍을 때 문자와 문자열을 직접 연산하게 되면 로그를 찍기도 전에 문자열 연산이 일어나서 컴퓨팅 자원을 사용하기 때문에 성능이 떨어지게 되어서 잘못된 사용법입니다. 치환을 애용합시다..&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;치환을 사용하면 성능 개선도 되고, 로그를 찍는 코드의 가독성도 높아지기 때문에 치환을 사용해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;올바른 예&lt;/p&gt;
&lt;pre id=&quot;code_1687934183374&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String name = &quot;박종연&quot;;
log.debug(&quot;hello, {}&quot;, name);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;틀린 예&lt;/p&gt;
&lt;pre id=&quot;code_1687934204041&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String name = &quot;박종연&quot;;
log.debug(&quot;hello, &quot;+name);&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;예외 로그 찍기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Exception Stack Trace&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;SLF4J는 Throwable객체를 파라미터로 넘기게 되면 Stack Trace를 로깅시켜 줍니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687934566690&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try {
	throw new Exception();
} catch(Exception ex) {
	log.error(&quot;Error on: &quot;, ex);
}

-&amp;gt;

Error on:
java.lang.Exception: Test
	at hello.itemservice.web.basic.BasicItemController.init(BasicItemController.java:38) ~[classes/:na]
	at hello.itemservice.web.basic.BasicItemControllerTest.initTest(BasicItemControllerTest.java:25) ~[classes/:na]
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:na]
	...&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Exception Message&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Exception Message는 치환문자를 이용하여 exception의 메세지를 넘겨주면 됩니다.&lt;/p&gt;
&lt;pre id=&quot;code_1687934656783&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try {
	throw new Exception(&quot;Error Message Test&quot;);
} catch(Exception ex) {
	log.error(&quot;Error Message: {}&quot;, ex.getMessage());
}&lt;/code&gt;&lt;/pre&gt;</description>
      <category>Java-Kotlin/Spring Boot</category>
      <category>Java</category>
      <category>spring</category>
      <category>spring boot</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/31</guid>
      <comments>https://parkstate.tistory.com/31#entry31comment</comments>
      <pubDate>Sat, 1 Jul 2023 01:50:24 +0900</pubDate>
    </item>
    <item>
      <title>도커와 CI환경 - (5) 도커(docker) 이미지 다뤄보기</title>
      <link>https://parkstate.tistory.com/30</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;도커 이미지 생성하는 순서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. dockerfile 작성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. docker 클라이언트 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. docker 서버 전달&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 이미지 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;dockerfile이란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker image를 만들기 위한 설정 파일입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너가 어떻게 행동할지에 대한 설정들을 정의해줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;docker 서버&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;docker 클라이언트에 전달된 모든 중요한 작업들을 하는 곳입니다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;dockerfile 만들기&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;dockerfile 만드는 순서&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. base image를 명시.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 추가적으로 필요한 파일을 다운 받기 위한 명령어들을 명시.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 컨테이너 시작시 실행 될 명령어를 명시.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;base image란?&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 이미지는 여러개의 레이어로 구성되어있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그 중에서 베이스 이미지는 이 이미지의 기반이 되는 부분입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;실습(hello를 출력하는 dockerfile 만들고 실행)&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. dockerfile을 만들 폴더 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;220&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZJFp8/btrV56DMnCf/B6V5yFtvnh7U0fEcsXt9UK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZJFp8/btrV56DMnCf/B6V5yFtvnh7U0fEcsXt9UK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZJFp8/btrV56DMnCf/B6V5yFtvnh7U0fEcsXt9UK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZJFp8%2FbtrV56DMnCf%2FB6V5yFtvnh7U0fEcsXt9UK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;240&quot; height=&quot;220&quot; data-origin-width=&quot;240&quot; data-origin-height=&quot;220&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 그 폴더에 dockerfile 생성&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;136&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/yVyYC/btrV67oEP1w/WqF6DZ9yOMdPewzC4jKkxk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/yVyYC/btrV67oEP1w/WqF6DZ9yOMdPewzC4jKkxk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/yVyYC/btrV67oEP1w/WqF6DZ9yOMdPewzC4jKkxk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FyVyYC%2FbtrV67oEP1w%2FWqF6DZ9yOMdPewzC4jKkxk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;400&quot; height=&quot;136&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;136&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. base image 명시&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. hello를 출력하는 명령어 명시&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2272&quot; data-origin-height=&quot;1760&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NPDyR/btrV5Ndonfp/EdAi1GmpdyZpB7UbwCPVGK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NPDyR/btrV5Ndonfp/EdAi1GmpdyZpB7UbwCPVGK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NPDyR/btrV5Ndonfp/EdAi1GmpdyZpB7UbwCPVGK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNPDyR%2FbtrV5Ndonfp%2FEdAi1GmpdyZpB7UbwCPVGK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2272&quot; height=&quot;1760&quot; data-origin-width=&quot;2272&quot; data-origin-height=&quot;1760&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 dockerfile을 실행하려면 빌드를 해주어야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;도커 이미지 빌드&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 폴더에서&lt;/p&gt;
&lt;pre id=&quot;code_1673539271101&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build ./&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지에 이름을 넣어서 빌드할수도 있습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1673539397148&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker build -t {나의 docker id}/{저장소 또는 프로젝트 이름}:{버전} ./&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지에 이름을 넣을때는 형식에 맞춰서 넣어주어야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1673539448490&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;예시

docker build -t pokoed3012/hello:latest&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;410&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/xJMTv/btrV5oZiyVk/fB90x2H9evWZj0kYluB0kk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/xJMTv/btrV5oZiyVk/fB90x2H9evWZj0kYluB0kk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/xJMTv/btrV5oZiyVk/fB90x2H9evWZj0kYluB0kk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FxJMTv%2FbtrV5oZiyVk%2FfB90x2H9evWZj0kYluB0kk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1444&quot; height=&quot;410&quot; data-origin-width=&quot;1444&quot; data-origin-height=&quot;410&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;212&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cjJZYk/btrV7egV7TS/aLfrCgFxCkGy3v57UegSn0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cjJZYk/btrV7egV7TS/aLfrCgFxCkGy3v57UegSn0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cjJZYk/btrV7egV7TS/aLfrCgFxCkGy3v57UegSn0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcjJZYk%2FbtrV7egV7TS%2FaLfrCgFxCkGy3v57UegSn0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;918&quot; height=&quot;212&quot; data-origin-width=&quot;918&quot; data-origin-height=&quot;212&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;build 명령어&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;build는 해당 디렉토리 내에서 dockerfile을 찾아서 docker클라이언트에 전달시켜줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;build 과정&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. alpine 이미지를 가져옴&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. 임시 컨테이너 생성(하드 디스크에 파일 시스템 스냅샷 추가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. 임시 컨테이너 실행(시작시 실행할 명령어 추가)&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. alpine 이미지 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;base image와 레이어들을 임시 컨테이너에 넣고, 명령어를 임시 컨테이너에 넣습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;그리고 그 임시 컨테이너를 토대로 새로운 이미지가 생성되고, 임시 컨테이너가 삭제됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;생성한 이미지 실행&lt;/h3&gt;
&lt;pre id=&quot;code_1673541274009&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run -it 이미지아이디또는이름&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;86&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/zALc1/btrV5oEYpbf/QVTOiaa085kk4keyBaeA51/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/zALc1/btrV5oEYpbf/QVTOiaa085kk4keyBaeA51/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/zALc1/btrV5oEYpbf/QVTOiaa085kk4keyBaeA51/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FzALc1%2FbtrV5oEYpbf%2FQVTOiaa085kk4keyBaeA51%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;972&quot; height=&quot;86&quot; data-origin-width=&quot;972&quot; data-origin-height=&quot;86&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CI-CD</category>
      <category>ci/cd</category>
      <category>도커(Docker)</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/30</guid>
      <comments>https://parkstate.tistory.com/30#entry30comment</comments>
      <pubDate>Mon, 16 Jan 2023 00:35:31 +0900</pubDate>
    </item>
    <item>
      <title>도커와 CI환경 - (4) 도커(docker) 컨테이너의 생명주기</title>
      <link>https://parkstate.tistory.com/29</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;도커 컨테이너의 생명주기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커 컨테이너의 생명주기는 이렇습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1. 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2. 시작&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3. 실행&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;4. 중지&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;5. 삭제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;생성&lt;/h3&gt;
&lt;pre id=&quot;code_1673537475176&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker create 이미지이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지로 컨테이너를 생성합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지의 파일 스냅샷이 컨테이너의 저장공간에 들어가고 명령어가 실행되지 않은 상태입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;시작&lt;/h3&gt;
&lt;pre id=&quot;code_1673537690701&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker start 컨테이너아이디또는이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너를 실행시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이미지의 명령어를 실행시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;docker run&lt;/h3&gt;
&lt;pre id=&quot;code_1673537814223&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run 이미지이름또는아이디&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위에 있는 생성과 시작을 한번에 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;보통 create와 start를 따로 사용하기보다는 docker run을 자주 사용합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;중지&lt;/h3&gt;
&lt;pre id=&quot;code_1673537893733&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker stop 컨테이너아이디또는이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하던 작업들을 완료하고 컨테이너를 중지합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1673537939985&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker kill 컨테이너아이디또는이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너를 그냥 바로 중지시킵니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;stop과 kill 차이&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;stop은 컨테이너가 하던 작업들을 완료하고 컨테이너를 안전하게 중지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;kill은 컨테이너가 하던 작업들을 신경 안쓰고 그냥 컨테이너를 중지합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;삭제&lt;/h3&gt;
&lt;pre id=&quot;code_1673538038387&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker rm 컨테이너아이디또는이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;중지된 컨테이너를 삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너가 실행중일때는 삭제하지 못합니다. 그래서 컨테이너를 중지시키고 삭제해야합니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;모든 컨테이너 삭제&lt;/h4&gt;
&lt;pre id=&quot;code_1673538121804&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker rm `docker ps -a -q`&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 컨테이너를 삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;이미지 삭제&lt;/h4&gt;
&lt;pre id=&quot;code_1673538166602&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker rmi 이미지아이디&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;컨테이너, 이미지, 네트워크 모두 삭제&lt;/h4&gt;
&lt;pre id=&quot;code_1673538196202&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker system prune&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커의 컨테이너, 이미지, 네트워크를 모두 삭제합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CI-CD</category>
      <category>ci/cd</category>
      <category>도커(Docker)</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/29</guid>
      <comments>https://parkstate.tistory.com/29#entry29comment</comments>
      <pubDate>Fri, 13 Jan 2023 00:44:05 +0900</pubDate>
    </item>
    <item>
      <title>도커와 CI환경 - (3) 도커(docker) 사용해보기</title>
      <link>https://parkstate.tistory.com/28</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;도커 컨테이너 실행해 보기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도커를 설치했으니 명령어를 입력해 봅시다.&lt;/p&gt;
&lt;pre id=&quot;code_1672240803180&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker run 이미지이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;위 명령어를 터미널에 치면 컴퓨터에 있는 도커 클라이언트가 도커 서버(daemon)를 통해 이미지 캐시 저장소에서 이미지를 찾고, 만약 이미지가 없으면 이미지들을 보관하는 도커 허브에서 이미지를 가져와서 컨테이너로 만들어서 실행합니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;990&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/AClRa/btrUOK3gYCh/nI5Brj57nCvoKLxBWJtx10/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/AClRa/btrUOK3gYCh/nI5Brj57nCvoKLxBWJtx10/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/AClRa/btrUOK3gYCh/nI5Brj57nCvoKLxBWJtx10/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FAClRa%2FbtrUOK3gYCh%2FnI5Brj57nCvoKLxBWJtx10%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1234&quot; height=&quot;990&quot; data-origin-width=&quot;1234&quot; data-origin-height=&quot;990&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도커 컨테이너 종료시키기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 종료&lt;/p&gt;
&lt;pre id=&quot;code_1672242966145&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker stop 컨테이너이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 강제 종료 (SIGKILL 시그널 전달)&lt;/p&gt;
&lt;pre id=&quot;code_1672243034570&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker kill 컨테이너이름&lt;/code&gt;&lt;/pre&gt;</description>
      <category>CI-CD</category>
      <category>ci/cd</category>
      <category>도커(Docker)</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/28</guid>
      <comments>https://parkstate.tistory.com/28#entry28comment</comments>
      <pubDate>Thu, 29 Dec 2022 00:58:41 +0900</pubDate>
    </item>
    <item>
      <title>도커와 CI환경 - (2) m1 맥 도커(docker)설치</title>
      <link>https://parkstate.tistory.com/27</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;도커(docker)설치 순서&lt;/h2&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;1. docker.com으로 이동&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;2. Get Started 버튼 클릭&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;3. Download for Mac - Apple Chip 버튼 클릭&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;4. 설치파일 실행 및 설치&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;5. Docker 회원가입&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;6. Docker 로그인&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. docker.com으로 이동&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.docker.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.docker.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1672071191472&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Docker: Accelerated, Containerized Application Development&quot; data-og-description=&quot;Docker is a platform designed to help developers build, share, and run modern applications. We handle the tedious setup, so you can focus on the code.&quot; data-og-host=&quot;www.docker.com&quot; data-og-source-url=&quot;https://www.docker.com/&quot; data-og-url=&quot;https://www.docker.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bolOzq/hyQ33jadq2/zNL1Pmg3UkLPkxw0KJkazK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/h8pNF/hyQ35uwxzJ/dBOOu7dMO3wkUHaga2B1k0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.docker.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.docker.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bolOzq/hyQ33jadq2/zNL1Pmg3UkLPkxw0KJkazK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/h8pNF/hyQ35uwxzJ/dBOOu7dMO3wkUHaga2B1k0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Docker: Accelerated, Containerized Application Development&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Docker is a platform designed to help developers build, share, and run modern applications. We handle the tedious setup, so you can focus on the code.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Get Started 버튼 클릭&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;웹사이트 우측 상단에 있는 Get Started 버튼을 클릭 해 주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tnKoW/btrUHRvtVg8/GyMFzBYxTdspEOU9wK3lkK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tnKoW/btrUHRvtVg8/GyMFzBYxTdspEOU9wK3lkK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tnKoW/btrUHRvtVg8/GyMFzBYxTdspEOU9wK3lkK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtnKoW%2FbtrUHRvtVg8%2FGyMFzBYxTdspEOU9wK3lkK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2032&quot; height=&quot;1167&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Download for Mac - Apple Chip 버튼 클릭&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Download for Mac - Intel Chip 버튼에 마우스 커서를 올리면 Apple Chip 버튼이 나옵니다. Apple Chip 버튼을 클릭하여 파일을 다운해주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1167&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cdOAxN/btrUI0S1zw5/xhScZQwK2fbkDPKwaKyRjk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cdOAxN/btrUI0S1zw5/xhScZQwK2fbkDPKwaKyRjk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cdOAxN/btrUI0S1zw5/xhScZQwK2fbkDPKwaKyRjk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcdOAxN%2FbtrUI0S1zw5%2FxhScZQwK2fbkDPKwaKyRjk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2032&quot; height=&quot;1167&quot; data-origin-width=&quot;2032&quot; data-origin-height=&quot;1167&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 설치파일 실행 및 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker.dmg 파일을 실행시켜서 Docker.app을 응용프로그램 폴더로 옮겨주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1664&quot; data-origin-height=&quot;904&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RZorz/btrUHSuq6hL/rcTWPah9XDnA3aEdV8KkA0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RZorz/btrUHSuq6hL/rcTWPah9XDnA3aEdV8KkA0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RZorz/btrUHSuq6hL/rcTWPah9XDnA3aEdV8KkA0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRZorz%2FbtrUHSuq6hL%2FrcTWPah9XDnA3aEdV8KkA0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1664&quot; height=&quot;904&quot; data-origin-width=&quot;1664&quot; data-origin-height=&quot;904&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5 / 6. Docker 회원가입 / 로그인&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker를 실행시키면 상단 네비게이션 바에 도커 아이콘이 생깁니다. 도커 아이콘을 누르고 Sign in / Create Docker ID를 누르고&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Docker계정을 만들고 로그인해주세요.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;스크린샷 2022-12-28 00.36.26.png&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;846&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/NwaWJ/btrUL7xj9LI/FcfYyFkaIvMAvdts8BCnrk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/NwaWJ/btrUL7xj9LI/FcfYyFkaIvMAvdts8BCnrk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/NwaWJ/btrUL7xj9LI/FcfYyFkaIvMAvdts8BCnrk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FNwaWJ%2FbtrUL7xj9LI%2FFcfYyFkaIvMAvdts8BCnrk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;654&quot; height=&quot;846&quot; data-filename=&quot;스크린샷 2022-12-28 00.36.26.png&quot; data-origin-width=&quot;654&quot; data-origin-height=&quot;846&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 6가지 단계로 도커를 애플 실리콘 맥에 설치할 수 있습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>CI-CD</category>
      <category>ci/cd</category>
      <category>도커(Docker)</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/27</guid>
      <comments>https://parkstate.tistory.com/27#entry27comment</comments>
      <pubDate>Wed, 28 Dec 2022 00:50:27 +0900</pubDate>
    </item>
    <item>
      <title>도커와 CI환경 - (1) 도커(docker)와 컨테이너, 도커를 쓰는 이유</title>
      <link>https://parkstate.tistory.com/26</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;도커(docker)란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffff00;&quot;&gt;컨테이너&lt;/span&gt;를 사용하여 응용프로그램을 더 쉽게 만들고 배포하고 실행할 수 있도록 설계된 도구입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffff00;&quot;&gt;컨테이너&lt;/span&gt; 기반의 오픈소스 가상화 플랫폼, 생태계입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://www.docker.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://www.docker.com/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1672071191472&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;Docker: Accelerated, Containerized Application Development&quot; data-og-description=&quot;Docker is a platform designed to help developers build, share, and run modern applications. We handle the tedious setup, so you can focus on the code.&quot; data-og-host=&quot;www.docker.com&quot; data-og-source-url=&quot;https://www.docker.com/&quot; data-og-url=&quot;https://www.docker.com/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bolOzq/hyQ33jadq2/zNL1Pmg3UkLPkxw0KJkazK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/h8pNF/hyQ35uwxzJ/dBOOu7dMO3wkUHaga2B1k0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://www.docker.com/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://www.docker.com/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bolOzq/hyQ33jadq2/zNL1Pmg3UkLPkxw0KJkazK/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/h8pNF/hyQ35uwxzJ/dBOOu7dMO3wkUHaga2B1k0/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Docker: Accelerated, Containerized Application Development&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Docker is a platform designed to help developers build, share, and run modern applications. We handle the tedious setup, so you can focus on the code.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;www.docker.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너라는 개념이 자주 나오는데, 컨테이너는 뭘까요?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;컨테이너(Container)란?&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;일반적인 컨테이너의 개념&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물건을 다양한 운송수단으로 쉽게 옮길 수 있도록 하는 상자 모양의 수송용기입니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;393&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/FAjrT/btrUzlQW3FP/lNdAl7Lo7OkKtpu2p6HEm1/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/FAjrT/btrUzlQW3FP/lNdAl7Lo7OkKtpu2p6HEm1/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/FAjrT/btrUzlQW3FP/lNdAl7Lo7OkKtpu2p6HEm1/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FFAjrT%2FbtrUzlQW3FP%2FlNdAl7Lo7OkKtpu2p6HEm1%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;700&quot; height=&quot;393&quot; data-origin-width=&quot;700&quot; data-origin-height=&quot;393&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;서버의 컨테이너 개념&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너 안에 여러가지의 실행환경과 프로그램을 넣어 이미지화 시켜 AWS(Amazon Web Service), GCP(Google Cloud Platform), Microsoft Azure등의 여러가지 환경에서 같은 실행 할 수 있도록 하는 박스입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;도커를 쓰는 이유&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;AWS&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #f1f4f6;&quot;&gt;Docker를 사용하면 코드를 더 빨리 전달하고, 애플리케이션 운영을 표준화하고, 코드를 원활하게 이동하고, 리소스 사용률을 높여 비용을 절감할 수 있습니다. Docker를 사용하면 어디서나 안정적으로 실행할 수 있는 단일 객체를 확보하게 됩니다. Docker의 간단한 구문을 사용해 완벽하게 제어할 수 있습니다. 폭넓게 도입되었다는 것은 Docker를 사용할 수 있는 도구 및 상용 애플리케이션의 에코시스템이 강력하다는 의미입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/docker/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://aws.amazon.com/ko/docker/&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1672072638509&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;company&quot; data-og-title=&quot;Docker란 무엇입니까? | AWS&quot; data-og-description=&quot;Q: Docker로 어떤 작업을 할 수 있습니까? Docker를 사용하면 환경에 구애받지 않고 애플리케이션을 신속하게 배포 및 확장할 수 있으며 코드가 문제없이 실행될 것임을 확신할 수 있습니다. 이는 Doc&quot; data-og-host=&quot;aws.amazon.com&quot; data-og-source-url=&quot;https://aws.amazon.com/ko/docker/&quot; data-og-url=&quot;https://aws.amazon.com/ko/docker/&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bblMt9/hyQ3WK7lq9/kCRCcG6uvwEjkHRk8N20Ek/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bFVPaB/hyQ3UfsIUx/3KcywcljKNlhTJTvZsIhe0/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109,https://scrap.kakaocdn.net/dn/jsErC/hyQ31llT6i/YJjOUAgzLvXek5favbL9tK/img.png?width=616&amp;amp;height=318&amp;amp;face=0_0_616_318&quot;&gt;&lt;a href=&quot;https://aws.amazon.com/ko/docker/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://aws.amazon.com/ko/docker/&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bblMt9/hyQ3WK7lq9/kCRCcG6uvwEjkHRk8N20Ek/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630,https://scrap.kakaocdn.net/dn/bFVPaB/hyQ3UfsIUx/3KcywcljKNlhTJTvZsIhe0/img.png?width=179&amp;amp;height=109&amp;amp;face=0_0_179_109,https://scrap.kakaocdn.net/dn/jsErC/hyQ31llT6i/YJjOUAgzLvXek5favbL9tK/img.png?width=616&amp;amp;height=318&amp;amp;face=0_0_616_318');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;Docker란 무엇입니까? | AWS&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Q: Docker로 어떤 작업을 할 수 있습니까? Docker를 사용하면 환경에 구애받지 않고 애플리케이션을 신속하게 배포 및 확장할 수 있으며 코드가 문제없이 실행될 것임을 확신할 수 있습니다. 이는 Doc&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;aws.amazon.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Azure&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로소프트 Azure는 3단어로 설명합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffff00;&quot;&gt;민첩성, 이식성, 신속한 확장성&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://azure.microsoft.com/ko-kr/resources/cloud-computing-dictionary/what-is-a-container#layout-container-uid8df5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://azure.microsoft.com/ko-kr/resources/cloud-computing-dictionary/what-is-a-container#layout-container-uid8df5&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1672072634095&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;컨테이너란?| Microsoft Azure&quot; data-og-description=&quot;Docker 컨테이너는 애플리케이션 패키징 및 배포를 위한 변경이 불가능한 인프라를 제공합니다. 컨테이너화는 리소스의 민첩성, 확장성 및 효율적인 사용을 개선합니다.&quot; data-og-host=&quot;azure.microsoft.com&quot; data-og-source-url=&quot;https://azure.microsoft.com/ko-kr/resources/cloud-computing-dictionary/what-is-a-container#layout-container-uid8df5&quot; data-og-url=&quot;https://azure.microsoft.com/ko-kr/resources/cloud-computing-dictionary/what-is-a-container&quot; data-og-image=&quot;&quot;&gt;&lt;a href=&quot;https://azure.microsoft.com/ko-kr/resources/cloud-computing-dictionary/what-is-a-container#layout-container-uid8df5&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://azure.microsoft.com/ko-kr/resources/cloud-computing-dictionary/what-is-a-container#layout-container-uid8df5&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url();&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너란?| Microsoft Azure&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Docker 컨테이너는 애플리케이션 패키징 및 배포를 위한 변경이 불가능한 인프라를 제공합니다. 컨테이너화는 리소스의 민첩성, 확장성 및 효율적인 사용을 개선합니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;azure.microsoft.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Google Cloud Platform(GCP)&lt;/h4&gt;
&lt;p id=&quot;separation-of-responsibility&quot; data-text=&quot;                       책임 분리&quot; data-ke-size=&quot;size18&quot;&gt;책임 분리&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너화를 통해 책임을 깔끔하게 분리할 수 있습니다. 즉, 개발자는 애플리케이션의 로직과 종속 항목에 집중하고, IT 운영팀은 특정 소프트웨어 버전 및 구성과 같은 애플리케이션의 세부 요소 대신 배포 및 관리에 집중할 수 있습니다.&lt;/p&gt;
&lt;p id=&quot;workload-portability&quot; data-text=&quot;                       워크로드 이동성&quot; data-ke-size=&quot;size18&quot;&gt;워크로드 이동성&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너는 Linux, Windows, Mac 등의 운영체제를 가리지 않고, 가상 머신, 물리적 서버, 개발자 컴퓨터, 데이터 센터, 온프레미스 환경, 퍼블릭 클라우드 등 사실상 어느 환경에서나 구동되므로 개발 및 배포가 크게 쉬워집니다.&amp;nbsp;&lt;/p&gt;
&lt;p id=&quot;application-isolation&quot; data-text=&quot;                       애플리케이션 격리&quot; data-ke-size=&quot;size18&quot;&gt;애플리케이션 격리&lt;/p&gt;
&lt;div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨테이너는 운영체제 수준에서 CPU, 메모리, 스토리지, 네트워크 리소스를 가상화하므로 개발자에게 다른 애플리케이션으로부터 논리적으로 격리된 OS 환경을 제공합니다.&lt;/p&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://cloud.google.com/learn/what-are-containers?hl=ko#section-3&quot;&gt;https://cloud.google.com/learn/what-are-containers?hl=ko#section-3&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;figure id=&quot;og_1672072687845&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;website&quot; data-og-title=&quot;컨테이너란? &amp;nbsp;|&amp;nbsp; Google Cloud&quot; data-og-description=&quot;컨테이너는 어떤 환경에서나 실행하기 위해 필요한 모든 요소를 포함하는 경량 소프트웨어 패키지입니다.&quot; data-og-host=&quot;cloud.google.com&quot; data-og-source-url=&quot;https://cloud.google.com/learn/what-are-containers?hl=ko#section-3&quot; data-og-url=&quot;https://cloud.google.com/learn/what-are-containers?hl=ko&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bDgTBe/hyQ1sZdEQk/kfCZvDFlBkBrf3KZrO033k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630&quot;&gt;&lt;a href=&quot;https://cloud.google.com/learn/what-are-containers?hl=ko#section-3&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://cloud.google.com/learn/what-are-containers?hl=ko#section-3&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bDgTBe/hyQ1sZdEQk/kfCZvDFlBkBrf3KZrO033k/img.png?width=1200&amp;amp;height=630&amp;amp;face=0_0_1200_630');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너란? &amp;nbsp;|&amp;nbsp; Google Cloud&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;컨테이너는 어떤 환경에서나 실행하기 위해 필요한 모든 요소를 포함하는 경량 소프트웨어 패키지입니다.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;cloud.google.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>CI-CD</category>
      <category>ci/cd</category>
      <category>도커(Docker)</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/26</guid>
      <comments>https://parkstate.tistory.com/26#entry26comment</comments>
      <pubDate>Tue, 27 Dec 2022 01:46:36 +0900</pubDate>
    </item>
    <item>
      <title>2022.10.21 최근 근황</title>
      <link>https://parkstate.tistory.com/25</link>
      <description>&lt;h1&gt;최근 근황&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;안녕하세요! 블로그 글 쓰는 게 거의 두 달 만이네요... 최근 두 달 동안 저는 굉장히 많은 일이 있었습니다.&lt;br /&gt;이번 글에서는 블로그 복귀 겸 해서, 이 두 달 동안 있었던 일들을 써보려고 합니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;첫 회사 취업&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 취업을 하여 초기 스타트업에서 일하게 되었습니다. 제 인생에 첫 회사이기 때문에 배울 것도, 익숙해져야 할 것들도 굉장히 많았습니다.&lt;br /&gt;제가 학교에서 배우거나 사용하지 않던 툴들, 개발 방식, 개발 프레임워크 등등을 모두 배우고 익숙해져야 해서 굉장히 정신없는 나날을 보냈습니다.&lt;br /&gt;이렇게 회사에서 일하게 되니 제가 그동안 얼마나 부족한 신입 개발자였는지 느끼게 되었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회사 적응과 학교와의 차이점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에는 당연히 저보다 개발에 대한 지식이 많은 개발자 분들이 많았고, 이런 개발자 분들의 도움 덕분에 제가 회사에 적응할 수 있었습니다.&lt;br /&gt;저 같은 경우엔 첫 출근 날부터 이것 저것 업무를 배정받아서 인수인계받았는데, 제 친구들은 회사에서 업무에 투입되진 않고 공부를 먼저 시키는 경우가 많았습니다.&lt;br /&gt;그런 부분이 부럽기도 했지만, 저는 프로젝트에 부딪히며 빠르게 성장하는 저의 모습을 보면서 이렇게 프로젝트를 하며 배우는 게 굉장히 많다는 생각에 부럽다는 생각이 잠겨 사라졌습니다.&lt;br /&gt;실제로 8월의 저와 지금 이 글을 적고 있는 저는 개발 지식이 굉장히 차이가 많이 난다고 생각하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 스스로 학교에서 회사처럼 프로젝트를 하며 성장했다고 생각했는데, 실제 회사의 프로젝트는 제가 학교에서 겪은 프로젝트와는 차원이 달랐습니다.&lt;br /&gt;학교에서 제가 한 프로젝트는 프로젝트 겉핥기라고 느껴질 정도로 실제 프로젝트는 꼼꼼했고, 역할과 절차가 나뉘어 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;역시 회사를 다니니 굉장히 다양한 개발자분들을 만나며 많이 배울 수 있고, 많은 경험을 할 수 있어서 행복하네요!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;새로 배우고 있는 기술&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 현재 제가 맡고 있는 부분은 프런트엔드 유지보수 및 개발과, 백엔드 개발입니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;프론트엔드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프런트엔드 개발을 하기 위한 react와 redux 등을 다시 복습하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프론트엔드 기술 목록&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;React&lt;/li&gt;
&lt;li&gt;Redux&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;백엔드&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 개발을 하기 위한 java와 spring 등은 여전히 배우고 있습니다.&lt;br /&gt;또 저희 회사의 백엔드가 대부분 python의 fastApi로 동작함에 따라 python과 fastApi도 차차 배우고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;백엔드 기술 목록&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring Boot(Java)&lt;/li&gt;
&lt;li&gt;FastApi(Python)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;데이터베이스&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저희 회사는 데이터베이스를 MySql과 PostgreSql을 사용하고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 한 번도 사용해보지 않은 PostgreSql에 더 집중하여 배우고 있습니다.&lt;br /&gt;근데 전반적인 sql실력이 부족하다 느껴져 sql도 다시 배울 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터베이스 기술 목록&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MySql&lt;/li&gt;
&lt;li&gt;PostgreSql&lt;/li&gt;
&lt;li&gt;DBeaber&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;AWS(Amazon Web Service)&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;학교에서는 AWS를 한 번도 사용해 보거나 배울 기회가 없었는데, 회사에 오니 AWS의 서비스를 많이 사용하고 있고, 이에 대한 기본 지식이 필수여서&lt;br /&gt;회사에서 사용하고 있는 AWS기술에 대해 배우고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;배우고 있는 AWS 서비스 목록&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EC2&lt;/li&gt;
&lt;li&gt;ECR(Elastic Container Registry)&lt;/li&gt;
&lt;li&gt;App Runner&lt;/li&gt;
&lt;li&gt;S3&lt;/li&gt;
&lt;li&gt;CloudFront&lt;/li&gt;
&lt;li&gt;Lightsail&lt;/li&gt;
&lt;li&gt;RDS&lt;/li&gt;
&lt;li&gt;CloudWatch&lt;/li&gt;
&lt;li&gt;Route 53&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Git과 Github&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발한 내용을 올릴 때 형상관리 도구를 회사에서 Git을 사용하기 때문에 Git을 배웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 Github를 사용하기 때문에 Github의 기능을 많이 배웠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기술 목록&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Github Action&lt;/li&gt;
&lt;li&gt;Git Flow&lt;/li&gt;
&lt;li&gt;Github issue&lt;/li&gt;
&lt;li&gt;PR(Pull Request)&lt;/li&gt;
&lt;li&gt;Git&lt;/li&gt;
&lt;li&gt;pre-commit(code style)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문서 작성 도구&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사에서 notion을 사용 중입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;회사를 다니며...&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회사를 다니면서 제가 가장 크게 느낀 점은 배우면 배울수록 더 배울게 많이 생긴다는 것입니다.&lt;br /&gt;그리고 배우고 기록을 안 하면 잊었을 때 다시 배우기 쉽지 않아서 다시 블로그를 적어야겠다고 생각이 들어서 다시 블로그를 적기로 하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 조만간은 지금까지 배운 것들을 복습하며 글을 적을 것 같습니다. 배운 거 복습하는 글을 다 적고 나면 또 배우고 있는 걸 다시 적게 되겠죠...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;다시 한번 열심히 적어보겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 글 읽어주셔서 감사합니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;edited_IMG_4264.JPG&quot; data-origin-width=&quot;2909&quot; data-origin-height=&quot;2159&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/eeq7qy/btrPaMMCK5U/QopKb2bA1rW5jNOjfREcU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/eeq7qy/btrPaMMCK5U/QopKb2bA1rW5jNOjfREcU0/img.png&quot; data-alt=&quot;회사에서 찍은 전경입니다.&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/eeq7qy/btrPaMMCK5U/QopKb2bA1rW5jNOjfREcU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Feeq7qy%2FbtrPaMMCK5U%2FQopKb2bA1rW5jNOjfREcU0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;2909&quot; height=&quot;2159&quot; data-filename=&quot;edited_IMG_4264.JPG&quot; data-origin-width=&quot;2909&quot; data-origin-height=&quot;2159&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;회사에서 찍은 전경입니다.&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;</description>
      <category>일상/회고</category>
      <category>Til</category>
      <category>근황</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/25</guid>
      <comments>https://parkstate.tistory.com/25#entry25comment</comments>
      <pubDate>Fri, 21 Oct 2022 19:00:57 +0900</pubDate>
    </item>
    <item>
      <title>2022.08.08 JS axios를 Client에서 사용할때 뜨는 CORS 오류</title>
      <link>https://parkstate.tistory.com/24</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;주말에 일어난 일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주말에 친구집을 가서 놀다가 새벽에 친구가 만들고 싶다는 학교 시간표 웹을 제작하는 모습을 보며 훈수를 두고 있었습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;근데 굉장히 익숙한 CORS 오류를 오랜만에 만나서 반가웠습니다 ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CORS(Cross Origin Resource Sharing) 오류는...&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;브라우저에있는 CORS 정책이 있는데 그것에 위배되어서 생기는 오류라고 알고있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자세한 부분은 저보다 다른 분들이 더 잘아시기 때문에 자료를 올리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CORS란... &lt;a href=&quot;https://evan-moon.github.io/2020/05/21/about-cors/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://evan-moon.github.io/2020/05/21/about-cors/&lt;/a&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 저희는 이렇게 CORS 오류를 해결했습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;CORS 오류 해결 방법&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래는 웹에서 바로 JS로 학교 시간표 API에 요청했지만, 학교 시간표 API에 요청하는 서버를 하나 따로 만들어서 웹은 요청하는 서버에 API를 가져와달라고 요청하고, 서버는 웹에서 요청이 오면 API에 정보를 가져와서 웹으로 다시 정보를 줍니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;소스코드... &lt;a href=&quot;https://github.com/yddl3013/Timetable&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/yddl3013/Timetable&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1659960421276&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - yddl3013/Timetable&quot; data-og-description=&quot;Contribute to yddl3013/Timetable development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/yddl3013/Timetable&quot; data-og-url=&quot;https://github.com/yddl3013/Timetable&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/x5GXI/hyPmdJcRSS/pcTPk52ljCKgEqrHirc8n0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/yddl3013/Timetable&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/yddl3013/Timetable&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/x5GXI/hyPmdJcRSS/pcTPk52ljCKgEqrHirc8n0/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - yddl3013/Timetable&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to yddl3013/Timetable development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아무래도 방학이다보니 개발이 손에 잡히지를 않아서 글도 자주 쓰지 않게 되네요.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 마지막 방학이니 되게 열심히 방학을 보내고 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;개발 공부에 찌들었던 저의 삶과 제 정신을 좀 회복하는 계기가 된거 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럼 또 다음에 글로 만나뵙겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다.&lt;/p&gt;</description>
      <category>JS-TS</category>
      <category>방학</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/24</guid>
      <comments>https://parkstate.tistory.com/24#entry24comment</comments>
      <pubDate>Mon, 8 Aug 2022 21:12:37 +0900</pubDate>
    </item>
    <item>
      <title>2022.07.16 학교 1 팀 1 기업 프로젝트(밈품명품) 마감 직전 스퍼트</title>
      <link>https://parkstate.tistory.com/22</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;1 팀 1 기업 프로젝트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;약 5달 전부터 학교랑 매칭 된 회사의 웹사이트와 기획안과 웹사이트 디자인을 퍼블리싱하는 프로젝트입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난번에는 밈품명품 프로젝트가 아니라 다른 프로젝트였지만 그 기획이 엎어지는 바람에 한 3달을 대기하다가&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;변경된 프로젝트를 받았는데 그 프로젝트는 굉장히 레트로 한 분위기의 웹 퍼블리싱 프로젝트였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;제가 맡은 역할&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 맡은 역할은 원래는 백엔드 개발팀 팀장이었지만, 엎어지면서 백엔드 개발이 아닌 이 프로젝트에서 프로젝트 매니저(PM) 역할을 맡게 되었습니다.&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 매니저로서 했던 일&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 프로젝트 매니저로서 프로젝트 초반에 애자일 개발 방법론을 토대로 여러 스프린트를 기획하였고, 저희 프로젝트 팀이 협업할 수 있는 슬랙 워크스페이스를 생성하고, 이슈 추적 프로그램인 JIRA와 연결하였고, 프로젝트 팀이 함께 사용할 깃허브 레포지토리를 생성하여 커밋 또는 브랜치 머지 등의 새 알림이 슬랙에 오도록 연동하였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트를 크게 나누기&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 저희 팀의 인원들에게 줄 일을 세 가지로 나누었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 서비스를 구성하게 되는 여러 개의 &lt;b&gt;페이지&lt;/b&gt;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;공통으로 대부분의 페이지에서 사용되는 &lt;b&gt;공통 컴포넌트&lt;/b&gt;,&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;페이지를 구성하는 &lt;b&gt;세부 컴포넌트&lt;/b&gt;,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 세 가지로 나누었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 나눈 세 가지의 일에 1~3명씩 팀원을 붙였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 초반&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;처음에는 순조로웠습니다. 프로젝트 파일도 단순했고, 모두 잘 진행되어갔습니다. 진행하며 리소스(디자인 파일, 이미지 등) 중 빠진 부분들이 있었긴 했지만 컴포넌트들을 제작하며 중간중간에 회사에게 받지 못한 리소스들을 정리해서 회사로 요구했습니다. 이런 점은 좀 불편하긴 했지만 좋았습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 중반&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 중반입니다. 지금 생각해보면 저는 이때부터 무언가 잘못되어간다는 것을 알아챘어야 합니다. 하루 이틀 프로젝트 마감 기한이 다가오며 세부 컴포넌트를 열심히 제작하고 있을 때였습니다. 공통 컴포넌트팀은 학교 일정 때문에 잠시 대기하였고, 페이지 조립팀은 세부 컴포넌트가 제작돼야 시작할 수 있다고 생각하여 페이지 조립 일정은 차일피일 미뤄졌습니다. 그리고 프로젝트 마감 기한은 여전히 다가오고 있었습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;프로젝트 후반&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;문제는 프로젝트 후반에 발견되었습니다. 공통 컴포넌트는 쉽게 페이지로 조립이 되었지만, 세부 컴포넌트들은 너무 복잡하고 많아서 그것들끼리 엮기 쉽지 않았기 때문에 페이지 조립팀이 페이지 조립을 제대로 하지 못했습니다. 그리고 프로젝트 매니저인 저는 세부 컴포넌트 팀의 팀장에게 현황을 물어보며 프로젝트의 진행도를 계산하였고, 이는 실제와는 차이가 컸습니다. 이 차이는 프로젝트를 점점 갉아먹으며 커가다가 결국 커질 대로 커진 계산과 실제의 차이는 이번 주에 터졌습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;현재&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 프로젝트는 진행되가며 세부 컴포넌트를 조립하기 어렵다는 문제를 갖고 있었지만 제가 발견하지 못했고, 이 문제는 더 큰 문제를 불러왔습니다. 결국에 발견하지 못한 문제점은 페이지를 조립하거나 변경하기 어려울 정도로 커졌으며, 완성도는 바닥까지 추락하였습니다. 이렇게 상황이 나빠짐에 따라 프로젝트 팀은 내부 갈등이 생겼고, 이 내부 갈등은 마감이 얼마 남지 않은 어제 말싸움까지 번졌습니다. 하지만 이렇게 서로 칼을 갈아봐야 상황만 나빠질 뿐이었기에, 힘을 합쳐 일단은 지금 상태에서 최선을 다하여 마감을 해보자는 생각으로 새벽까지 개발에 몰두하고 있습니다. 하지만 나빠질 대로 나빠진 완성도였기에 처음 생각했던 기준까지 완성도를 끌어올리지 못하고, 다음에는 이런 실수를 하지 말자는 생각으로 새벽에 이렇게 글을 적습니다...&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;깨달은 점 세줄 요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 매니저는 팀원 하나하나를 살펴야 한다는 점을 알게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 것은 사소한 문제에서 시작된다는 점을 알게 되었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 이 모든 문제는 프로젝트 매니저로서 최선을 다해 프로젝트를 살피지 않았던 저의 책임이라는 것을 느꼈습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로는 이번 프로젝트로 깨달은 부분을 잊지 않고 다음에는 같은 실수를 되풀이하지 않도록 노력하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트에 저의 팀으로 함께 힘써주신 저의 동료이자 후배분들께 이 글에서라도 제가 실수한 점을 다시 사과드립니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 프로젝트는 저에게 심적으로 굉장히 얻어가는 게 많았던 프로젝트입니다. 이번 일을 저뿐만이 아닌 저의 후배분들도 반면교사 삼아서 더욱 성장할 수 있는 계기가 되었으면 하는 마음으로 제가 알게 된 점을 이렇게 글로 남깁니다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;좋은 경험을 이번 프로젝트로 하고 갑니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 글 끝까지 읽어주셔서 감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에 또 다른 글로 찾아오겠습니다.&lt;/p&gt;</description>
      <category>Side Project</category>
      <category>Til</category>
      <category>웹사이트 제작</category>
      <category>웹퍼블리싱</category>
      <category>팀 프로젝트</category>
      <category>학교 프로젝트</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/22</guid>
      <comments>https://parkstate.tistory.com/22#entry22comment</comments>
      <pubDate>Sat, 16 Jul 2022 03:26:19 +0900</pubDate>
    </item>
    <item>
      <title>2022.07.12 사이드 프로젝트(ChargerPin) 안드로이드 앱 프로젝트 생성</title>
      <link>https://parkstate.tistory.com/21</link>
      <description>&lt;h1&gt;사이드 프로젝트(ChargerPin project)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;ChargerPin 프로젝트의 서버는 지난번에 완성하였고, 이제는 Client인 안드로이드 앱을 생성하였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;안드로이드 앱 진행사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 프로젝트를 생성하여 git에 올려두었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;a href=&quot;https://github.com/pokoed/ChargerPinAndroidClient&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;ChargerPinAndroidClient&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1657626049094&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-ke-align=&quot;alignCenter&quot; data-og-type=&quot;object&quot; data-og-title=&quot;GitHub - pokoed/ChargerPinAndroidClient&quot; data-og-description=&quot;Contribute to pokoed/ChargerPinAndroidClient development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/pokoed/ChargerPinAndroidClient&quot; data-og-url=&quot;https://github.com/pokoed/ChargerPinAndroidClient&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/2brKp/hyO3hyGGE6/oCmyekpAmHm8c3U1jsw8Wk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600&quot;&gt;&lt;a href=&quot;https://github.com/pokoed/ChargerPinAndroidClient&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/pokoed/ChargerPinAndroidClient&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/2brKp/hyO3hyGGE6/oCmyekpAmHm8c3U1jsw8Wk/img.png?width=1200&amp;amp;height=600&amp;amp;face=0_0_1200_600');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot; data-ke-size=&quot;size16&quot;&gt;GitHub - pokoed/ChargerPinAndroidClient&lt;/p&gt;
&lt;p class=&quot;og-desc&quot; data-ke-size=&quot;size16&quot;&gt;Contribute to pokoed/ChargerPinAndroidClient development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot; data-ke-size=&quot;size16&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오늘 한 내용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 앱 프로젝트를 생성하고, 메인 Activity와 로그인/회원가입 Activity를 생성하였습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 할 내용&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음엔 오늘 생성해놓은 로그인/회원가입 Activity에 api의 로그인/회원가입 부분을 연결할 예정입니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 스프링을 벗어났는데 더 큰 적인 안드로이드네요... 앞으로도 더 힘내서 개발해보겠습니다.&lt;/p&gt;</description>
      <category>Side Project</category>
      <category>사이드 프로젝트</category>
      <category>안드로이드</category>
      <category>앱제작</category>
      <category>코틀린</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/21</guid>
      <comments>https://parkstate.tistory.com/21#entry21comment</comments>
      <pubDate>Tue, 12 Jul 2022 20:44:20 +0900</pubDate>
    </item>
    <item>
      <title>2022.07.11 사이드 프로젝트(ChargerPin) 즐겨찾기 기능 완성</title>
      <link>https://parkstate.tistory.com/20</link>
      <description>&lt;h1&gt;사이드 프로젝트(ChargerPin project)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 진행 중입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;구현할_기능들&quot; data-ke-size=&quot;size23&quot;&gt;구현할 기능들&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;s&gt;회원가입/로그인&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;전기차 충전소 검색&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;전기차 충전소 조회&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;전기차 충전소 즐겨찾기(컨트롤러, 서비스 제작)&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;즐겨찾기 조회&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;즐겨찾기 삭제&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;전기차 충전소 상세조회&lt;/s&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오늘 구현한 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즐겨찾기를 추가하고 조회하고 변경하고 지우는 즐겨찾기의 CRUD를 모두 제작하여 컨트롤러에 매핑해주었습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1657532610007&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;BookmarkController.java

@Slf4j
@CrossOrigin
@AllArgsConstructor
@Controller
@RequestMapping(&quot;/bookmark&quot;)
public class BookmarkController {
    private final BookmarkServiceImpl bookmarkService;

    @PostMapping(&quot;/add&quot;)
    public Object addBookmark(@RequestBody BookmarkRequest request) {
        log.info(&quot;/bookmark/add start&quot;);

        return bookmarkService.add(request);
    }

    @GetMapping(&quot;/read/{userId}&quot;)
    public Object readBookmark(@PathVariable String userId) {
        log.info(&quot;/bookmark/read start&quot;);

        return bookmarkService.read(userId);
    }

    @PatchMapping(&quot;/update&quot;)
    public Object updateBookmark(@RequestBody BookmarkRequest request) {
        log.info(&quot;/bookmark/update start&quot;);

        return bookmarkService.update(request);
    }

    @DeleteMapping(&quot;/delete/{id}&quot;)
    public Object deleteBookmark(@PathVariable Long id) {
        log.info(&quot;/bookmark/delete start&quot;);

        return bookmarkService.delete(id);
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 구현할 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입, 전기차 충전소 조회 및 즐겨찾기 추가, 제거 등의 기능이 있는 api가 완성되었으니 이제는 api를 연결해줄 앱을 만들어주도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앱을 연결하기 위해서 열심히 제작하던 api서버가 드디어 완성되었네요!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 앱을 제작해본 경험이 적어서 앞으로 api를 연결할 앱을 잘 제작할 수 있을지는 모르겠지만 구글의 힘을 빌려서 또 한번 힘내서 잘 제작해보도록 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Side Project</category>
      <category>api제작</category>
      <category>사이드 프로젝트</category>
      <category>스프링</category>
      <category>스프링 부트</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/20</guid>
      <comments>https://parkstate.tistory.com/20#entry20comment</comments>
      <pubDate>Mon, 11 Jul 2022 18:49:53 +0900</pubDate>
    </item>
    <item>
      <title>2022.07.11 박종연의 성장하는 개발 블로그 공지사항</title>
      <link>https://parkstate.tistory.com/notice/19</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;블로그 주인 현황&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 일단 고등학생입니다. 고등학생인 제가 가지고 있는 지식은 잘못된 지식이나 정확하지 않은 지식이 많다고 생각합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 제가 가진 지식을 블로그에 글로 적을만큼 자신감이 없어서 저는 제가 배워가고, 만들어가고있는 것들을 하루하루 적어가려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 제가 적어가는 박종연과 함께 성장하는 블로그를 만들어갈 예정이니 잘 부탁드립니다!&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;한줄요약&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자신감이 생길때까지 TIL만 주구장창 올리겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Github&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;pokoed(박종연) &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/pokoed&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/pokoed&lt;/a&gt;&lt;/p&gt;</description>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/notice/19</guid>
      <pubDate>Mon, 11 Jul 2022 08:59:47 +0900</pubDate>
    </item>
    <item>
      <title>2022.07.10 사이드 프로젝트(ChargerPin) 컨트롤러, 서비스 제작</title>
      <link>https://parkstate.tistory.com/18</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h1&gt;사이드 프로젝트(ChargerPin project)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 진행 중입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;구현할_기능들&quot; data-ke-size=&quot;size23&quot;&gt;구현할 기능들&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;s&gt;회원가입/로그인&lt;/s&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(제작 완료)&lt;/li&gt;
&lt;li&gt;&lt;s&gt;전기차 충전소 검색&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;전기차 충전소 조회&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;전기차 충전소 즐겨찾기(컨트롤러, 서비스 제작)&lt;/li&gt;
&lt;li&gt;즐겨찾기 조회&lt;/li&gt;
&lt;li&gt;즐겨찾기 삭제&lt;/li&gt;
&lt;li&gt;전기차 충전소 상세조회&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오늘 구현한 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;컨트롤러에 url 매핑해주었고, 서비스 부분에 즐겨찾기에 필요한 코드를 추가하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 서비스를 테스트해주었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;테스트 결과&lt;/p&gt;
&lt;pre id=&quot;code_1657464459563&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;code&quot;: 1,
    &quot;msg&quot;: &quot;ok&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 구현할 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 오늘 제작해준 것들을 바탕으로 즐겨찾기 기능을 구현하려고 합니다.&lt;/p&gt;</description>
      <category>Side Project</category>
      <category>api제작</category>
      <category>사이드 프로젝트</category>
      <category>스프링</category>
      <category>스프링 부트</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/18</guid>
      <comments>https://parkstate.tistory.com/18#entry18comment</comments>
      <pubDate>Mon, 11 Jul 2022 00:07:05 +0900</pubDate>
    </item>
    <item>
      <title>2022.07.08 사이드 프로젝트(ChargerPin) 즐겨찾기 기능 구현 준비</title>
      <link>https://parkstate.tistory.com/16</link>
      <description>&lt;h1&gt;사이드 프로젝트(ChargerPin project)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 진행 중입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 id=&quot;구현할_기능들&quot; data-ke-size=&quot;size23&quot;&gt;구현할 기능들&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;s&gt;회원가입/로그인&lt;/s&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(제작 완료)&lt;/li&gt;
&lt;li&gt;&lt;s&gt;전기차 충전소 검색&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;전기차 충전소 조회&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;전기차 충전소 즐겨찾기(준비 중)&lt;/li&gt;
&lt;li&gt;즐겨찾기 조회&lt;/li&gt;
&lt;li&gt;즐겨찾기 삭제&lt;/li&gt;
&lt;li&gt;전기차 충전소 상세조회&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오늘 구현한 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 전기차 충전소 즐겨찾기 기능을 구현하기 위해 BookmarkController, BookmarkService(BookmarkServiceImpl), BookmarkRequest, BookmarkResponse 등의 파일을 제작하고 준비하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 UserBookmarkChargerRepository에 생성될 때 생기는 userId로 조회하는 메서드인 findByUserId를 만들어주고 테스트했습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;테스트 결과&lt;/p&gt;
&lt;pre id=&quot;code_1657210442236&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;=========================================
UserBookmarkChargerEntity(bookmarkId=1, chargerName=bookmark1, userId=TestSample99)
UserBookmarkChargerEntity(bookmarkId=2, chargerName=bookmark2, userId=TestSample99)
UserBookmarkChargerEntity(bookmarkId=3, chargerName=bookmark3, userId=TestSample99)
UserBookmarkChargerEntity(bookmarkId=4, chargerName=bookmark4, userId=TestSample99)
UserBookmarkChargerEntity(bookmarkId=5, chargerName=bookmark5, userId=TestSample99)
UserBookmarkChargerEntity(bookmarkId=6, chargerName=bookmark6, userId=TestSample99)
UserBookmarkChargerEntity(bookmarkId=7, chargerName=bookmark7, userId=TestSample99)
UserBookmarkChargerEntity(bookmarkId=8, chargerName=bookmark8, userId=TestSample99)
UserBookmarkChargerEntity(bookmarkId=9, chargerName=bookmark9, userId=TestSample99)
UserBookmarkChargerEntity(bookmarkId=10, chargerName=bookmark10, userId=TestSample99)
UserBookmarkChargerEntity(bookmarkId=11, chargerName=bookmark11, userId=TestSample99)
// 중략
UserBookmarkChargerEntity(bookmarkId=99, chargerName=bookmark99, userId=TestSample99)&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 구현할 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 오늘 제작해준 것들을 바탕으로 본격적인 즐겨찾기 기능을 구현하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 행복한 금요일이기 때문에 그냥 준비만 하고 끝냈습니다 :)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번 주도 정말 수고 많았던 것 같습니다 ㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;주말 쉬고 또 재충전해서 돌아오겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;감사합니다!&lt;/p&gt;</description>
      <category>Side Project</category>
      <category>API 개발</category>
      <category>사이드 프로젝트</category>
      <category>스프링</category>
      <category>스프링 부트</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/16</guid>
      <comments>https://parkstate.tistory.com/16#entry16comment</comments>
      <pubDate>Fri, 8 Jul 2022 02:18:41 +0900</pubDate>
    </item>
    <item>
      <title>2022.07.07 사이드 프로젝트(ChargerPin) 검색 기능 구현</title>
      <link>https://parkstate.tistory.com/15</link>
      <description>&lt;h1&gt;사이드 프로젝트(ChargerPin project)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 진행 중입니다.&lt;/p&gt;
&lt;h3 id=&quot;구현할_기능들&quot; data-ke-size=&quot;size23&quot;&gt;구현할 기능들&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;s&gt;회원가입/로그인&lt;/s&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(제작 완료)&lt;/li&gt;
&lt;li&gt;&lt;s&gt;전기차 충전소 검색&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;&lt;s&gt;전기차 충전소 조회&lt;/s&gt;&lt;/li&gt;
&lt;li&gt;전기차 충전소 즐겨찾기&lt;/li&gt;
&lt;li&gt;즐겨찾기 조회&lt;/li&gt;
&lt;li&gt;즐겨찾기 삭제&lt;/li&gt;
&lt;li&gt;전기차 충전소 상세조회&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오늘 구현한 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사이드 프로젝트(ChargerPin)에서 중요한 기능이었던 회원가입은 지난번에 제작하였고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;지난번에 전기차 충전소 조회 기능을 구현하였고,&amp;nbsp;오늘은 전기차 충전소 조회를 이제 검색어를 적용하여 검색할 수 있는 기능을 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;api request body&lt;/p&gt;
&lt;pre id=&quot;code_1657205549390&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;keyword&quot;: &quot;용산역 아이파크&quot;
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;api response body&lt;/p&gt;
&lt;pre id=&quot;code_1657205643271&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;response&quot;: {
        &quot;header&quot;: {
            &quot;resultCode&quot;: &quot;00&quot;,
            &quot;resultMsg&quot;: &quot;NORMAL SERVICE.&quot;
        },
        &quot;body&quot;: {
            &quot;items&quot;: {
                &quot;item&quot;: [
                    {
                        &quot;addr&quot;: &quot;서울특별시 용산구 한강로3가 40-999 4.5F층 달주차장&quot;,
                        &quot;chargeTp&quot;: 2,
                        &quot;cpId&quot;: 725,
                        &quot;cpNm&quot;: &quot;급속06&quot;,
                        &quot;cpStat&quot;: 1,
                        &quot;cpTp&quot;: 10,
                        &quot;csId&quot;: 120,
                        &quot;csNm&quot;: &quot;용산역 아이파크몰&quot;,
                        &quot;lat&quot;: 37.530549041044424,
                        &quot;longi&quot;: 126.96516550972167,
                        &quot;statUpdateDatetime&quot;: &quot;2022-07-07 23:50:27&quot;
                    },
                    {
                        &quot;addr&quot;: &quot;서울특별시 용산구 한강로3가 40-999 4.5F층 달주차장&quot;,
                        &quot;chargeTp&quot;: 1,
                        &quot;cpId&quot;: 739,
                        &quot;cpNm&quot;: &quot;완속09&quot;,
                        &quot;cpStat&quot;: 2,
                        &quot;cpTp&quot;: 3,
                        &quot;csId&quot;: 120,
                        &quot;csNm&quot;: &quot;용산역 아이파크몰&quot;,
                        &quot;lat&quot;: 37.530549041044424,
                        &quot;longi&quot;: 126.96516550972167,
                        &quot;statUpdateDatetime&quot;: &quot;2022-07-07 23:50:00&quot;
                    },

				...
                    
                ]
            },
            &quot;numOfRows&quot;: 10,
            &quot;pageNo&quot;: 1,
            &quot;totalCount&quot;: 21
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 구현할 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전기차 충전소 검색한 것 중에 원하는 것을 즐겨찾기에 추가하는 기능을 제작할 예정입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 진짜 진짜 블로그 TIL 포스팅을 까먹고 있다가 23시 50분이 되어서야 글을 적고 포스팅하네요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음에는 미리미리 적고 포스팅해야겠습니다ㅠㅠㅠ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;생각한 것보다 잘 검색 기능을 완성해서 오늘은 만족스러운 하루였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;긴 글 읽어주셔서 감사합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Github&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;pokoed(박종연) &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/pokoed&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/pokoed&lt;/a&gt;&lt;/p&gt;</description>
      <category>Side Project</category>
      <category>API 개발</category>
      <category>사이드 프로젝트</category>
      <category>스프링</category>
      <category>스프링 부트</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/15</guid>
      <comments>https://parkstate.tistory.com/15#entry15comment</comments>
      <pubDate>Thu, 7 Jul 2022 23:58:21 +0900</pubDate>
    </item>
    <item>
      <title>2022.07.06 Spring Mock 사용해보기</title>
      <link>https://parkstate.tistory.com/14</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 Spring Mock을 사용해보았습니다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mock을 사용하면 좋은 점&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Mock을 사용하면 그냥 Controller에 테스트를 하는 것보다 의존성이 단절시킬 수 있어서 쉽게 테스트할 수 있습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(참고: &lt;a href=&quot;https://elevatingcodingclub.tistory.com/61&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://elevatingcodingclub.tistory.com/61&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저도 오늘 처음 Mock을 사용해서 사실 저 좋은 점을 실제로 체감하지는 못했습니다...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;Mock 적용 테스트&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;제가 Spring으로 전에 제작해본 todoList api를 Mock으로 테스트 해보았습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1657111176060&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@WebMvcTest(TodoController.class)
class TodoControllerTest {

    @Autowired
    MockMvc mvc;

    @MockBean
    TodoService service;

    private TodoEntity expected;


    @BeforeEach
    void setUp() {
        this.expected = new TodoEntity();

        this.expected.setId(123L);
        this.expected.setTitle(&quot;TestTitle&quot;);
        this.expected.setOrder(0L);
        this.expected.setCompleted(false);

    }

    @Test
    void create() throws Exception {
        when(this.service.add(any(TodoRequest.class)))
                .then(i -&amp;gt; {
                    TodoRequest request = i.getArgument(0, TodoRequest.class);
                    return new TodoEntity(this.expected.getId(), this.expected.getTitle(), this.expected.getOrder(), this.expected.getCompleted());
                });
        TodoRequest request = new TodoRequest();
        request.setTitle(&quot;ANY TITLE&quot;);

        ObjectMapper mapper = new ObjectMapper();
        String content = mapper.writeValueAsString(request);

        // request를 body에 넣어주어야하지만 직접적으로 넣어줄 수 없어서
        // ObjectMapper를 사용하였음.
        this.mvc.perform(post(&quot;/&quot;).contentType(MediaType.APPLICATION_JSON).content(content))
                .andExpect(status().isOk())
                .andExpect(jsonPath(&quot;$.title&quot;).value(&quot;ANY TITLE&quot;));
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 사이드 프로젝트를 진행하지 못해서 조금 아쉽지만 몸상태가 좋지 않은 관계로 하루 쉬어가겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;: )&lt;/p&gt;</description>
      <category>Java-Kotlin/Spring Boot</category>
      <category>spring boot</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/14</guid>
      <comments>https://parkstate.tistory.com/14#entry14comment</comments>
      <pubDate>Wed, 6 Jul 2022 21:41:39 +0900</pubDate>
    </item>
    <item>
      <title>2022.07.05 사이드 프로젝트에 OpenApi 연결</title>
      <link>https://parkstate.tistory.com/13</link>
      <description>&lt;h1&gt;사이드 프로젝트(ChargerPin project)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 진행 중입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현할 기능들&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;s&gt;회원가입/로그인&lt;/s&gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;(제작 완료)&lt;/li&gt;
&lt;li&gt;&lt;s&gt;전기차 충전소 검색&lt;/s&gt; (충전소 위치로 검색 기능 완료)&lt;/li&gt;
&lt;li&gt;전기차 충전소 조회&lt;/li&gt;
&lt;li&gt;전기차 충전소 즐겨찾기&lt;/li&gt;
&lt;li&gt;즐겨찾기 조회&lt;/li&gt;
&lt;li&gt;즐겨찾기 삭제&lt;/li&gt;
&lt;li&gt;전기차 충전소 상세조회&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오늘 구현한 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 전기차 충전소를 위치로 검색하는 기능을 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Spring으로 다른 Api에 요청해보는 게 처음이어서 여러 자료를 찾고 그 자료에 있는 방식 중에 지금 제게 맞는 방식으로 조금씩 변경하여 구현하였습니다. (RestTemplate 방식)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 방식도 있었지만 RestTemplate가 제일 나아 보였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그리고 OpenApi의 서비스 키에 특수문자가 포함되어있는데 URIComponentsBuilder에서 /(Slash)만 인코딩 안 하는 문제가 있어서&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그냥 기본 String에 파라미터를 모두 붙여서 URI로 만들어주어서 Api에 요청하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1657013806277&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;String url = &quot;http://openapi.kepco.co.kr/service/EvInfoServiceV2/getEvSearchList?ServiceKey=${서비스키}&quot;+&quot;&amp;amp;pageNo=1&amp;amp;numOfRows=10&quot;;
url+=&quot;&amp;amp;addr=서초구 우면동&quot;.replace(&quot; &quot;, &quot;%20&quot;);

try {
	uri = new URI(url);
} catch (URISyntaxException e) {
	e.printStackTrace();
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;결과&lt;/h4&gt;
&lt;pre id=&quot;code_1657013919355&quot; class=&quot;javascript&quot; data-ke-language=&quot;javascript&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;{
    &quot;response&quot;: {
        &quot;header&quot;: {
            &quot;resultCode&quot;: &quot;00&quot;,
            &quot;resultMsg&quot;: &quot;NORMAL SERVICE.&quot;
        },
        &quot;body&quot;: {
            &quot;items&quot;: {
                &quot;item&quot;: {
                    &quot;addr&quot;: &quot;서울특별시 서초구 우면동 763 4층 주차장&quot;,
                    &quot;chargeTp&quot;: 2,
                    &quot;cpId&quot;: 5894,
                    &quot;cpNm&quot;: &quot;급속01&quot;,
                    &quot;cpStat&quot;: 2,
                    &quot;cpTp&quot;: 10,
                    &quot;csId&quot;: 2052,
                    &quot;csNm&quot;: &quot;남서울농협 하나로마트 우면점&quot;,
                    &quot;lat&quot;: 37.45685749231517,
                    &quot;longi&quot;: 127.01585735496195,
                    &quot;statUpdateDatetime&quot;: &quot;2022-07-05 18:35:00&quot;
                }
            },
            &quot;numOfRows&quot;: 10,
            &quot;pageNo&quot;: 1,
            &quot;totalCount&quot;: 1
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 구현할 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 나머지 전기차 충전소 검색 기능을 구현해야 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 남은 검색 기능은 이렇습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;충전소의 이름으로 검색&lt;/li&gt;
&lt;li&gt;충전소 고유 ID로 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 URI 인코딩, 디코딩 문제로 너무 시간도 많이 잡아먹어서 힘들었고, 다음에는 인코딩, 디코딩 문제가 다신 안 일어났으면 좋겠다는 생각을 하게 만든 하루였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래도 계속 사이드 프로젝트가 진행이 된다는 게 되게 기분이 좋네요  &lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 아직도 부족하니까 앞으로도 열심히 하겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Github&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;pokoed(박종연) &lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://github.com/pokoed&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://github.com/pokoed&lt;/a&gt;&lt;/p&gt;</description>
      <category>Side Project</category>
      <category>API 개발</category>
      <category>사이드 프로젝트</category>
      <category>스프링</category>
      <category>스프링 부트</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/13</guid>
      <comments>https://parkstate.tistory.com/13#entry13comment</comments>
      <pubDate>Tue, 5 Jul 2022 18:51:23 +0900</pubDate>
    </item>
    <item>
      <title>2022.06.29 사이드 프로젝트 제작</title>
      <link>https://parkstate.tistory.com/11</link>
      <description>&lt;h1&gt;사이드 프로젝트(ChargerPin project)&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 진행 중입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;구현할 기능들&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;s&gt;회원가입/로그인&lt;/s&gt; (제작 완료)&lt;/li&gt;
&lt;li&gt;전기차 충전소 검색&lt;/li&gt;
&lt;li&gt;전기차 충전소 조회&lt;/li&gt;
&lt;li&gt;전기차 충전소 즐겨찾기&lt;/li&gt;
&lt;li&gt;즐겨찾기 조회&lt;/li&gt;
&lt;li&gt;즐겨찾기 삭제&lt;/li&gt;
&lt;li&gt;전기차 충전소 상세조회&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;오늘 구현한 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 회원가입/로그인을 구현하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;회원가입은 지난번에 프로젝트를 진행하면서 구현하고 테스트까지 해놓았지만, 로그인은 구현하지 않아서 오늘 로그인 기능을 구현하였습니다. 회원가입은 DB에&amp;nbsp; 연동하고 DB에 테스트할게 여러 가지 있었지만, 로그인은 유저 DB에 있는 데이터와 요청하며 넘어온 데이터를 비교만 하면 돼서 생각보다 쉽게 구현하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;258&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cwNATK/btrF5PDsZnW/uY5ZUFvlEVaCZ44qcgYuD1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cwNATK/btrF5PDsZnW/uY5ZUFvlEVaCZ44qcgYuD1/img.png&quot; data-alt=&quot;Post 요청 Body&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cwNATK/btrF5PDsZnW/uY5ZUFvlEVaCZ44qcgYuD1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcwNATK%2FbtrF5PDsZnW%2FuY5ZUFvlEVaCZ44qcgYuD1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;258&quot; height=&quot;77&quot; data-origin-width=&quot;258&quot; data-origin-height=&quot;77&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Post 요청 Body&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;147&quot; data-origin-height=&quot;77&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/XYdf9/btrF3ZAG86J/8hRaKnC0gT998iPvNkjJ6k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/XYdf9/btrF3ZAG86J/8hRaKnC0gT998iPvNkjJ6k/img.png&quot; data-alt=&quot;응답 Body&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/XYdf9/btrF3ZAG86J/8hRaKnC0gT998iPvNkjJ6k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FXYdf9%2FbtrF3ZAG86J%2F8hRaKnC0gT998iPvNkjJ6k%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;147&quot; height=&quot;77&quot; data-origin-width=&quot;147&quot; data-origin-height=&quot;77&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;응답 Body&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;다음 구현할 기능&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다음으로는 전기차 충전소 검색 기능을 구현하려고 합니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;전기차 충전소를 검색하는 방법은 이렇습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;충전소의 위치로 검색&lt;/li&gt;
&lt;li&gt;충전소의 이름으로 검색&lt;/li&gt;
&lt;li&gt;충전소 고유 ID로 검색&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이런 검색을 다 가능하게 하기 위해서 위치와 이름 ID 모두 검색해보는 방법이 있고, 셋의 특징별로 나누어서 필요한 것만 검색해보는 방법이 있을 것 같습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;마치며&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체적인 구현 방식은 프로젝트를 진행하면서 바뀐다는 것을 또다시 느끼는 하루였습니다 ㅋㅋㅋㅋ&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원래 로그인도 이렇게 구현하려 했던 게 아닌데 결과적으로는 이렇게 간단하게 구현했네요...&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;맘에 안 드는 부분은 나중에 리팩터링을 한번 진행하는 걸로 하겠습니다.&lt;/p&gt;</description>
      <category>Side Project</category>
      <category>API 개발</category>
      <category>사이드 프로젝트</category>
      <category>스프링</category>
      <category>스프링 부트</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/11</guid>
      <comments>https://parkstate.tistory.com/11#entry11comment</comments>
      <pubDate>Wed, 29 Jun 2022 21:00:58 +0900</pubDate>
    </item>
    <item>
      <title>django프로젝트 생성 및 세팅하기</title>
      <link>https://parkstate.tistory.com/10</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 django로 API를 제작해보았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 제가 공부한 내용을 글로 남기겠습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;파이참으로 django 프로젝트 생성하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;파일에서 새 프로젝트를 누르고&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;246&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dYnCW1/btryQCLLEPN/IVM2LuU9xu4VMtapACQJp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dYnCW1/btryQCLLEPN/IVM2LuU9xu4VMtapACQJp0/img.png&quot; data-alt=&quot;File &amp;amp;gt; New Project...&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dYnCW1/btryQCLLEPN/IVM2LuU9xu4VMtapACQJp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdYnCW1%2FbtryQCLLEPN%2FIVM2LuU9xu4VMtapACQJp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;628&quot; height=&quot;246&quot; data-origin-width=&quot;628&quot; data-origin-height=&quot;246&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;File &amp;gt; New Project...&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;프로젝트 위치를 정하고, 가상 환경을 설정하였습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1736&quot; data-origin-height=&quot;1336&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cqrRfC/btryQR2ZlZw/hF0kiHKvu5ucxr4FiLEKF0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cqrRfC/btryQR2ZlZw/hF0kiHKvu5ucxr4FiLEKF0/img.png&quot; data-alt=&quot;Location, 파이썬 환경 설정 가능&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cqrRfC/btryQR2ZlZw/hF0kiHKvu5ucxr4FiLEKF0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcqrRfC%2FbtryQR2ZlZw%2FhF0kiHKvu5ucxr4FiLEKF0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1736&quot; height=&quot;1336&quot; data-origin-width=&quot;1736&quot; data-origin-height=&quot;1336&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Location, 파이썬 환경 설정 가능&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;djangorestframework 설치&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;pip 명령어를 통해 djangorestframework를 설치하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649418386987&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;pip install djangorestframework&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;설치가 되면 프로젝트의 settings.py 파일에 추가해줘야 합니다.&lt;/p&gt;
&lt;pre id=&quot;code_1649418664483&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# settings.py

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',
    'rest_framework', # 이 줄 추가
]&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;398&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/Wr9hT/btryPqrMzHN/WttUvtC6ab7VHUac7TG950/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/Wr9hT/btryPqrMzHN/WttUvtC6ab7VHUac7TG950/img.png&quot; data-alt=&quot;settings.py&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Wr9hT/btryPqrMzHN/WttUvtC6ab7VHUac7TG950/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FWr9hT%2FbtryPqrMzHN%2FWttUvtC6ab7VHUac7TG950%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;538&quot; height=&quot;398&quot; data-origin-width=&quot;538&quot; data-origin-height=&quot;398&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;settings.py&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;django 서버 실행&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;control + r 을 눌러 django 서버를 실행하고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;127.0.0.1:8000 에 접속하여 사이트가 작동되나 확인했습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;1278&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/d1Phyi/btryPpNemPI/qoIlCjOiWFh2kOf4zku8D1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/d1Phyi/btryPpNemPI/qoIlCjOiWFh2kOf4zku8D1/img.png&quot; data-alt=&quot;127.0.0.1:8000 화면&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/d1Phyi/btryPpNemPI/qoIlCjOiWFh2kOf4zku8D1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fd1Phyi%2FbtryPpNemPI%2FqoIlCjOiWFh2kOf4zku8D1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;640&quot; height=&quot;502&quot; data-origin-width=&quot;1628&quot; data-origin-height=&quot;1278&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;127.0.0.1:8000 화면&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;마치며&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;오늘은 이렇게 django 프로젝트를 생성하고 세팅해보았습니다.&amp;nbsp;&lt;/p&gt;</description>
      <category>Python</category>
      <category>django</category>
      <category>Python</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/10</guid>
      <comments>https://parkstate.tistory.com/10#entry10comment</comments>
      <pubDate>Fri, 8 Apr 2022 20:57:09 +0900</pubDate>
    </item>
    <item>
      <title>Git branch 나누고 합치기</title>
      <link>https://parkstate.tistory.com/9</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;오늘은 git branch를 나누고 합쳐보겠습니다.&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;branch 란?&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여러 개발자들이 함께 협업을 하게 되면 같은 소스코드 위에서 한 사람은 새로운 기능을 개발하기도 하고, 한 사람은 버그 수정을 하기도 한다. 이런 상황에서 여러 개발자들이 동시에 한 소스코드나 레포지토리에서 여러 작업을 할 수 있게 하는 기능이 Git의 branch입니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;branch 덕분에 하나의 프로젝트를 여러 갈래로 나누어서 개발, 수정 등의 관리를 할 수 있습니다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;branch 나누기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. 나의 현재 branch 확인하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;나의 프로젝트가 있는 경로에서 터미널에&lt;/p&gt;
&lt;pre id=&quot;code_1649158726829&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git branch&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 입력하면 브랜치들이 나옵니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;156&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wDFF5/btryyF9Mwqt/wwt3fVNzbwsMbGExMOsvZk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wDFF5/btryyF9Mwqt/wwt3fVNzbwsMbGExMOsvZk/img.png&quot; data-alt=&quot;* 이 붙은 브랜치는 현재 브랜치&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wDFF5/btryyF9Mwqt/wwt3fVNzbwsMbGExMOsvZk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FwDFF5%2FbtryyF9Mwqt%2Fwwt3fVNzbwsMbGExMOsvZk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;156&quot; height=&quot;56&quot; data-origin-width=&quot;156&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;* 이 붙은 브랜치는 현재 브랜치&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;2. branch 생성하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에&lt;/p&gt;
&lt;pre id=&quot;code_1649159048404&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git branch 브랜치이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 입력하면 입력한 브랜치 이름을 가진 브랜치가 생성됩니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;3. branch 이동하기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;터미널에&lt;/p&gt;
&lt;pre id=&quot;code_1649159186093&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git checkout 브랜치이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 입력하면 입력한 브랜치이름을 가진 브랜치로 이동됩니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;56&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ckKn6Q/btryxjzwUAj/4oCl6YzDEthfBabYaCKe81/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ckKn6Q/btryxjzwUAj/4oCl6YzDEthfBabYaCKe81/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ckKn6Q/btryxjzwUAj/4oCl6YzDEthfBabYaCKe81/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FckKn6Q%2FbtryxjzwUAj%2F4oCl6YzDEthfBabYaCKe81%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;744&quot; height=&quot;56&quot; data-origin-width=&quot;744&quot; data-origin-height=&quot;56&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 브랜치에 처음 이동하게 되면 push가 안됩니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그럴 때는&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1649159390661&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git push -u origin 브랜치이름&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 입력해줍니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;4. branch 합치기&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;새로운 브랜치에서 변경사항을 커밋하고 푸시 한 이후에 그 변경사항을 다른 브랜치에 합치려면&lt;/p&gt;
&lt;pre id=&quot;code_1649159500128&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git checkout 합치려는브랜치&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 입력한 뒤&lt;/p&gt;
&lt;pre id=&quot;code_1649159528085&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;git merge 변경사항이있는브랜치&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;를 입력해주면&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;합치려는 브랜치에 합친 브랜치의 변경사항이 합쳐집니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style6&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다른 친구들과 같이 협업하여 개발하려 하니 git branch를 다시 새기려고 복습하였습니다.&lt;/p&gt;</description>
      <category>Git</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/9</guid>
      <comments>https://parkstate.tistory.com/9#entry9comment</comments>
      <pubDate>Wed, 6 Apr 2022 10:35:37 +0900</pubDate>
    </item>
    <item>
      <title>개발 블로그 개설기</title>
      <link>https://parkstate.tistory.com/3</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;언젠가 개발 블로그를 개설하여 다른 개발하시는 분들에게 도움이 되고 싶다는 생각을 하곤 했었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;드디어 개발 블로그를 개설하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 이번 글은 제가 개발 블로그를 개설하기까지의 고민 과정입니다!&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-client=&quot;ca-pub-3824154259221679&quot; data-ad-slot=&quot;1805119225&quot; data-ad-format=&quot;auto&quot; data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []). push({});
&lt;/script&gt;
&lt;/div&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;개발 블로그를 어디에 개설할지...&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일단 블로그를 개설한다는 생각을 하고, 어디에 개설해야 할지 고민하였습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;후보로는 Velog, Tistory, 네이버 블로그, github.io 가 있었습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 딱 한 번에 고르기 어려워서 이 플랫폼들의 장단점을 생각해보았습니다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Velog&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점: 개설이 쉽고 간단함, 깔끔함, 마크다운으로 글 작성 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점: 개인적으로 커스텀할 수 없음, 통계 보기가 어려움&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Tistory&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점: 개인적인 커스텀이 쉬움, 자료가 많음, 여러 플러그인이 많음, 통계보기가 쉬움, 마크다운으로 글 작성 가능&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점: 커스텀할게 많아도 너무 많음, 개설을 하고서 세팅하기까지의 과정이 생각보다 어려움&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;네이버 블로그&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점: 개설이 쉽고 간단함, 네이버에 노출이 잘 됨&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점: 구글에 노출이 잘 안 됨, 커스텀할 게 없음, 깔끔하지 않음, 코드 블록 기능이 빈약함&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-client=&quot;ca-pub-3824154259221679&quot; data-ad-slot=&quot;8451801512&quot; data-ad-format=&quot;auto&quot; data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []). push({});
&lt;/script&gt;
&lt;/div&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;github.io&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;장점: 모든게 커스텀임, 내가 원하는 대로 다 바꾸고 꾸밀 수 있음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점: 글이 많아지면 글 관리가 어려움, 다른 블로그 사이트에 있는 기본적인 통계 기능이 없음, 모든 게 커스텀임, 내가 모든 걸 관리해야 함&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;최종 결정&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;저는 결국 Tistory로 블로그를 개설하기로 마음을 먹고 블로그 세팅을 하고 이 글을 적습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;결정 이유&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Velog는 통계를 보기 어렵다는 점이 저에게는 큰 단점으로 다가왔고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;네이버 블로그는 깔끔하지 않아서 마음에 들지 않았습니다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그래서 최종 후보로 Tistory와 github.io로 고민하고 있었는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;github.io는 제가 모든 걸 관리해야한다는 단점때문에 모든걸 관리하는 데에 제 시간을 너무 많이 뺏길까 봐 Tistory로 결정하였습니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;div align=&quot;center&quot;&gt;&lt;ins class=&quot;adsbygoogle&quot; style=&quot;display: block;&quot; data-ad-client=&quot;ca-pub-3824154259221679&quot; data-ad-slot=&quot;7731947914&quot; data-ad-format=&quot;auto&quot; data-full-width-responsive=&quot;true&quot;&gt;&lt;/ins&gt;
&lt;script&gt;
     (adsbygoogle = window.adsbygoogle || []). push({});
&lt;/script&gt;
&lt;/div&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;앞으로 제가 배운 것과 알게 된 것들을 이 블로그에 올릴 예정입니다!&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;같이 성장해나가는 기록을 남기겠습니다!&lt;/p&gt;</description>
      <category>일상</category>
      <category>Til</category>
      <category>블로그</category>
      <author>박종연</author>
      <guid isPermaLink="true">https://parkstate.tistory.com/3</guid>
      <comments>https://parkstate.tistory.com/3#entry3comment</comments>
      <pubDate>Tue, 5 Apr 2022 18:32:56 +0900</pubDate>
    </item>
  </channel>
</rss>