<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>개발자국</title>
    <link>https://cajava.tistory.com/</link>
    <description></description>
    <language>ko</language>
    <pubDate>Tue, 4 Aug 2026 16:17:25 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Dogfoot_JW</managingEditor>
    <image>
      <title>개발자국</title>
      <url>https://tistory1.daumcdn.net/tistory/3800476/attach/638813e52ec64c9cb251931f98e353c1</url>
      <link>https://cajava.tistory.com</link>
    </image>
    <item>
      <title>[Flutter] Java-Gradle간 버전 충돌 문제 해결 (Flutter Java 버전 지정하기)</title>
      <link>https://cajava.tistory.com/93</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;Gradle 버전 충돌 발생!!&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구버전의 플러터 소스코드를 빌드하다 보면, Gradle과 Java간 버전 충돌 문제가 발생하기도 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Flutter 버전은 3.22.3을 사용하고, Java는 21버전을 사용하고 있었는데, 프로젝트의 Gradle버전이 7.5.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;그래서 Java 버전을 17로 설치한 후 JAVA_HOME 경로까지 수정하였으나, Gradle 오류는 계속해서 발생하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;원인을 찾기 위해 &lt;code&gt;flutter doctor -v&lt;/code&gt; 를 통해 자세한 개발환경의 버전들을 체크하였지만, 내가 Java 17을 다시 설치하고 환경변수까지 변경했음에도, 로그에 나오는 버전은 21로 되어 있었다.&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의 JDK는 Android Studio가 설치한 JDK에 의해 자동으로 인식이 되기 때문에 만약 Android Studio를 최신버전으로 사용한다면 별도로 Flutter의 JDK경로를 지정해줄 필요가 있다. (이는 Android Studio가 설치되어 있지 않은 환경일 경우에도 별도로 JDK 경로 지정이 필요함)&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;h2 data-ke-size=&quot;size26&quot;&gt;Flutter의 Java버전 지정&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;Windows:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763647909402&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flutter config --jdk-dir=&quot;C:\Program Files\Java\jdk-&amp;lt;version&amp;gt;&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;MacOS, Linux:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763647942855&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;flutter config --jdk-dir=&quot;/Library/Java/JavaVirtualMachines/jdk-&amp;lt;version&amp;gt;.jdk/Contents/Home&quot;&lt;/code&gt;&lt;/pre&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;h2 data-ke-size=&quot;size26&quot;&gt;Flutter의 Java 버전 확인&lt;/h2&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에서 설정된 자바 버전을 확인해보자&lt;/p&gt;
&lt;pre class=&quot;ada&quot;&gt;&lt;code&gt;flutter doctor --verbose&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;code&gt;Java version Java(TM) SE Runtime Environment (Build 17.xx)&lt;/code&gt; 처럼 원하는 버전이 정상적으로 표시되면 제대로 적용이 된 것이다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;416&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cr5QOM/dJMcadAmJPZ/4qmyH1KgWkJzELEQaHIXB1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cr5QOM/dJMcadAmJPZ/4qmyH1KgWkJzELEQaHIXB1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cr5QOM/dJMcadAmJPZ/4qmyH1KgWkJzELEQaHIXB1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcr5QOM%2FdJMcadAmJPZ%2F4qmyH1KgWkJzELEQaHIXB1%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;1294&quot; height=&quot;416&quot; data-origin-width=&quot;1294&quot; data-origin-height=&quot;416&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;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;이후 .gradle 내에 있는 캐시를 초기화 해줄 필요가 있다. (이전 JDK버전이 작업한 흔적 지우기)&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;Windows:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763648048027&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rd /s /q [사용자프로필]\.gradle\caches flutter clean&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;MacOS, Linux:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763648080433&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;rm -rf ~/.gradle/caches flutter clean&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;모든 CMD창과 IDE를 닫고 다시 켠 다음 빌드를 해보면 Gradle-Java간 버전 충돌 이슈를 해결할 수 있다.&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>  Flutter</category>
      <category>Crash</category>
      <category>error</category>
      <category>Flutter</category>
      <category>fluttre</category>
      <category>Gradle</category>
      <category>java</category>
      <category>Version</category>
      <category>버전 충돌</category>
      <category>플러터</category>
      <category>플러터 오류</category>
      <author>Dogfoot_JW</author>
      <guid isPermaLink="true">https://cajava.tistory.com/93</guid>
      <comments>https://cajava.tistory.com/93#entry93comment</comments>
      <pubDate>Wed, 3 Dec 2025 17:53:48 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] TargetSDK, Gradle 버전 쉽게 올리는 노하우</title>
      <link>https://cajava.tistory.com/94</link>
      <description>&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;Android의 TargetSDK는 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;수년간 개발자들을 괴롭혀온 Gradle 버전 수정 작업이기 때문에 내 나름대로 쉽게 해결할 수 있는 노하우를 정리하여 문서화를 했다.&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;2025.07월에 작성한 글이라 최신 버전에서는 통하지 않을 수 있음.&lt;/b&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;b&gt;구성&lt;/b&gt;&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AndroidStudio v2025.1.1&lt;/li&gt;
&lt;li&gt;Flutter v3.22.3&lt;/li&gt;
&lt;li&gt;Gradle v7.4&lt;/li&gt;
&lt;li&gt;AGP(Android Gradle Plugin) v7.3.1&lt;/li&gt;
&lt;li&gt;Java v17&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;진행&lt;/b&gt;&lt;/h2&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;1. 프로젝트의 SDK 버전 먼저 올려보기&lt;/b&gt;&lt;/h4&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의 app/build.gradle 의 &lt;b&gt;targetSdkVersion, compileSdk&lt;/b&gt;를 먼저 올려보기.&lt;br /&gt;나는 34 버전에서 35로 변경을 해봤다.&lt;br /&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;2-A. 빌드가 잘 되면 이상 없음.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;2. 빌드오류의 경우 AGP 버전 호환성 체크 (이런게 있으니 훑어보기)&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;대부분 &lt;b&gt;CompileSDK&lt;/b&gt; 버전을 올리면 아주 높은 확률로 오류가 발생함.&lt;br /&gt;이럴때는 보통 Gradle 또는 AGP(Android Gradle Plugin)의 버전 이슈일 경우가 대부분.&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;따라서 AGP(Android Gradle Plugin)을 기준으로 Gradle의 적정 버전을 찾고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle을 기준으로 Java의 적정 버전을 찾아야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;(SDK 버전 &amp;gt; AGP 버전 &amp;gt; Gradle 버전 &amp;gt; Java 버전 순)&lt;/b&gt;&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&gt;프로젝트 내의 버전 위치&lt;/b&gt;&lt;br /&gt;&lt;b&gt;SDK:&lt;/b&gt; prj/android/app/build.gradle &amp;gt; targetSdkVersion &amp;amp; compileSdk&lt;br /&gt;&lt;b&gt;AGP:&lt;/b&gt; prj/android/settings.gradle &amp;gt; id &quot;com.android.application&quot; version &quot;[AGP버전]&quot; apply false&lt;br /&gt;&lt;b&gt;Gradle:&lt;/b&gt; prj/android/gradle/gradle-wrapper.properties &amp;gt; distributionUrl=https://services.gradle.org/distributions/gradle-[Gradle버전]-all.zip&lt;br /&gt;&lt;b&gt;Java:&lt;/b&gt; 플러터가 바라보는 버전에 따라 다름 (터미널에 &lt;code&gt;flutter doctor -v&lt;/code&gt; 로 &lt;b&gt;Android Toolchain&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;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;(추가) 버전 호환성에 대한 참고 문서는 여기서 확인할 수 있음.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AGP에 맞는 Gradle 버전 체크: (&lt;a href=&quot;https://developer.android.com/build/releases/gradle-plugin?hl=ko#updating-gradle&quot;&gt;https://developer.android.com/build/releases/gradle-plugin?hl=ko#updating-gradle&lt;/a&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Gradle에 맞는 Java버전 체크: (&lt;a href=&quot;https://docs.gradle.org/current/userguide/compatibility.html&quot;&gt;https://docs.gradle.org/current/userguide/compatibility.html&lt;/a&gt;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;3. 본격적으로 Android Studio의 자체 기능으로 AGP 버전 올리기&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;AndroidStudio로 프로젝트의 &lt;b&gt;android&lt;/b&gt; 네이티브 플랫폼 폴더를 직접 열어보기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;왼쪽 하단에 Build메뉴에서 Gradle Sync가 정상적으로 완료되어야 한다. (오래걸릴 수 있음)&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;648&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ozjbA/dJMcabCBhm8/Uxz1oYpFsL90s8aYuOZc7k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ozjbA/dJMcabCBhm8/Uxz1oYpFsL90s8aYuOZc7k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ozjbA/dJMcabCBhm8/Uxz1oYpFsL90s8aYuOZc7k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FozjbA%2FdJMcabCBhm8%2FUxz1oYpFsL90s8aYuOZc7k%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;1178&quot; height=&quot;648&quot; data-origin-width=&quot;1178&quot; data-origin-height=&quot;648&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;p data-ke-size=&quot;size16&quot;&gt;그 다음&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;상단의 &lt;b&gt;Tools &amp;gt; AGP Upgrade Assistant&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;나의 경우, SDK35 버전이 Gradle 8.0(현재는 7.4) 이상부터 호환이 되기 때문에 AGP 버전을 8.1.4로 잡아봤다.&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;(갑자기 AGP버전을 크게 올리면 한번에 맞이하게 될 빌드오류 좀비들이 몰려온다.)&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;br /&gt;AGP 버전이 올라가면서 Gradle 버전도 자동으로 올라가는데,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;그로 인한 Native 코드들도 Android Studio가 알아서 변경해주기 때문이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;아래 사진에 빨간 박스는 업그레이드를 할 경우 수정하게 될 사항들임.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock widthContent&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;496&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/mmlqK/dJMcahv3bQb/gkvM4lsjgnBg6FkHkAka7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/mmlqK/dJMcahv3bQb/gkvM4lsjgnBg6FkHkAka7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/mmlqK/dJMcahv3bQb/gkvM4lsjgnBg6FkHkAka7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FmmlqK%2FdJMcahv3bQb%2FgkvM4lsjgnBg6FkHkAka7K%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;818&quot; height=&quot;496&quot; data-origin-width=&quot;818&quot; data-origin-height=&quot;496&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;p data-ke-size=&quot;size16&quot;&gt;덕분에 개발자는 &lt;b&gt;Assistant&lt;/b&gt; 기능만을 활용해 Gradle 버전을 올릴때,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Native 코드들의 수정 작업에 할당하는 에너지를 줄일 수 있다.&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;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;4. 빌드 테스트&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;버전 수정이 완료되었다면, &lt;b&gt;--debug&lt;/b&gt; 와 &lt;b&gt;--release&lt;/b&gt; 모두 빌드해보기.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;--release&lt;/b&gt;의 경우 &lt;b&gt;proguard&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;빌드 오류가 발생한다면 표시되는 오류 메시지를 잘 보고 GPT를 통해 잘 대응해보기.&lt;br /&gt;보통 외부 패키지에 의한 오류일 확률이 높긴하다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;내가 작업한 현 시점에서는 inappwebview(앱 내에서 웹표시를 하는 패키지)의 네이티브 코드에서 오류가 발생했다. 최신 버전으로 올리면 해결 가능하나 Flutter 버전도 최신이어야 가능하기 때문에 일단은 해당패키지의 Android 코드를 직접 수정해서 해결을 했다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;5. 커밋 후 다른 PC에서 Pull을 한 다음 빌드 테스트 해보기&lt;/b&gt;&lt;/h4&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;반드시 다른 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;이상.&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;br /&gt;&lt;a href=&quot;https://docs.flutter.dev/release/breaking-changes/android-java-gradle-migration-guide&quot;&gt;https://docs.flutter.dev/release/breaking-changes/android-java-gradle-migration-guide&lt;/a&gt;&lt;/p&gt;</description>
      <category>  Flutter</category>
      <category>AGP</category>
      <category>Android</category>
      <category>build</category>
      <category>Flutter</category>
      <category>Gradle</category>
      <category>SDK</category>
      <category>Version</category>
      <category>그레이들</category>
      <category>마이그레이션</category>
      <category>플러터</category>
      <author>Dogfoot_JW</author>
      <guid isPermaLink="true">https://cajava.tistory.com/94</guid>
      <comments>https://cajava.tistory.com/94#entry94comment</comments>
      <pubDate>Wed, 3 Dec 2025 17:53:34 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] 손쉽게 넥스트클라우드 개인나스 구성</title>
      <link>https://cajava.tistory.com/92</link>
      <description>&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;서버는 이미 미니PC를 통해 사용하고 있고, 문제는 OS였는데, 시놀로지 OS를 사용하기에는 너무 무겁고 엄청난 고난을 겪을 것 같았다. 그래서 이전에 자주 들어본 Nextcloud를 사용하게 되었는데, 내 서버 OS에 다이렉트로 설치했다간, 언제 갑자기 개발환경들과 대환장 콜라보를 할지 몰라 컨테이너화를 하게 되었다.&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;pre class=&quot;ada&quot;&gt;&lt;code&gt;Docker Compose
├── db      : MariaDB 컨테이너 (데이터베이스)
└── app     : Nextcloud 컨테이너 (웹 애플리케이션)&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;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;docker-compose.yml&lt;/h2&gt;
&lt;pre class=&quot;bash&quot; data-ke-language=&quot;bash&quot;&gt;&lt;code&gt;version: &quot;3.9&quot;

services:
  # MariaDB 데이터베이스
  db:
    image: mariadb:10.6
    restart: always
    command: --transaction-isolation=READ-COMMITTED
    volumes:
      - ./db:/var/lib/mysql
    environment:
      - MYSQL_ROOT_PASSWORD=myrootpass1234
      - MYSQL_PASSWORD=mypass1234
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud

  # Nextcloud 앱
  app:
    image: nextcloud
    restart: always
    ports:
      - 5000:80
    depends_on:
      - db
    volumes:
      - ./dir:/var/www/html
    environment:
      - MYSQL_PASSWORD=mypass1234
      - MYSQL_DATABASE=nextcloud
      - MYSQL_USER=nextcloud
      - MYSQL_HOST=db

volumes:
  db:
  dir:&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;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. MariaDB 컨테이너 (&lt;code&gt;db&lt;/code&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;b&gt;image&lt;/b&gt;: MariaDB 10.6 버전 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;restart: always&lt;/b&gt;: 컨테이너 자동 재시작&lt;/li&gt;
&lt;li&gt;&lt;b&gt;command&lt;/b&gt;: 트랜잭션 격리 수준 &lt;code&gt;READ-COMMITTED&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;volumes&lt;/b&gt;: &lt;code&gt;./db&lt;/code&gt; 폴더를 컨테이너의 &lt;code&gt;/var/lib/mysql&lt;/code&gt;에 마운트 &amp;rarr; 데이터 영구 저장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;environment&lt;/b&gt;: DB 계정 및 비밀번호 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;MYSQL_ROOT_PASSWORD&lt;/code&gt; : 루트 계정&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MYSQL_DATABASE&lt;/code&gt; : Nextcloud 데이터베이스&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MYSQL_USER&lt;/code&gt; / &lt;code&gt;MYSQL_PASSWORD&lt;/code&gt; : Nextcloud 접속용 계정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Nextcloud 컨테이너 (&lt;code&gt;app&lt;/code&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;b&gt;image&lt;/b&gt;: 공식 Nextcloud 이미지 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;restart: always&lt;/b&gt;: 앱 자동 재시작&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ports&lt;/b&gt;: 호스트 5000번 &amp;rarr; 컨테이너 80번 매핑&lt;/li&gt;
&lt;li&gt;&lt;b&gt;depends_on&lt;/b&gt;: &lt;code&gt;db&lt;/code&gt; 컨테이너 먼저 시작&lt;/li&gt;
&lt;li&gt;&lt;b&gt;volumes&lt;/b&gt;: &lt;code&gt;./dir&lt;/code&gt; &amp;rarr; &lt;code&gt;/var/www/html&lt;/code&gt; 마운트 &amp;rarr; 파일과 설정 영구 저장&lt;/li&gt;
&lt;li&gt;&lt;b&gt;environment&lt;/b&gt;: DB 연결 정보 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;MYSQL_HOST=db&lt;/code&gt; : 연결할 DB 호스트&lt;/li&gt;
&lt;li&gt;&lt;code&gt;MYSQL_DATABASE&lt;/code&gt;, &lt;code&gt;MYSQL_USER&lt;/code&gt;, &lt;code&gt;MYSQL_PASSWORD&lt;/code&gt; : DB 계정 정보&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 볼륨 정의&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;db&lt;/code&gt; : MariaDB 데이터 저장용&lt;/li&gt;
&lt;li&gt;&lt;code&gt;dir&lt;/code&gt; : Nextcloud 파일 및 설정 저장용&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;h2 data-ke-size=&quot;size26&quot;&gt;사용 방법&lt;/h2&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;code&gt;docker-compose.yml&lt;/code&gt; 파일 생성&lt;/li&gt;
&lt;li&gt;같은 디렉터리에 &lt;code&gt;db&lt;/code&gt;와 &lt;code&gt;dir&lt;/code&gt; 폴더 생성 (없으면 자동 생성 가능)&lt;/li&gt;
&lt;li&gt;Docker Compose 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;docker-compose up -d&lt;/code&gt;&lt;/p&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; start=&quot;4&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;브라우저에서 &lt;code&gt;http://localhost:5000&lt;/code&gt; 접속 후 초기 설정 진행&lt;/li&gt;
&lt;/ol&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;Nextcloud iOS/Android 앱에서 로그인 시 &amp;ldquo;Current user is not logged in&amp;rdquo; 오류가 발생하는 경우가 있다.&lt;br /&gt;이 문제는 HTTPS 설정과 관련이 깊다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;Nextcloud 컨테이너 내부 &lt;code&gt;config/config.php&lt;/code&gt;&lt;/b&gt; 파일에 다음 옵션을 추가해야 한다&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763647267159&quot; class=&quot;php&quot; data-ke-language=&quot;php&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;'overwriteprotocol' =&amp;gt; 'https',&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;span style=&quot;letter-spacing: 0px;&quot;&gt;이 옵션은 Nextcloud가 외부에서 접속할 때 HTTPS 프로토콜을 사용하도록 강제한다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;특히 &lt;/span&gt;Docker + Nginx 환경에서 HTTPS 리버스 프록시를 사용하는 경우 필수 설정임.&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;Nginx 업로드 용량 제한 수정&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본 Nginx 설정에서는 업로드 파일 크기에 제한이 있다.&lt;br /&gt;Nextcloud에서 대용량 파일 업로드가 필요하면 Nginx 설정을 수정해야 한다.&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;code&gt;nginx.conf&lt;/code&gt; 또는 사이트 설정):&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;server {
    listen 443 ssl;
    server_name example.com;

    location / {
        proxy_pass http://172.0.0.1:5000;
        proxy_set_header Host example.com;
        proxy_set_header Connection upgrade;

        # Nextcloud 전용 업로드 제한 해제
        client_max_body_size 50G;
        client_body_timeout 36000s;
        proxy_read_timeout 36000s;
        proxy_send_timeout 36000s;
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;client_max_body_size&lt;/code&gt; : 업로드 최대 용량 설정&lt;/li&gt;
&lt;li&gt;Nextcloud PHP 설정(&lt;code&gt;php.ini&lt;/code&gt;)에서도 &lt;code&gt;upload_max_filesize&lt;/code&gt;와 &lt;code&gt;post_max_size&lt;/code&gt;를 동일하게 늘려야 대용량 업로드가 정상 동작한다.&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Linux</category>
      <category>Docker</category>
      <category>docker nas</category>
      <category>docker-compose</category>
      <category>NextCloud</category>
      <category>Synology</category>
      <category>개인 나스</category>
      <category>나스</category>
      <category>넥스트클라우드</category>
      <category>시놀로지 도커</category>
      <category>홈 나스</category>
      <author>Dogfoot_JW</author>
      <guid isPermaLink="true">https://cajava.tistory.com/92</guid>
      <comments>https://cajava.tistory.com/92#entry92comment</comments>
      <pubDate>Thu, 20 Nov 2025 23:04:25 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] Flutter 3.38의 새로운 기능</title>
      <link>https://cajava.tistory.com/91</link>
      <description>&lt;h3 data-end=&quot;162&quot; data-start=&quot;125&quot; data-ke-size=&quot;size23&quot;&gt;1. 코드 작성 간소화 &amp;ndash; &amp;ldquo;dot shorthands&amp;rdquo;&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;602&quot; data-start=&quot;163&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;514&quot; data-start=&quot;163&quot;&gt;새로 도입된 Dart 기능 &amp;lsquo;dot shorthands&amp;rsquo;를 통해 타입이나 클래스명을 반복해서 쓰는 번거로움이 줄어듭니다. &lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;br /&gt;예)이전에는 MainAxisAlignment.start, CrossAxisAlignment.center처럼 풀네임을 써야 했습니다. &lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;396&quot; data-start=&quot;282&quot;&gt;
&lt;div&gt;
&lt;div&gt;&lt;span style=&quot;letter-spacing: 0px;&quot;&gt;Column( mainAxisAlignment: .start, crossAxisAlignment: .center, children: [ &amp;hellip; ], )&lt;/span&gt;&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-end=&quot;602&quot; data-start=&quot;515&quot;&gt;이 기능은 Dart 3.10 + Flutter 3.38부터 기본 활성화되어 있습니다. &lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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-end=&quot;623&quot; data-start=&quot;604&quot; data-ke-size=&quot;size23&quot;&gt;2. 웹 개발 경험 향상&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1015&quot; data-start=&quot;624&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;771&quot; data-start=&quot;624&quot;&gt;flutter run 시 웹 개발용 설정 파일(web_dev_config.yaml)을 프로젝트 루트에 둘 수 있어, 호스트&amp;middot;포트&amp;middot;헤더 등 설정을 팀 내 통일해서 사용 가능해졌습니다. &lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;880&quot; data-start=&quot;772&quot;&gt;프록시 설정이 강화되어, 웹 클라이언트가 동일 호스트의 동적 엔드포인트에 연결할 때 개발 환경이 좀 더 유연해졌습니다. &lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1015&quot; data-start=&quot;881&quot;&gt;웹용 상태 유지형(hot reload) 개발 경험이 개선되어서, -d web-server 옵션으로 여러 브라우저에서 동시에 개발 중에도 상태 변경이 즉시 반영됩니다.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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-end=&quot;1045&quot; data-start=&quot;1017&quot; data-ke-size=&quot;size23&quot;&gt;3. 프레임워크 및 UI 라이브러리 개선&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;1765&quot; data-start=&quot;1046&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1169&quot; data-start=&quot;1046&quot;&gt;OverlayPortal을 활용해 부모 위젯의 레이아웃 제약을 벗어나 앱 전역 수준의 UI(예: 알림 팝업)를 쉽게 구현할 수 있도록 개선됨.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1341&quot; data-start=&quot;1170&quot;&gt;안드로이드 네비게이션 경험 개선: MaterialApp의 뒤로 가기 제스처(back gesture)에 대해 홈 화면의 미리보기를 보여주는 예측 전환(predictive back route transition)이 기본 활성화 됨.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1468&quot; data-start=&quot;1342&quot;&gt;데스크탑(Windows)에서 연결된 디스플레이의 해상도, 주사율, 물리 크기 등을 조회할 수 있게 되어 복잡한 창 관리 기능 구현이 수월해졌습니다.&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1606&quot; data-start=&quot;1469&quot;&gt;스크롤/슬리버(Sliver) 관련 버그들이 대폭 수정되어, 복합 레이아웃(예: 가로 + 세로 스크롤 병합)에서 포커스 및 터치 이벤트 동작이 보다 예측 가능해졌습니다.&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;1765&quot; data-start=&quot;1607&quot;&gt;접근성(Accessibility) 강화: iOS에서 기본으로 접근성 활성화 가능해졌고, SliverSemantics 위젯으로 복잡한 슬리버 구조에서도 스크린리더 등에 대한 설명을 추가할 수 있도록 개선됨.&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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-end=&quot;1799&quot; data-start=&quot;1767&quot; data-ke-size=&quot;size23&quot;&gt;4. iOS 및 Android 플랫폼 지원 강화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2474&quot; data-start=&quot;1800&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;1882&quot; data-start=&quot;1800&quot;&gt;iOS 26, Xcode 26, macOS 26을 완벽히 지원합니다.&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2018&quot; data-start=&quot;1883&quot;&gt;iOS에서는 애플이 요구하는 &lt;b&gt;UIScene 라이프사이클&lt;/b&gt;을 지원하도록 업데이트 되었으며, 기존 앱이라면 수동 혹은 실험 기능을 통해 마이그레이션이 필요합니다.&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2160&quot; data-start=&quot;2019&quot;&gt;Android에서는 &amp;ldquo;16KB 페이지사이즈&amp;rdquo; 호환성 요구사항이 도입됨에 따라 NDK 버전을 r28 이상으로 업데이트했고, 이로 인해 앱 출시 준비에 중요한 변화를 담았습니다.&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2293&quot; data-start=&quot;2161&quot;&gt;Android 메모리 누수(memory leak) 이슈가 해결되었습니다. 특히 Activity 종료 시 시스템에 의해 강제 종료될 때 발생하던 누수가 수정됨.&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2474&quot; data-start=&quot;2294&quot;&gt;Android 개발 환경에서 요구하는 최소 버전이 정리됨: Java 17, Kotlin Gradle Plugin(KGP) 2.2.20, Android Gradle Plugin(AGP) 8.11.1, Gradle 8.14 등이 권장 버전으로 명시됨. &lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;span&gt;&lt;span&gt;&lt;span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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-end=&quot;2496&quot; data-start=&quot;2476&quot; data-ke-size=&quot;size23&quot;&gt;5. 엔진 및 렌더링 개선&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;2906&quot; data-start=&quot;2497&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;2642&quot; data-start=&quot;2497&quot;&gt;성능 오버레이(performance overlay)의 렌더링 효율이 높아져, 그래픽 백엔드(Skia, Impeller)에서 오버헤드가 줄고 성능 데이터를 더 정확히 볼 수 있게 됨.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2782&quot; data-start=&quot;2643&quot;&gt;Vulkan 및 OpenGL ES 백엔드의 안정성 및 성능 개선: 파이프라인 캐시, 페이스(waiters), 이미지 레이아웃 전환 등 여러 부문에서 개선이 이루어졌습니다.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;2906&quot; data-start=&quot;2783&quot;&gt;CanvasKit과 Skwasm 렌더러 간의 코드 공유(refactoring) 작업이 진행되어 앞으로 개발 및 유지보수가 더 수월해질 예정입니다.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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-end=&quot;2937&quot; data-start=&quot;2908&quot; data-ke-size=&quot;size23&quot;&gt;6. DevTools 및 IDE 통합 강화&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;3406&quot; data-start=&quot;2938&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3082&quot; data-start=&quot;2938&quot;&gt;&amp;ldquo;Widget Previews&amp;rdquo; 기능이 VS Code 및 IntelliJ/Android Studio 플러그인에 통합되어, IDE 내에서 실제 위젯 렌더링 미리보기가 가능해졌습니다.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3182&quot; data-start=&quot;3083&quot;&gt;프리뷰 환경에서 라이트/다크 모드 대응, IDE 테마 연동, 공간 최적화 등 사용자 경험이 개선됨.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3295&quot; data-start=&quot;3183&quot;&gt;@Preview 어노테이션이 더 유연해졌고(final이 아님), 커스텀 프리뷰를 만들기 위한 확장도 가능해졌습니다.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3406&quot; data-start=&quot;3296&quot;&gt;DevTools 네트워크 패널, 인스펙터(Widget Inspector) 등에서 사용자들이 지적한 문제점들이 많이 해결됨.&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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-end=&quot;3459&quot; data-start=&quot;3408&quot; data-ke-size=&quot;size23&quot;&gt;7. 폐기 예정(Deprecated) 및 깨질 수 있는(Breaking) 변경사항&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4151&quot; data-start=&quot;3460&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;3563&quot; data-start=&quot;3460&quot;&gt;SDK 루트에 있던 version 파일이 제거되고 flutter.version.json으로 대체됨.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3646&quot; data-start=&quot;3564&quot;&gt;AssetManifest.json이 기본 생성되지 않도록 변경됨.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3748&quot; data-start=&quot;3647&quot;&gt;SnackBar에 액션이 포함된 경우 자동으로 dismiss(사라지는) 되지 않도록 동작이 변경됨.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;3902&quot; data-start=&quot;3749&quot;&gt;OverlayPortal.targetsRootOverlay 생성자가 폐기(deprecated)되었고, 대신 OverlayPortal(overlayLocation: &amp;hellip;) 형태로 사용해야 함.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;li data-end=&quot;4044&quot; data-start=&quot;3903&quot;&gt;CupertinoDynamicColor의 withAlpha, withOpacity 등이 더 이상 권장되지 않고 대신 일반 Color 메서드를 사용하도록 유도됨.&lt;/li&gt;
&lt;li data-end=&quot;4151&quot; data-start=&quot;4045&quot;&gt;Android 타겟팅 시 Java 17 이상이 반드시 필요하며, 이는 기존 개발환경에서는 깜짝 변화일 수 있습니다.&amp;nbsp;&lt;span data-state=&quot;closed&quot;&gt;&lt;span data-testid=&quot;webpage-citation-pill&quot;&gt;&lt;/span&gt;&lt;/span&gt;&lt;/li&gt;
&lt;/ul&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;h2 data-end=&quot;4173&quot; data-start=&quot;4158&quot; data-ke-size=&quot;size26&quot;&gt;개발자에게 주는 팁&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-end=&quot;4683&quot; data-start=&quot;4174&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-end=&quot;4288&quot; data-start=&quot;4174&quot;&gt;이미 Flutter 3.x 버전을 사용 중이라면 가능하면 이번 3.38 버전으로 빠르게 업그레이드해서 신기능(dot shorthands, 웹 설정 개선, 위젯 프리뷰 등)을 활용하는 것이 좋습니다.&lt;/li&gt;
&lt;li data-end=&quot;4380&quot; data-start=&quot;4289&quot;&gt;특히 웹 및 데스크탑 타겟 프로젝트가 있다면, 웹 설정 파일(web_dev_config.yaml) 도입이나 슬리버/스크롤 관련 버그 수정이 유의미합니다.&lt;/li&gt;
&lt;li data-end=&quot;4486&quot; data-start=&quot;4381&quot;&gt;iOS 및 Android의 플랫폼 요구사항(예: UIScene, Java 17, 16KB 페이지 등)을 꼭 확인하세요. 미리 준비하지 않으면 릴리스 이후 대응이 급해질 수 있습니다.&lt;/li&gt;
&lt;li data-end=&quot;4603&quot; data-start=&quot;4487&quot;&gt;커스텀 플러그인이나 빌드 스크립트를 사용하고 있다면 breaking change 목록(예: version 파일 변경, AssetManifest.json 변화 등)을 검토해 두는 것이 안전합니다.&lt;/li&gt;
&lt;li data-end=&quot;4683&quot; data-start=&quot;4604&quot;&gt;Widget Previews 기능은 아직 실험적(experimental)입니다. 프로젝트에 도입할 때는 기대치와 안정성 수준을 고려하세요.&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://blog.flutter.dev/whats-new-in-flutter-3-38-3f7b258f7228&quot; target=&quot;_blank&quot; rel=&quot;noopener&amp;nbsp;noreferrer&quot;&gt;https://blog.flutter.dev/whats-new-in-flutter-3-38-3f7b258f7228&lt;/a&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>  Flutter</category>
      <category>3.38</category>
      <category>DART</category>
      <category>dot shorthands</category>
      <category>Flutter</category>
      <category>플러터</category>
      <author>Dogfoot_JW</author>
      <guid isPermaLink="true">https://cajava.tistory.com/91</guid>
      <comments>https://cajava.tistory.com/91#entry91comment</comments>
      <pubDate>Thu, 20 Nov 2025 22:54:54 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] lsyncd (로컬) 데이터 이중화 docker-compose.yaml 구성</title>
      <link>https://cajava.tistory.com/90</link>
      <description>&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;br /&gt;&lt;code&gt;Lsyncd&lt;/code&gt;는 매우 강력한 선택이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 Docker 환경에서 Lsyncd를 구성해, 재부팅 후에도 자동으로 동작하는 하드 백업 시스템을 완성하도로 하겠다.&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;우선 Lsyncd란?&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lsyncd(Live Syncing Daemon)는 Linux의 &lt;code&gt;inotify&lt;/code&gt; 기능을 활용해,&lt;br /&gt;&lt;b&gt;특정 폴더의 변경을 실시간 감지하고 rsync로 동기화&lt;/b&gt;하는 데몬이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, 파일이 추가&amp;middot;수정&amp;middot;삭제될 때마다 대상 폴더에 자동 반영한다.&lt;br /&gt;&amp;rarr; &lt;b&gt;&amp;ldquo;실시간 미러링 백업&amp;rdquo;&lt;/b&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;h2 data-ke-size=&quot;size26&quot;&gt;구성 방식&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;[호스트: /data]  &amp;larr;&amp;rarr;  [Docker 컨테이너: lsyncd]  &amp;larr;&amp;rarr;  [외장하드: /backup]&lt;/code&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;code&gt;/data&lt;/code&gt;: 원본 데이터가 있는 폴더 (예: 서버 데이터, 로그, 프로젝트 파일 등)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/backup&lt;/code&gt;: 외장 하드 마운트 경로 (동기화 대상)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;lsyncd&lt;/code&gt;: Docker 컨테이너로 동작하며 실시간 감시 + 동기화 수행&lt;/li&gt;
&lt;/ul&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;h2 data-ke-size=&quot;size26&quot;&gt;1. 폴더 준비&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;먼저, 동기화할 폴더와 백업 폴더를 만든다.&lt;/p&gt;
&lt;pre id=&quot;code_1763645483546&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo mkdir -p /data sudo mkdir -p /backup&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;code&gt;/backup&lt;/code&gt; 경로에 마운트한다.&lt;br /&gt;UUID 기반으로 자동 마운트되도록 &lt;code&gt;/etc/fstab&lt;/code&gt;에 등록해두는 게 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1763645505511&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo blkid # 외장하드의 UUID 확인

sudo vi /etc/fstab # 맨 아랫줄에 예시 추가&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;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;UUID=1234-ABCD /backup ext4 defaults 0 0&lt;/code&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;적용:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763645534898&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo mount -a&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;
&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;2. Dockerfile 작성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;lsyncd&lt;/code&gt;를 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;b&gt;Dockerfile&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;FROM ubuntu:22.04

RUN apt-get update &amp;amp;&amp;amp; \
    apt-get install -y lsyncd rsync &amp;amp;&amp;amp; \
    mkdir -p /etc/lsyncd /sync_source /sync_target

COPY lsyncd.conf.lua /etc/lsyncd/lsyncd.conf.lua

CMD [&quot;lsyncd&quot;, &quot;-nodaemon&quot;, &quot;/etc/lsyncd/lsyncd.conf.lua&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;
&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;3. Lsyncd 설정파일 작성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;lsyncd.conf.lua&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;nix&quot;&gt;&lt;code&gt;settings {
    logfile = &quot;/var/log/lsyncd.log&quot;,
    statusFile = &quot;/var/log/lsyncd.status&quot;,
    maxDelays = 5,
}

sync {
    default.rsync,
    source = &quot;/sync_source&quot;,
    target = &quot;/sync_target&quot;,
    rsync = {
        archive = true,
        compress = false,
        verbose = true,
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;&amp;nbsp;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;주요 옵션 설명&lt;/b&gt;&lt;/h4&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;항목&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;logfile&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;로그 경로&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;inotifyMode&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;변경 감지 모드 (CloseWrite는 파일 닫힐 때 감지)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;maxDelays&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;동기화 지연 최대 시간 (초 단위)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;archive&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;파일 소유자/권한/타임스탬프 유지&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;code&gt;--delete&lt;/code&gt;&lt;/td&gt;
&lt;td&gt;원본에서 삭제된 파일은 대상에서도 삭제 (완전 미러링)&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;4. docker-compose.yml 작성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;docker-compose.yml&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;http&quot;&gt;&lt;code&gt;version: &quot;3.8&quot;

services:
  lsyncd:
    build: .
    container_name: lsyncd_backup
    volumes:
      - /data:/sync_source:ro
      - /backup:/sync_target
      - ./lsyncd.conf.lua:/etc/lsyncd/lsyncd.conf.lua:ro
    restart: unless-stopped&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;/data&lt;/code&gt;는 읽기 전용(&lt;code&gt;:ro&lt;/code&gt;)로 마운트해 원본을 보호하고,&lt;br /&gt;&lt;code&gt;/backup&lt;/code&gt;은 쓰기 가능으로 두어 복제 작업이 가능하게 한다.&lt;br /&gt;&lt;br /&gt;&lt;br /&gt;&lt;/p&gt;
&lt;/blockquote&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;5. 실행하기&lt;/h2&gt;
&lt;pre id=&quot;code_1763645642677&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker compose up -d # -d 옵션을 빼면 현재 상태를 바로 확인&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;정상 실행되면 Lsyncd가 &lt;code&gt;/data&lt;/code&gt; 폴더의 변경을 실시간 감지하여&lt;br /&gt;&lt;code&gt;/backup&lt;/code&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;로그 확인:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763645726526&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;docker logs -f lsyncd_backup&lt;/code&gt;&lt;/pre&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;h2 data-ke-size=&quot;size26&quot;&gt;6. 재부팅 후 자동 실행&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Compose 파일의 &lt;code&gt;restart: unless-stopped&lt;/code&gt; 옵션 덕분에&lt;br /&gt;Docker 데몬이 부팅될 때 Lsyncd 컨테이너가 자동 재시작된다.&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_1763645762239&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo systemctl is-enabled docker&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;b&gt;만약 비활성이라면:&lt;/b&gt;&lt;/p&gt;
&lt;pre id=&quot;code_1763645783713&quot; class=&quot;bash&quot; data-ke-language=&quot;bash&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;sudo systemctl enable docker&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;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;7. Docker 권한 구조 이해&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Lsyncd 컨테이너는 &lt;b&gt;컨테이너 내부에서 root 권한&lt;/b&gt;으로 동작한다.&lt;br /&gt;하지만 &lt;b&gt;호스트 전체의 root 권한은 아니다.&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;table data-ke-align=&quot;alignLeft&quot;&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;권한 종류&lt;/th&gt;
&lt;th&gt;설명&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;컨테이너 내부 root&lt;/td&gt;
&lt;td&gt;있음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;호스트 전체 root&lt;/td&gt;
&lt;td&gt;없음&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;마운트된 디렉토리 조작&lt;/td&gt;
&lt;td&gt;가능&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;시스템 설정 / 커널 접근&lt;/td&gt;
&lt;td&gt;불가능&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;즉, &lt;code&gt;/data&lt;/code&gt;, &lt;code&gt;/backup&lt;/code&gt; 폴더 내의 파일 읽기/쓰기/삭제는 가능하지만&lt;br /&gt;호스트 OS 자체에는 영향을 주지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;⚠️ &lt;code&gt;--privileged&lt;/code&gt; 옵션을 주면 호스트의 root 권한과 거의 동일해지지만,&lt;br /&gt;&lt;b&gt;보안상 매우 위험하므로 절대 추천하지 않는다.&lt;br /&gt;&lt;br /&gt;&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&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;8. 실제 동작 예시&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;1️⃣ &lt;code&gt;/data&lt;/code&gt;에 파일을 생성&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;echo &quot;test&quot; &amp;gt; /data/example.txt&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;2️⃣ &lt;code&gt;/backup&lt;/code&gt; 확인&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;cat /backup/example.txt&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3️⃣ &lt;code&gt;/data&lt;/code&gt;에서 파일 삭제&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;rm /data/example.txt&lt;/code&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;rarr; 잠시 후 &lt;code&gt;/backup/example.txt&lt;/code&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;h2 data-ke-size=&quot;size26&quot;&gt;결론&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;실시간 변경 감지 (inotify 기반)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;Docker로 환경 분리&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;재부팅 후에도 자동 실행&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;원본 보호 (&lt;code&gt;:ro&lt;/code&gt; 마운트)&lt;/b&gt;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;호스트 시스템에 영향 없음&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;단점이라면, &lt;b&gt;Docker를 통한 오버헤드가 아주 약간 존재&lt;/b&gt;한다는 점 정도다.&lt;br /&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;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;이 설정을 기반으로, NAS나 백업 서버 자동화에도 확장 가능하다.&lt;/i&gt;&lt;br /&gt;예를 들어 &lt;code&gt;/backup&lt;/code&gt;을 NFS나 SMB 공유 폴더로 연결해 네트워크 백업 시스템으로도 손쉽게 확장할 수 있다.&lt;/p&gt;</description>
      <category>  Linux</category>
      <category>Backup</category>
      <category>Docker</category>
      <category>docker-compose</category>
      <category>lsync</category>
      <category>rsync</category>
      <category>도커</category>
      <category>리눅스</category>
      <category>미러서버</category>
      <category>백업</category>
      <category>자동백업</category>
      <author>Dogfoot_JW</author>
      <guid isPermaLink="true">https://cajava.tistory.com/90</guid>
      <comments>https://cajava.tistory.com/90#entry90comment</comments>
      <pubDate>Thu, 20 Nov 2025 22:42:34 +0900</pubDate>
    </item>
    <item>
      <title>[Docker] 손쉽게 NodeJS 프로젝트 구성 (with docker-compose)</title>
      <link>https://cajava.tistory.com/89</link>
      <description>&lt;h1&gt;Node.js 프로젝트 Docker 구성 및 폴더 구조&lt;/h1&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이 글에서는 Node.js 기반 예시용 Discord 봇 프로젝트를 Docker Compose와 Dockerfile을 사용해 구성하는 방법을 설명하도록 하겠다.&lt;br /&gt;MariaDB를 데이터베이스로 사용하며, 소스코드와 Node.js 모듈을 효율적으로 관리할 수 있는 구성을 보여준다.&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;Node.js 프로젝트 구조&lt;/h2&gt;
&lt;pre class=&quot;jboss-cli&quot;&gt;&lt;code&gt;my-project/
├── src/                   # TypeScript 소스코드
│   ├── repository/          
│   ├── modules/            
│   └── index.ts           # 엔트리 포인트
├── db/                    # MariaDB 데이터 및 초기 쿼리
│   ├── .mariadb/          # DB 실제 데이터 저장
│   └── mariadb_init/      # 초기 SQL 스크립트
├── .env                   # 환경 변수
├── package.json            # Node.js 의존성 관리
├── tsconfig.json           # TypeScript 설정
├── Dockerfile             # Node.js 컨테이너 구성
└── docker-compose.yml     # 서비스 정의
&lt;/code&gt;&lt;/pre&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;h2 data-ke-size=&quot;size26&quot;&gt;Docker Compose 구성&lt;/h2&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;version: &quot;3.9&quot;

services:
  myproject_server:
    build: 
      context: .
      dockerfile: ./Dockerfile #프로젝트를 컨테이너에 구성하기 위해 Dockerfile 실행
    restart: always
    depends_on:
      - myproject_db #DB먼저 될때까지 대기
    volumes:
      - ./src:/app/src #소스 실시간 동기화
      - /app/node_modules #모듈 파일 유지

  myproject_db:
    image: mariadb:11.4
    restart: always
    environment:
      MYSQL_ROOT_PASSWORD: rootpassword
      MYSQL_DATABASE: mydb
      MYSQL_USER: me
      MYSQL_PASSWORD: mypw
    ports:
      - &quot;10001:3306&quot;
    volumes:
      - ./db/.mariadb:/var/lib/mysql
      - ./db/mariadb_init:/docker-entrypoint-initdb.d #초기 실행 쿼리문 복사

networks:
  #현재 컴포즈 내에서 공유할 네트워크
  default:
    name: my_net
    driver: bridge
&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;b&gt;설명:&lt;/b&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. myproject_server&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;build&lt;/b&gt;: 현재 디렉터리와 Dockerfile을 기반으로 컨테이너 빌드&lt;/li&gt;
&lt;li&gt;&lt;b&gt;restart: always&lt;/b&gt;: 컨테이너가 중지되면 자동 재시작&lt;/li&gt;
&lt;li&gt;&lt;b&gt;depends_on&lt;/b&gt;: DB 컨테이너가 준비될 때까지 대기&lt;/li&gt;
&lt;li&gt;&lt;b&gt;volumes&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;./src:/app/src&lt;/code&gt;: 호스트의 소스를 컨테이너와 실시간 동기화&lt;/li&gt;
&lt;li&gt;&lt;code&gt;/app/node_modules&lt;/code&gt;: 컨테이너 내부 모듈 유지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. myproject_db&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;image&lt;/b&gt;: MariaDB 11.4 사용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;environment&lt;/b&gt;: DB 계정 및 비밀번호 설정&lt;/li&gt;
&lt;li&gt;&lt;b&gt;ports&lt;/b&gt;: 호스트 10001 &amp;rarr; 컨테이너 3306&lt;/li&gt;
&lt;li&gt;&lt;b&gt;volumes&lt;/b&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;./db/.mariadb&lt;/code&gt;: DB 데이터 영구 저장&lt;/li&gt;
&lt;li&gt;&lt;code&gt;./db/mariadb_init&lt;/code&gt;: 초기 SQL 스크립트 자동 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 네트워크&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;my_net&lt;/code&gt; 브리지 네트워크를 통해 컨테이너끼리 통신&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;h2 data-ke-size=&quot;size26&quot;&gt;Dockerfile 구성&lt;/h2&gt;
&lt;pre class=&quot;dockerfile&quot;&gt;&lt;code&gt;FROM node:20

WORKDIR /app

# package.json 복사 후 의존성 설치
COPY ./package*.json ./
RUN npm install

# 환경파일 및 TypeScript 설정 복사
COPY ./.env ./
COPY ./tsconfig.json ./

# TypeScript 글로벌 설치
RUN npm install -g typescript ts-node

CMD [&quot;npm&quot;, &quot;start&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;&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;WORKDIR /app&lt;/b&gt; : 컨테이너 내부 작업 디렉토리&lt;/li&gt;
&lt;li&gt;&lt;b&gt;package.json / package-lock.json 복사&lt;/b&gt; &amp;rarr; &lt;code&gt;npm install&lt;/code&gt;로 의존성 설치&lt;/li&gt;
&lt;li&gt;&lt;b&gt;환경 변수, TypeScript 설정 복사&lt;/b&gt; &amp;rarr; 컨테이너 빌드 시 필요한 설정 적용&lt;/li&gt;
&lt;li&gt;&lt;b&gt;TypeScript 글로벌 설치&lt;/b&gt; &amp;rarr; &lt;code&gt;ts-node&lt;/code&gt;로 TypeScript 실행 가능&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CMD [&quot;npm&quot;, &quot;start&quot;]&lt;/b&gt; &amp;rarr; 컨테이너 시작 시 프로젝트 실행&lt;/li&gt;
&lt;/ol&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;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;&lt;code&gt;docker-compose up -d&lt;/code&gt; 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;logs -f&lt;/code&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;이 구성을 통해 호스트에서 소스를 바로 수정하면서 컨테이너에서 실행되는 Node.js 봇을 실시간으로 테스트할 수 있고, MariaDB 데이터도 영구 저장할 수 있다.&lt;/p&gt;</description>
      <category>  Linux</category>
      <category>container</category>
      <category>Docker</category>
      <category>docker compose</category>
      <category>Kafka</category>
      <category>node</category>
      <category>node docker</category>
      <category>nodejs</category>
      <category>도커</category>
      <category>오케스트레이션</category>
      <category>컴포즈</category>
      <author>Dogfoot_JW</author>
      <guid isPermaLink="true">https://cajava.tistory.com/89</guid>
      <comments>https://cajava.tistory.com/89#entry89comment</comments>
      <pubDate>Thu, 20 Nov 2025 22:27:59 +0900</pubDate>
    </item>
    <item>
      <title>[책] 이펙티브 엔지니어 후기</title>
      <link>https://cajava.tistory.com/88</link>
      <description>&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;KakaoTalk_20250315_194831392.jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/c2wYRe/btsMLYH4gTV/85eLJGeukwkZbbrj0SXvZk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/c2wYRe/btsMLYH4gTV/85eLJGeukwkZbbrj0SXvZk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/c2wYRe/btsMLYH4gTV/85eLJGeukwkZbbrj0SXvZk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fc2wYRe%2FbtsMLYH4gTV%2F85eLJGeukwkZbbrj0SXvZk%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;453&quot; height=&quot;604&quot; data-filename=&quot;KakaoTalk_20250315_194831392.jpg&quot; data-origin-width=&quot;1080&quot; data-origin-height=&quot;1440&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;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;소감&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&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;회사에 있는 책 중에 &quot;이펙티브 엔지니어&quot;라는 책을 발견하게 되었다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;요즘 사내에 개발자들이 많아져서 그런지 IT 관련 서적을 많이 구매하는 것 같았다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;살짝 훑어봤을 때 내용이 크게 어려워 보이지 않았고, 책에서 사용하는 용어들도 대부분 개발자라면 바로 알 만한 단어들로 이뤄져 있었다. 두껍지도 않아 (대략 250페이지 정도) 시간 내서 읽어보려고 이번 주에 완독을 했는데, 사실 테마가 &quot;개발자들을 위한&quot;, &quot;IT업무 효율&quot;이지만, 이 책에서 나오는 개발 용어들을 일반 업무용어로 바꿔서 읽으면 비개발 직군에게도 충분히 유용할 수 있는 자기 개발서라고 생각한다. 특히 업무의 우선순위도 선별과정, 지표를 활용한 내 업무에서의 활용, 차라리 빠른 실패를 경험하기 등 내가 읽으면서도 &quot;응? 이건 개발자들에게 국한되지는 않겠는걸?&quot;이라는 생각이 들기도 했다. 하여튼 평상시 본능적으로 이끌렸던 &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;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;요약&lt;/b&gt;&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&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;Part 1. 올바른 마인드셋을 갖춰라&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;1. 레버리지가 높은 활동에 집중해라.&lt;/b&gt;&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;&amp;nbsp;레버리지는 생산한 효과/투자한 시간으로 내가 투자한 시간 대비 얼마나 큰 이펙트를 발휘하는지에 대한 척도다. &quot;업무를 줄이기 위한 업무&quot;, &quot;우선순위를 정하는 업무&quot;, &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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;2. 학습을 위해 최적화하라&lt;/b&gt;&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;&amp;nbsp;자기 자신을 성장시키기 위해서는 수단과 방법을 가리지 말아야 한다. 학습은 늘어날수록 이자처럼 복리가 발생한다. 더 많이 배우려 하고, 더 빨리 시작할수록 좋다. 회사를 다닌다고 학습할 기회가 없는 게 아니다. 회사라는 상황에 맞는 방식으로 학습을 최적화를 하면 된다. &quot;회사에서 가장 뛰어난 개발자의 추상화 코드 연구하기&quot;, &quot;혹독한 리뷰어에게 코드리뷰를 받기&quot;, &quot;여러 프로젝트를 경험하며 자신의 문제점 파악하기&quot;, &quot;회사에서 지원해 주는 웬만한 교육들을 들어보기&quot; 등 다양하게 방식으로 얼마든지 최적화할 수 있고, 회사 밖에서도 내 상황에 맞게 학습을 최적화할 수 있다. &quot;생산적인 토론 그룹에 참가하기&quot;, &quot;더 나은 개발자가 되기 위해 사이드 프로젝트를 하기&quot;, &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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;3. 우선순위를 정기적으로 점검하라&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;적절하고 올바른 목표를 선택하면 제품의 성장 속도를 크게 높일 수 있다. 핵심 영역에서 0.5퍼센트만 성장해도 사용자가 복리 이자처럼 수백만 명이 추가될 수 있다. 우선순위를 높이는 것 또한 레버리지를 높이는 활동이다. &quot;자신이 할 일을 적고 검토해 보기&quot;, &quot;중요하지만 급하지 않은 일을 하기&quot;, &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;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Part 2. 실행, 실행, 실행&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;4. 반복 속도에 투자하라&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;업무를 할 때 우리는 종종 반복적인 일을 접하게 된다. 반복 속도가 느려지는 것은 다른 기회를 놓칠 수 있고 더 많은 것을 배우기가 힘들어진다. 반복 속도를 높이는 일 또한 레버리지가 높은 활동이다. 만약 배포 프로세스가 너무 오래 걸린다면 자동화 배포 툴을 활용할 수 있고, 컴파일 시간, 개발 소요시간이 오래 걸린다면, 그에 맞는 다양한 IDE를 사용할 수 있다. 개발을 하면서 주기적인 반복 과정을 전체론적인 시각(Holistic view)으로 보고, 자신의 권한 범위 내에 있는 회사 또는 팀과 관련된 병목을 간과하지 말자.&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;size18&quot;&gt;&lt;b&gt;5. 개선하려는 사항을 측정하라&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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;6. 아이디어는 일찍 그리고 자주 검증하라&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;조슈아 레비는 밤을 새워가며 구글의 대항마로 준비한 쿨을 공개했다. 하지만 기대와는 다르게 쿨은 정말 느리고 버그가 많았으며, 심지어 타임지(Time manazine)에서는 &quot;제구실을 못 한다&quot;라고 까지 했다. 이런 참패를 겪은 쿨의 개발자들과 레비는 이 경험에서 &quot;제품 조기 검증의 중요성&quot;을 깨달았다. 언론에 세부사항이 나가는 것이 두려워 제품을 시험해 볼 알파 테스터를 한 명도 고용하지 않았던 것이다. 시장에서 적은 노력으로 검증할 방법을 찾는 것도 중요하며, 미리 프로토타입 모델 출시하기, A/B테스트 등을 통해 사용자들의 반응을 조사해 보기 등을 통해 사전에 소프트웨어를 빠르고 효과적으로 검증해야 한다. 혼자서 모든 것을 담당하는 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;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size18&quot;&gt;&lt;b&gt;7. 프로젝트 추정 기술을 향상시켜라&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;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;&quot;4주 이내에 완성할 가능성이 50%, 8주 이내에는 90% &quot;라고 말하는 것처럼 추정을 확률 분포로 생각하기&lt;/li&gt;
&lt;li&gt;내가 하면 1분 만에 끝내지만 다른 사람이 하면 3시간이 걸릴 수 있기 때문에 실제 업무 담당자가 추정하게 하기&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;h4 style=&quot;color: #000000; text-align: start;&quot; data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;Part 3. 장기적인 가치를 구축하라&lt;/b&gt;&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;8. 품질과 실용주의 사이에서 균형을 유지하라.&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;9. 운영 부담을 최소화하라&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억 달러로 인수될 당시 사용자 수는 4천만 명에 달했다. 그에 반해 직원은 단 13명이 전부였다. 이들이 얻은 가장 가치 있는 교훈은 &quot;운영 부담을 최소화하기였다&quot;, 검증된 기술을 고집하고, 간단한 일부터 처리하기, 그리고 기계적인 일에 대한 자동화가 운영 부담을 줄여 주여 주는데 큰 도움이 되었다고 한다.&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&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;&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;&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>  Book</category>
      <category>개발자기개발</category>
      <category>개발자책</category>
      <category>엔지니어</category>
      <category>이펙티브</category>
      <author>Dogfoot_JW</author>
      <guid isPermaLink="true">https://cajava.tistory.com/88</guid>
      <comments>https://cajava.tistory.com/88#entry88comment</comments>
      <pubDate>Sun, 16 Mar 2025 17:33:41 +0900</pubDate>
    </item>
    <item>
      <title>Certbot SSL발급 최고의 적용 방법 with Nginx</title>
      <link>https://cajava.tistory.com/87</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;개요&lt;/h2&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;certbot을 통해 발급한 &lt;code&gt;*.mydomain.com&lt;/code&gt; 이라는 와일드카드 주소가 renew로 갱신이 안 돼서 다른 방법을 찾게 되었다.&lt;br /&gt;와일드카드도 자동 갱신이 가능하긴 한데 과정이 너무 복잡하여 다른 방법을 찾게 되었고, 이번엔 가장 깔끔한 방법을 정리하고자 한다.&lt;br /&gt;아래 내용은 web호스팅, 프록시 호스팅을 예시로 두고 있다.&lt;br /&gt;또한 가비아 등 별도로 도메인 공급업체에서 ACME 수동 인증을 통하지 않아서 번거로움을 줄일 수 있다.&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Ubuntu 22.04&lt;/li&gt;
&lt;li&gt;certbot&lt;/li&gt;
&lt;li&gt;nginx&lt;/li&gt;
&lt;li&gt;code-server (설치되어있음. 8080 포트 사용)&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;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;1. Certbot 설치하기&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Ubuntu/Debian&lt;/h4&gt;
&lt;pre class=&quot;sql&quot;&gt;&lt;code&gt;sudo apt update 
sudo apt install certbot
sudo apt install python3-certbot-nginx # Nginx와 acme챌린지를 위한 플러그인
sudo apt install python3-certbot-apache # Apache와 acme챌린지를 위한 플러그인&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;CentOS/RHEL&lt;/h4&gt;
&lt;pre class=&quot;cmake&quot;&gt;&lt;code&gt;sudo yum install certbot 
sudo yum install python3-certbot-nginx # Nginx와 acme챌린지를 위한 플러그인
sudo yum install python3-certbot-apache # Apache와 acme챌린지를 위한 플러그인&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;예제에서는 apache도 존재하지만, 내가 시도하는 방식은 nginx 위주로만 작성하였기 때문에 nginx플러그인을 설치하도록 한다.&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. 인증서 발급하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;!!!! 해당 방식을 진행하기 전엔 무조건 도메인 제공 업체에 내가 사용할 1차 도메인 mydomain.com, 2차 도메인 second.mydomain.com 등이 미리 추가가 되어있어야 한다!!!!&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;아래 명령어를 통해 SSL 발급을 시도한다.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;sudo certbot certonly --nginx --cert-name mydomain.com -d mydomain.com -d www.mydomain.com -d code.mydomain.com
# mydomain.com과 www.mydomain.com과 code.mydomain.com에 대한 인증서를 izvillain.com이라는 이름으로 합쳐서 발급&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;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;code&gt;sudo certbot&lt;/code&gt;: SSL을 발급하고 저장할 때 Certbot이 시스템디렉터리에 접근하여 작업을 수행하기 때문에 sudo권한이 필요&lt;/li&gt;
&lt;li&gt;&lt;code&gt;certonly&lt;/code&gt;: 해당 옵션이 없다면 nginx나 apache 플러그인을 통해 certbot이 알아서 막. conf파일을 수정하게 된다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--nginx&lt;/code&gt;: 인증 방식 중 웹서버인증방식인 nginx를 선택&lt;/li&gt;
&lt;li&gt;&lt;code&gt;--cert-name&lt;/code&gt;: 인증서들을 묶어둘 대표 이름 (나중에 SSL 디렉터리가 해당이름으로 생성됨)&lt;/li&gt;
&lt;li&gt;&lt;code&gt;-d&lt;/code&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;3. Nginx Https(80 TO 443) 자동 업그레이드 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 잘 진행했다면 SSL인증서는 &lt;code&gt;/etc/letsencrypt/live/mydomain.com/&lt;/code&gt; 에 존재할 것이다.&lt;br /&gt;이제 할 일은 Nginx 웹서버로 들어오는 모든 요청을 https로 업그레이드하도록 유도하게 하는 것이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어를 입력하여, 공용 &lt;code&gt;http.conf&lt;/code&gt; 파일을 만들도록 하자&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;cd /etc/nginx/sites-available
sudo vi http.conf&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;perl&quot;&gt;&lt;code&gt;#shared http
server{
    listen 80;
    return 308 https://$host:443$request_uri; #들어오는 모든 80번 포트 요청에 대해 443으로 업그레이드
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;:wq&lt;/code&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;pre class=&quot;awk&quot;&gt;&lt;code&gt;sudo ln -s /etc/nginx/sites-available/http.conf /etc/nginx/sites-enabled/http.conf&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;b&gt;nginx 유효성 검사&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo nginx -t&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Success가 잘 표시된다면 문제없는 상태다.&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. SSL 적용하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;조금만 더 하면 기초작업은 완료된다.&lt;br /&gt;아래 명령어를 통해 SSL을 적용할 도메인에 대한 conf 파일을 만들어주도록 하자.&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;sudo vi /etc/nginx/sites-available/mydomain.com.conf&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;아래 .conf파일 에서 1번과 2번은 같은 웹사이트로 가게 하는 예시고,&lt;br /&gt;3번은 별도로 내부에서 호스팅 하는 다른 서버로 이동하게 하는 예시다.&lt;br /&gt;나의 경우는 code-server를 로컬포트 8080으로 열고,&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;3번과 같이 구성하여 외부에서는 code.mydomain.com으로만 유입 가능하게 처리를 했다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;#mydomain.com을 사용하는 모든 호스팅들

#1: 웹호스트
server {
        listen 443 ssl;
        server_name mydomain.com; #1차도메인으로  들어오는 주소  캐치

        ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem; #
        ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;

        location / {
                root /home/user/myweb/build/web; #호스팅할 웹사이트 경로
                index index.html; #시작할 웹사이트 인덱스 파일
        }
}

#2: 웹호스트 www에 대해 강제로 mydomain.com으로 이동
server {
        listen 443 ssl;
        server_name www.mydomain.com; #2차 도메인 www로 들어오는 주소  캐치

        ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;

        return 301 https://mydomain.com$request_uri; #이동시킬  실제  주소
}


#3: 프록시호스트 (또는 가상호스트)
server {
        listen 443 ssl;
        server_name code.mydomain.com; #2차 도메인 code로 들어오는 주소 캐치

        ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem;
        ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;

        location / {
                proxy_pass http://127.0.0.1:8080; #로컬에서 호스팅중인 실제 주소
                proxy_set_header Host code.mydomain.com; #반드시 요청 URL 과 동일하게 적어야함 (오류가 난다면 대부분 여기서 문제,  포트도 붙여서 시도해보기)
                proxy_set_header Upgrade $http_upgrade;
                proxy_set_header Connection upgrade;
                #proxy_set_header Accept-Encoding gzip;

        }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;:wq&lt;/code&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;필요하다면 server{} 항목을 더 추가하여 info.mydomain.com 또는 sample.mydomain.com 같은 다양한 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;b&gt;링크 생성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;sudo ln -s /etc/nginx/sites-available/mydomain.com.conf /etc/nginx/sites-enabled/mydomain.com.conf&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;링크를 생성한 이유는 nginx 기본 설정으로 인해 &lt;code&gt;sites-enabled&lt;/code&gt; 폴더 안에 있는 &lt;code&gt;.conf&lt;/code&gt; 파일만 읽을 수 있기 때문이다. 나중에 해당 사이트를 잠시 안 쓰려면 mydomain.com.conf.bak 으로 이름만 바꾸면 된다.&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;nginx 유효성 검사&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo nginx -t&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Success 가 잘 출력되면 문제없음.&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;Nginx 서비스 재시작&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo systemctl restart nginx &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;h3 data-ke-size=&quot;size23&quot;&gt;5. (선택사항) SSL 자동갱신 구성&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 명령어를 입력하면 certbot으로 생성한 모든 SSL를 갱신할 수 있다.&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo certbot renew&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;근데 3개월마다 갱신해주려고 신경을 써야한다는게 여간 귀찮은 일이 아닐 수 없다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이를 해결하기 위한 가장 심플한 방법은 리눅스의 자동화 기능인 crontab을 사용해서 갱신되도록 구성하면 된다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;우선 아래 명령어로 원하는 디렉토리에 certbot관련된 폴더와 파일을 만들도록 하자&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;cd /home/user #디렉토리는 꼭 자신의 디렉토리에 맞게 변경
mkdir certbot
cd certbot
vi renew.sh&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;b&gt;쉘스크립트 작성&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;bash&quot;&gt;&lt;code&gt;#!/bin/bash

LOG_FILE=&quot;/home/user/certbot/renew.log&quot; #디렉토리는 꼭 자신의 디렉토리에 맞게 변경

echo &quot;[$(date)] Certbot 갱신 시작&quot; &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;

certbot renew &amp;gt;&amp;gt; &quot;$LOG_FILE&quot; 2&amp;gt;&amp;amp;1

echo &quot;[$(date)] Certbot 갱신 완료&quot; &amp;gt;&amp;gt; &quot;$LOG_FILE&quot;
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;:wq 로 저장 및 나오기&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 class=&quot;angelscript&quot;&gt;&lt;code&gt;chmod 777 renew.sh&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;여기까지 되었다면 아래 명령어를 입력해서&lt;/p&gt;
&lt;pre class=&quot;haxe&quot;&gt;&lt;code&gt;sudo ./renew.sh&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;b&gt;크론탭 추가&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;nginx&quot;&gt;&lt;code&gt;sudo crontab -e #반드시 sudo로 해야 su권한으로만 실행 가능한 certbot renew 가 정상실행됨.&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;dts&quot;&gt;&lt;code&gt;#3시간 마다 certbot renew
0 */3 * * * /home/user/certbot/renew.sh #디렉토리는 꼭 자신의 디렉토리에 맞게 변경&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 3시간마다 자동으로 SSL을 모두 갱신할 수 있게 되었다.&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;6. (선택사항) 2차 도메인 추가&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;현재까지 나는 &lt;code&gt;mydomain.com&lt;/code&gt;, &lt;code&gt;www.mydomain.com&lt;/code&gt; &lt;code&gt;code.mydomain.com&lt;/code&gt; 이렇게 3가지의 SSL 인증서를 &lt;code&gt;mydomain.com&lt;/code&gt;이라는 곳에 합쳐서 발급했다.&lt;br /&gt;만약 &lt;code&gt;info.mydomain.com&lt;/code&gt; 이라는 주소를 쓰는 사이트를 호스팅 하려면 어떻게 해야 할까?&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;b&gt;!!!! 해당 방식을 진행하기 전엔 무조건 도메인 제공 업체에 내가 사용할 1차 도메인 mydomain.com, 2차 도메인 second.mydomain.com 등이 미리 추가가 되어있어야 한다!!!!&lt;/b&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;아래 코드를 통해 &lt;code&gt;info.mydomain.com&lt;/code&gt; 인증서를 추가하자.&lt;/p&gt;
&lt;pre class=&quot;css&quot;&gt;&lt;code&gt;sudo certbot certonly --nginx --cert-name mydomain.com -d mydomain.com -d www.mydomain.com -d info.mydomain.com&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이번엔 처음 인증서를 발급할 때랑은 달리&lt;/p&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;- - - - - - - - - - - - - - - - - - - - - - - - - - 
You are updating certificate izvillain.com to include new domain(s):
+ info.mydomain.com

You are also removing previously included domain(s):
(None)

Did you intend to make this change?
- - - - - - - - - - - - - - - - - - - - - - - - - -
(U)pdate certificate/(C)ancel: U
&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;b&gt;반드시!!&lt;/b&gt; 잘 확인해보고 &lt;code&gt;U&lt;/code&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차 도메인을 신규호스트에 물리기 위해 nginx의 .conf파일을 수정하도록 하자&lt;/p&gt;
&lt;pre class=&quot;vim&quot;&gt;&lt;code&gt;vi /etc/nginx/sites-enabled/mydomain.com.conf&lt;/code&gt;&lt;/pre&gt;
&lt;pre class=&quot;routeros&quot;&gt;&lt;code&gt;... 끝에 추가

#4: 추가 웹호스트
server {
        listen 443 ssl;
        server_name info.mydomain.com; #1차도메인으로  들어오는 주소  캐치

        ssl_certificate /etc/letsencrypt/live/mydomain.com/fullchain.pem; #
        ssl_certificate_key /etc/letsencrypt/live/mydomain.com/privkey.pem;

        location / {
                root /home/user/infoweb/build/web; #호스팅할 웹사이트 경로
                index index.html; #시작할 웹사이트 인덱스 파일
        }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;code&gt;:wq&lt;/code&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;nginx 유효성 검사&lt;/b&gt;&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo nginx -t&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Success가 잘 표시된다면 문제없는 상태므로 nginx 서비스 재시작&lt;/p&gt;
&lt;pre class=&quot;ebnf&quot;&gt;&lt;code&gt;sudo systemctl restart nginx&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이제 새로 추가한 주소인 info.mydomain.com 이 잘 접속되는지 확인해보자.&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;7. (선택사항) Certbot 유용한 명령어들&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;code&gt;sudo certbot certificates&lt;/code&gt;: 현재 certbot을 통해 등록되어 있는 모든 인증서들을 표시한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo certbot delete --cert-name mydomain.com&lt;/code&gt;: 해당 이름을 사용하는 모든 인증서를 삭제한다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;sudo certbot renew&lt;/code&gt;: 모든 SSL인증서 갱신.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>  Linux</category>
      <category>certbot</category>
      <category>code-server</category>
      <category>https</category>
      <category>letsencrypt</category>
      <category>ssl</category>
      <category>서트폿</category>
      <author>Dogfoot_JW</author>
      <guid isPermaLink="true">https://cajava.tistory.com/87</guid>
      <comments>https://cajava.tistory.com/87#entry87comment</comments>
      <pubDate>Tue, 4 Mar 2025 14:59:29 +0900</pubDate>
    </item>
    <item>
      <title>[Flutter] 두 키보드 포커스 해제 방법의 차이 (FocusScope VS FocusManager)</title>
      <link>https://cajava.tistory.com/86</link>
      <description>&lt;h3 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;서론&lt;/span&gt;&lt;span&gt;&lt;/span&gt;&lt;/h3&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Flutter에서 키보드를 내리는 방법에는 여러 가지가 있다. 그중에서도 가장 많이 사용하는 방법이 &lt;/span&gt;&lt;span&gt;FocusManager.instance.primaryFocus?.unfocus();&lt;/span&gt;&lt;span&gt;와 &lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;FocusScope.of(context).unfocus();이 있다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이번 글에서는 이 두 방식의 차이점과 각각의 특징에 대해 자세히 살펴보려고 한다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-pm-slice=&quot;1 1 []&quot; 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-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;1. &lt;/span&gt;&lt;span&gt;FocusManager.instance.primaryFocus?.unfocus();&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 방식은 현재 활성화된 입력 포커스를 전역적으로 해제하는 방법이다. Flutter의 &lt;/span&gt;&lt;span&gt;FocusManager&lt;/span&gt;&lt;span&gt;는 애플리케이션 전체의 포커스 상태를 관리하는데, 여기서 &lt;/span&gt;&lt;span&gt;primaryFocus&lt;/span&gt;&lt;span&gt;를 통해 현재 활성화된 포커스를 가져올 수 있다. 이 값을 사용해 포커스를 해제하면 키보드가 내려가는 원리다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;특징&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;전역적으로 동작한다&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 특정 &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;와 관계없이 현재 활성화된 포커스를 해제한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;간단하다&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 코드 한 줄로 손쉽게 사용할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;장점&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;어디서든 호출할 수 있다. &lt;/span&gt;&lt;span&gt;BuildContext&lt;/span&gt;&lt;span&gt;가 필요 없으니 전역적인 키보드 해제 동작을 구현할 때 간편하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;현재 포커스 상태를 직접 가져와 해제하므로 직관적이다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;단점&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;특정 위젯 트리 내에서만 동작을 제한하거나 세밀하게 제어하기 어렵다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;pre id=&quot;code_1736315008675&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FocusManager.instance.primaryFocus?.unfocus();&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;2. &lt;/span&gt;&lt;span&gt;FocusScope.of(context).unfocus();&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;이 방식은 특정 위젯 트리와 관련된 포커스를 해제할 때 사용된다. Flutter에서는 포커스를 관리하는 범위(scope)라는 개념이 있는데, &lt;/span&gt;&lt;span&gt;FocusScope&lt;/span&gt;&lt;span&gt;는 해당 범위 안의 모든 포커스를 관리한다. 이 범위 내의 포커스를 해제하면 키보드가 내려가는 방식이다.&lt;/span&gt;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;특징&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;로컬하게 동작한다&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 특정 위젯 트리의 &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt;를 기반으로 해당 트리 내의 포커스를 해제한다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;포커스 스코프를 제어한다&lt;/b&gt;&lt;/span&gt;&lt;span&gt;: 여러 위젯 간 포커스를 세밀하게 조작하거나 제어할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;장점&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;특정 범위의 포커스를 해제할 수 있어, 여러 입력 필드가 있는 화면에서도 필요한 부분만 제어할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;FocusScope&lt;/span&gt;&lt;span&gt;를 활용하면 포커스를 전환하거나 특정 조건에서만 동작하도록 구현할 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;단점&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;호출하려면 반드시 &lt;/span&gt;&lt;span&gt;BuildContext&lt;/span&gt;&lt;span&gt;가 필요하다.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;전역적으로 키보드를 숨기기에는 적합하지 않을 수 있다.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;사용 예시&lt;/span&gt;&lt;/h4&gt;
&lt;pre id=&quot;code_1736315029781&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;FocusScope.of(context).unfocus();&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;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;주요 차이점&lt;/span&gt;&lt;/h3&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: 134px;&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;height: 17px;&quot;&gt;특징&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;FocusManger.instance&lt;/td&gt;
&lt;td style=&quot;height: 17px;&quot;&gt;FocusScope.of(context)&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;&lt;b&gt;동작 범위&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;전역적으로 활성화된 포커스&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;특정 &lt;/span&gt;&lt;span&gt;context&lt;/span&gt;&lt;span&gt; 내의 포커스&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;&lt;b&gt;context&lt;/b&gt;&lt;/span&gt;&lt;span&gt;&lt;b&gt; 필요 여부&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;필요 없음&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;필요&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;&lt;b&gt;대상&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;현재 활성화된 포커스&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;해당 컨텍스트와 연결된 포커스&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;&lt;b&gt;주요 활용&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;전역 키보드 해제&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;특정 위젯 트리 내 포커스 해제&lt;/span&gt;&lt;/td&gt;
&lt;/tr&gt;
&lt;tr style=&quot;height: 20px;&quot;&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;&lt;b&gt;세부 제어 가능 여부&lt;/b&gt;&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;불가능&lt;/span&gt;&lt;/td&gt;
&lt;td style=&quot;height: 20px;&quot;&gt;&lt;span&gt;가능&lt;/span&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;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;1 3 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;언제 어떤 방식을 써야 할까?&lt;/span&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;FocusManager.instance.primaryFocus?.unfocus();&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;&lt;b&gt;전역적으로&lt;/b&gt;&lt;/span&gt;&lt;span&gt; 키보드를 내리고 싶을 때.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;특정 위젯이나 화면과 관계없이 활성화된 포커스를 빠르게 해제해야 할 때.&lt;/span&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;span&gt;FocusScope.of(context).unfocus();&lt;/span&gt;&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-spread=&quot;false&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;span&gt;특정 화면이나 위젯 트리 내에서만 포커스를 해제하고 싶을 때.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;여러 &lt;/span&gt;&lt;span&gt;TextField&lt;/span&gt;&lt;span&gt;가 있는 화면에서 로컬하게 포커스를 관리해야 할 때.&lt;/span&gt;&lt;/li&gt;
&lt;li&gt;&lt;span&gt;FocusScope&lt;/span&gt;&lt;span&gt;를 활용해 포커스를 전환하거나 세밀한 제어가 필요할 때.&lt;/span&gt;&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;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h3 data-pm-slice=&quot;1 1 []&quot; data-ke-size=&quot;size23&quot;&gt;&lt;span&gt;결론&lt;/span&gt;&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;두 방식 모두 키보드를 내리는 데 자주 사용되지만, 상황에 따라 적절한 방식을 선택하는 것이 중요하다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;전역적으로 간단히 포커스를 해제하려면 &lt;/span&gt;&lt;span&gt;FocusManager.instance.primaryFocus?.unfocus();&lt;/span&gt;&lt;span&gt;가 더 적합하다. &lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;반면, 특정 컨텍스트 내에서만 동작하도록 제한하려면 &lt;/span&gt;&lt;span&gt;FocusScope.of(context).unfocus();&lt;/span&gt;&lt;span&gt;를 사용하는 게 맞다.&lt;/span&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span&gt;Flutter 개발을 하면서 이 두 가지를 적절히 활용하면 키보드 제어가 훨씬 수월해질 것이다.&lt;/span&gt;&lt;/p&gt;</description>
      <category>  Flutter</category>
      <category>Flutter</category>
      <category>flutter keyboard hide</category>
      <category>Focus</category>
      <category>focusmanager</category>
      <category>focusscope</category>
      <category>keyboard</category>
      <category>플러터</category>
      <author>Dogfoot_JW</author>
      <guid isPermaLink="true">https://cajava.tistory.com/86</guid>
      <comments>https://cajava.tistory.com/86#entry86comment</comments>
      <pubDate>Wed, 8 Jan 2025 14:48:39 +0900</pubDate>
    </item>
    <item>
      <title>이벤트 소싱 (Event Sourcing) 은 무엇일까?</title>
      <link>https://cajava.tistory.com/85</link>
      <description>&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; 이벤트 소싱(Event Sourcing)&lt;/b&gt;은 시스템의 상태를 직접 저장하는 대신, 발생한 이벤트를 순서대로 기록하고 이 이벤트들을 기반으로 상태를 재구성하는 설계 패턴이다. 전통적인 CRUD 방식과 다르게 모든 상태 변경의 이력을 기록하고 이를 통해 현재 상태를 파악한다.&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style5&quot; /&gt;
&lt;h3 style=&quot;color: #000000; text-align: start;&quot; 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;&lt;b&gt;이벤트(Event)&lt;/b&gt;&lt;br /&gt;이벤트는 시스템에서 발생한 &lt;b&gt;상태 변경의 기록&lt;/b&gt;이다. 예를 들어, 은행 시스템에서는 계좌 개설, 입금, 출금 같은 것이 이벤트가 될 수 있다. 각 이벤트에는 발생 시간, 관련된 데이터, 변경 사항이 포함된다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 로그(Event Log)&lt;/b&gt;&lt;br /&gt;이벤트들은 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;시간 순서대로 기록되며 불변(Immutable)&lt;/b&gt;&lt;/span&gt;이다. 삭제되거나 수정되지 않고, 모든 상태 변경 이력을 보관한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;리플레이(Replaying)&lt;/b&gt;&lt;br /&gt;이벤트 로그를 처음부터 순차적으로 재생하여 시스템의 상태를 재구성하는 것을 의미한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;커맨드(Command)와 이벤트(Event)&lt;/b&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;는 시스템에 수행하라고 요청하는 작업이다. 예를 들어, &quot;송금하기&quot;가 커맨드다.&lt;/li&gt;
&lt;li&gt;커맨드가 성공적으로 처리되면 &lt;b&gt;이벤트&lt;/b&gt;가 생성된다. 예를 들어, &quot;송금 완료됨&quot;이라는 이벤트가 기록될 수 있다.&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;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;b&gt;예시로 살펴보는 이벤트 소싱&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;은행 계좌 시스템&lt;/h4&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;:&lt;/li&gt;
&lt;/ol&gt;
&lt;pre id=&quot;code_1734333118127&quot; class=&quot;csharp&quot; data-ke-language=&quot;csharp&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;1. 계좌 개설 (초기 잔액: 0)
2. 입금 (금액: 1000원)
3. 출금 (금액: 500원&lt;/code&gt;&lt;/pre&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;상태 재구성&lt;/b&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;b&gt;계좌 개설&lt;/b&gt; &amp;rarr; 잔액: 0&lt;/li&gt;
&lt;li&gt;&lt;b&gt;입금 (1000원)&lt;/b&gt; &amp;rarr; 잔액: 1000&lt;/li&gt;
&lt;li&gt;&lt;b&gt;출금 (500원)&lt;/b&gt; &amp;rarr; 잔액: 500&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 이벤트를 통해 잔액이 500원이 된 이유를 명확하게 알 수 있다.&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;b&gt;CRUD 방식과 이벤트 소싱의 비교&lt;/b&gt;&lt;/h3&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;1. CRUD 방식&lt;/h4&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CRUD 방식에서는 데이터베이스에 현재 상태만 저장한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;계좌 ID잔액&lt;/p&gt;
&lt;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;12345&lt;/td&gt;
&lt;td&gt;500&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;잔액이 왜 500원이 되었는지에 대한 이력이 남아 있지 않다.&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;h4 data-ke-size=&quot;size20&quot;&gt;2. 이벤트 소싱 방식&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;table style=&quot;border-collapse: collapse; width: 100%;&quot; border=&quot;1&quot; data-ke-align=&quot;alignLeft&quot;&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;2024-01-01&lt;/td&gt;
&lt;td&gt;계좌 개설&lt;/td&gt;
&lt;td&gt;초기 잔액: 0&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-01-05&lt;/td&gt;
&lt;td&gt;입금&lt;/td&gt;
&lt;td&gt;금액: 1000원&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;2024-01-10&lt;/td&gt;
&lt;td&gt;출금&lt;/td&gt;
&lt;td&gt;금액: 500원&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&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;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;b&gt;장점&lt;/b&gt;&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: decimal;&quot; data-ke-list-type=&quot;decimal&quot;&gt;
&lt;li&gt;&lt;b&gt;이력 관리와 감사(Audit)&lt;/b&gt;&lt;br /&gt;모든 변경 이력이 기록되므로 변경 원인을 명확하게 추적할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;상태 복원 및 재구성&lt;/b&gt;&lt;br /&gt;이벤트 로그를 리플레이하여 시스템 상태를 원하는 시점으로 되돌리거나 재구성할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;디버깅과 시간 여행&lt;/b&gt;&lt;br /&gt;과거 상태로 돌아가서 문제를 재현하고 디버깅할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;CQRS와의 시너지&lt;/b&gt;&lt;br /&gt;쓰기 모델과 읽기 모델을 분리하여 성능을 최적화할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;동시성 문제 해결&lt;/b&gt;&lt;br /&gt;동시성의 근본적인 문제인 &lt;b&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;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;b&gt;단점&lt;/b&gt;&lt;/h3&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;&lt;br /&gt;전통적인 CRUD보다 복잡하며, 이벤트를 처리하고 버전 관리하는 로직이 필요하다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;성능 문제&lt;/b&gt;&lt;br /&gt;이벤트가 많아지면 상태 재구성에 시간이 걸릴 수 있다. 이를 해결하기 위해 &lt;b&gt;스냅샷&lt;/b&gt;을 사용하기도 한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이벤트 수정의 어려움&lt;/b&gt;&lt;br /&gt;기록된 이벤트는 불변이기 때문에 잘못된 이벤트를 수정하기 어렵다. 대신 보정 이벤트를 추가해야 한다.&lt;/li&gt;
&lt;/ol&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;b&gt;활용 사례&lt;/b&gt;&lt;/h3&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;: 거래 이력을 관리해야 하는 은행이나 결제 시스템&lt;/li&gt;
&lt;li&gt;&lt;b&gt;이커머스&lt;/b&gt;: 주문 처리 및 배송 상태를 관리하는 시스템&lt;/li&gt;
&lt;li&gt;&lt;b&gt;게임 개발&lt;/b&gt;: 플레이어의 행동 이력을 기록하고 재생&lt;/li&gt;
&lt;li&gt;&lt;b&gt;분산 시스템&lt;/b&gt;: 서비스 간 데이터 일관성을 유지해야 하는 시스템&lt;/li&gt;
&lt;li&gt;&lt;b&gt;형상관리 시스템&lt;/b&gt;: 프로그램 소스코드와 변화에 대한 히스토리를 자세하게 기록하는 시스템. (Git, SVN 등)&lt;/li&gt;
&lt;/ol&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;</description>
      <category>  개발이론</category>
      <category>eventsourcing</category>
      <category>git</category>
      <category>동시성</category>
      <category>이벤트소싱</category>
      <author>Dogfoot_JW</author>
      <guid isPermaLink="true">https://cajava.tistory.com/85</guid>
      <comments>https://cajava.tistory.com/85#entry85comment</comments>
      <pubDate>Mon, 16 Dec 2024 16:23:25 +0900</pubDate>
    </item>
  </channel>
</rss>