<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>Hyeon's.log</title>
        <link>https://velog.io/</link>
        <description>레벨업하는 개발자</description>
        <lastBuildDate>Mon, 05 Aug 2024 12:29:26 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>Hyeon's.log</title>
            <url>https://velog.velcdn.com/images/hyn_053/profile/52a622e7-1452-4020-8314-e2b71e32eef5/image.png</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. Hyeon's.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/hyn_053" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[우아한테크캠프 7기 중간 회고]]></title>
            <link>https://velog.io/@hyn_053/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BA%A0%ED%94%84-7%EA%B8%B0-%EC%A4%91%EA%B0%84-%ED%9A%8C%EA%B3%A0</link>
            <guid>https://velog.io/@hyn_053/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BA%A0%ED%94%84-7%EA%B8%B0-%EC%A4%91%EA%B0%84-%ED%9A%8C%EA%B3%A0</guid>
            <pubDate>Mon, 05 Aug 2024 12:29:26 GMT</pubDate>
            <description><![CDATA[<p>시간이 정말 빠르게 흘러갔다. 체스 미션을 시작으로 WAS 미션, JSP 카페까지 3개의 개인 프로젝트를 완료하고 이제 팀 프로젝트만 남았다. 팀 프로젝트 시작 전에 지난 6주를 돌아보고자 한다.</p>
<h1 id="6주간의-여정">6주간의 여정</h1>
<h2 id="무엇을-배웠나">무엇을 배웠나?</h2>
<p>미션을 하며 아래와 같은 기술을 배웠다.</p>
<ul>
<li>자바 체스 - 객체지향, …</li>
<li>WAS - 소켓, 멀티쓰레드, 리플렉션, 자바의 구조, 객체지향, …</li>
<li>JSP - Servlet, …</li>
<li>그 외 - 쉘 스크립트, AWS, DB, OS, …</li>
</ul>
<p>그러나 이 곳에서 가장 얻을 수 있었던 것은 기술적인 성장 뿐만 아니라 내면적인 성장이였다.</p>
<h3 id="학습의-태도">학습의 태도</h3>
<p>여기서 가장 크게 배웠던 건 학습의 태도였다. 예전에는 어려움에 부딪히면 바로 검색부터 했고, 블로그나 AI의 답변에 의존했다. 하지만 그 정보들이 부정확한 경우가 많았고, 영어 때문에 공식 문서나 해외 자료를 꺼렸다. </p>
<p>하지만 이곳에서 공식 문서의 중요성을 깨달았다. 처음 미션엔 평소에 하던 것 처럼 했는데 다른 캠퍼분들은 공식 문서를 읽기 시작하셨다. 공식 문서엔 블로그와 AI로는 알 수 없었던 내용이 가득했다. 다른 캠퍼분들은 공식 문서를 읽고 이 기술의 개요와 이 기술의 활용법, 그리고 조심해야할 사항들까지 이를 공유해주셨다.</p>
<p>다른 캠퍼들이 공식 문서를 읽고 얻은 인사이트를 공유하는 걸 보며, 저도 차츰 공식 문서를 읽기 시작했다. 처음엔 어려웠지만 점점 익숙해졌고, 이런 작은 변화가 앞으로의 성장에 큰 도움이 될 거라 믿는다.</p>
<h3 id="함께-성장하는-방법">함께 성장하는 방법</h3>
<p>이 곳에서 얻은 또 다른 큰 변화는 함께의 가치를 다시 한번 깨달은 것이다. 뛰어난 동료들 속에서 때로는 초라해 보이기도 했지만, 캠퍼분들의 열정과 깊이 있는 접근법에서 많은 인사이트를 얻었다.</p>
<p>그룹 리뷰 시간에 모여 서로가 배운 점, 서로의 코드에서 자신의 경험을 바탕으로 문제를 해결할 방법에 대해서 토론하는 것은 정말 많이 도움이 되는 것 같다.</p>
<p>심지어 내가 에러의 고통으로 머리를 감싸고 있으면 보고 계시던 분들이 삼삼오오 모여 에러를 해결해주시기도 하고 내가 몰랐던 내용에 대해서 알려주시곤 한다.</p>
<p>어려움에 처했을 때 캠퍼분들이 자발적으로 도와주는 모습을 보며, 나 역시 그들에게 어떻게든 도움이 되고 싶다는 마음이 생겼다. </p>
<p>또한 다들 캠프 활동 외에도 개인 공부나 프로젝트까지 바쁘게 사시면서 엄청 열심히 하셔서 나 또한 열심히 하게 되는 것 같다. </p>
<p>이런 상호작용이 바로 &#39;함께&#39;의 힘이 아닐까?</p>
<h1 id="앞으로">앞으로..</h1>
<h2 id="기록의-생활화">기록의 생활화</h2>
<p>기록을 생활화해야겠다. 네트워킹 데이에서 많이 느꼈다. 가끔 코드를 짤 때 분명 내가 어떠한 의사결정을 통해서 코드를 그렇게 짜는 것이 좋겠다고 생각을 했고 그렇게 코드를 짜고 나서 가끔 왜 그렇게 됐는지 까먹을 때도 있었다. 또 모두가 그렇게 하길래 따라 한 적도 있었다. </p>
<p>이렇게 까먹고 내가 설계한 방향과는 다르게 코드의 양상이 흘러가기도 하고 이러한 무책임한 결정들이 쌓였을 때 코드가 엉망이 되는 경우가 많았다.</p>
<p>이번 네트워킹 데이 때 시니어 분에게 면접에서 어떤 질문을 하시는지 물었을 때 ‘어떤 결정을 했을 때 그 이유에 대해 묻는다.’라고 하셨다. 프로젝트를 진행하고 코드를 짜며 그렇게 하기까지 어떤 생각과 사고를 거쳤고, 그렇게 하기로 한 이유에 대해서 말할 수만 있다면 충분하다고 하셨다.</p>
<p>나는 매번 프로젝트를 할 때 문서화를 처음엔 활발히 하다가 나중에는 미루고 결국엔 하지 않는 나쁜 습관이 있었는데 이번 기회에 그러한 습관을 깨보고 싶다. </p>
<p>지금 하는 방법보다 더 나은 다른 방법이 없는가? 우리가 선택한 실행 관례가 우리 프로젝트에 적합한가? 가치는 무엇인가? 다른 것을 시도해 볼 시점인가? 왜 내가 이러한 결정을 해야만 했는가? 와 같은 다양한 방면으로 기록하고 이러한 기록을 바탕으로 프로젝트를 진행하기로 마음먹었다.</p>
<p>이번 프로젝트 기간 동안 꼭 지키고 싶다.</p>
<h2 id="지속가능한-성장">지속가능한 성장</h2>
<p>저번 네트워킹 데이에서 나를 관통하는 말이었다. </p>
<p>‘나는 과연 지속 가능한 성장을 하고 있는가?’ </p>
<p>지난 취준 기간 동안 나는 무조건으로 열심히 했다. 열심히 하는 내가 뿌듯해 더 열심히 하려고 했던 것 같다. 하지만 계속되는 좌절에 번아웃까지 오게 되었고 그 와중에 운이 좋아 우테캠으로 오게 되었다.</p>
<p>우테캠이라는 조직에 속해 캠퍼 분들과 함께하며 괜찮아졌지만 캠프가 지나갈 수 록 다시 혼자가 되어 나아가야 할 길에 대해 고민하던 중 이 질문이 나를 관통했다.</p>
<p>현재는 지속 가능한 성장이라는 숙제를 안고 고민하고 있다. 단순히 열심히 하는 것을 넘어, 장기적인 관점에서 어떻게 성장해 나갈지 고민하고 있다. 캠프가 끝날 때에는 꼭 이 문제에 대해 해답을 찾았으면 좋겠다. </p>
<h1 id="마무리-하며">마무리 하며</h1>
<p>이번 우테캠 세미나에서 발표한 소프트웨어 장인이라는 책에 대해 발표하게 되었는데 아래는 책에서 우리가 하는 일이 얼마나 중요하고 가치 있는지 알려주는 구절이다.</p>
<blockquote>
<p>공익사업에 성공에도 기여하고 자연재해와 같은 것에 정부에서 자원봉사자들의 도움을 필요로 하는 사람들을 찾을 수 있도록 하거나 자연재해로부터의 피해를 최소화하는 데도 소프트웨어의 영향이 미친다.
…
어떻게 우리의 일을 자랑스럽게 생각하지 않을 수 있을까?
어떻게 우리의 일을 그저 출퇴근하는 생계수단으로만 치부할 수 있나?
이토록 중요하고, 어떤 때는 생명에 영향을 미치는 그런 일을, 어떻게 소중히 여기지 않을 수 있나?</p>
</blockquote>
<ul>
<li>소프트웨어 장인 309p<blockquote>
</blockquote>
</li>
</ul>
<p>사실 이 책은 내가 개발을 시작하게 된 계기가 된 책이다. 하지만 시간이 지나면서 이런 초심을 잊고 살았던 것 같다. 매일 컴퓨터 앞에 앉아 코딩만 하다 보니, 내가 하는 일의 의미를 잊어버렸던 것이다.</p>
<p>이번에 다시 이 구절을 읽으면서, 내가 하는 일이 얼마나 중요하고 가치 있는지 새삼 깨달았다.  단순히 코드를 작성하는 게 아니라, 누군가의 삶에 영향을 미칠 수 있는 일을 하고 있다는 것이다.</p>
<p>앞으로 남은 캠프 기간 동안, 이런 마음가짐을 잊지 않고, 매일 코딩할 때마다 &quot;이게 누군가에게 어떤 도움이 될까?&quot;라고 생각하는 개발자가 되고 싶다.</p>
<p>남은 기간 동안 더 열심히, 그리고 더 의미 있게 공부하고 싶다. 내가 하는 일에 자부심을 갖고, 정말 좋은 개발자가 되기 위해 노력해야겠다. </p>
<p>남은 기간도 힘내보자!</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Jar에서 Resource 가져오기]]></title>
            <link>https://velog.io/@hyn_053/Jar%EC%97%90%EC%84%9C-Resource-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0</link>
            <guid>https://velog.io/@hyn_053/Jar%EC%97%90%EC%84%9C-Resource-%EA%B0%80%EC%A0%B8%EC%98%A4%EA%B8%B0</guid>
            <pubDate>Sun, 07 Jul 2024 09:40:10 GMT</pubDate>
            <description><![CDATA[<h2 id="문제-상황">문제 상황</h2>
<p>우테캠 WAS 미션 구현 중 정적 리소스 파일을 HttpResponse에 반환해야 하는 일이 있었다.</p>
<p>정적 리소스를 상대 경로로 불러와서 사용했으나, 어째서인지 AWS에서 Jar 파일로 실행 시 Static 파일을 찾을 수 없다는 메시지가 떴다. 분명히 Intellij 환경에선 잘 실행이 되었는데 무슨 문제일까?</p>
<p>아래는 문제의 코드이다.</p>
<pre><code class="language-java">
private static final String BASE_DIRECTORY = &quot;src/main/resources/static&quot;;

...

private static File getFile(final String path) {
      String staticPath = BASE_DIRECTORY + path;

      File file = new File(staticPath);

      if (file.isDirectory()) {
          file = new File(staticPath + &quot;/index.html&quot;);
      }

      if (!file.exists()) {
          throw new IllegalArgumentException(&quot;Not found static file&quot;);
      }

      return file;
}</code></pre>
<p>문제를 파악하기 위해 Jar란 무엇인지 알아보자.</p>
<h2 id="jar">Jar</h2>
<blockquote>
<p><strong>Jar</strong>는 여러개의 자바 클래스 파일과, 클래스들이 이용하는 관련 리소스(텍스트, 그림 등) 및 메타데이터를 하나의 파일로 모아서 자바 플랫폼에 응용 소프트웨어나 라이브러리를 배포하기 위한 소프트웨어 패키지 파일 포맷이다.
<strong>Jar 파일</strong>은 자바 런타임이 효율적으로 애플리케이션을 배포할 수 있는 수단으로 설계되었는데, 자바 애플리케이션을 구성하는 클래스와 관련 리소스들을 단일 파일로 묶어 압축된 형태로 한 차례의 요청으로 애플리케이션 전체를 다운로드 할 수 있게 해준다.
출처 - <a href="https://ko.wikipedia.org/wiki/JAR_(%ED%8C%8C%EC%9D%BC_%ED%8F%AC%EB%A7%B7)">위키피디아</a></p>
</blockquote>
<p>위의 글을 보면 Jar는 하나의 압축 파일인데 왜 절대 경로, 상대 경로로 파일을 가져올 수 없는 것일까?</p>
<h3 id="상대-경로로-가져-올-경우">상대 경로로 가져 올 경우</h3>
<pre><code class="language-java">log.debug(&quot;path = {}&quot;, Paths.get(&quot;&quot;).toAbsolutePath());

IDE 내에서 경로를 탐색 했을 때 
Static path = /Users/woowatech14/Desktop/java-was
Static file getAbsolutePath: /Users/woowatech14/Desktop/java-was/src/main/resources/static

프로젝트 내 build/libs 에서 jar를 시작한 경우 - 에러 !
Static path = /Users/woowatech14/Desktop/java-was/build/libs
Static file getAbsolutePath: /Users/woowatech14/Desktop/java-was/build/libs/src/main/resources/static

Jar 파일을 바탕화면에서 실행 했을 때 - 에러 !
path = /Users/woowatech14/Desktop
Static file getAbsolutePath: /Users/woowatech14/Desktop/src/main/resources/static</code></pre>
<h3 id="절대-경로로-가져올-경우">절대 경로로 가져올 경우</h3>
<pre><code class="language-java"> private static final String ROOT = &quot;/static&quot;;

 private static File getFile(final String path) {
    // 현재 클래스의 위치에서 ROOT 경로를 기준으로 절대 경로를 구합니다.
    Path absolutePath = Path.of(ROOT).toAbsolutePath();

    // 구한 절대 경로와 요청된 path를 결합하여 리소스의 URL을 얻습니다.
    URL url = StaticHandler.class.getResource(ROOT + path);

    // URL에서 파일 경로를 추출합니다.
    String fileResource = url.getFile();

    // 추출한 파일 경로로 File 객체를 생성합니다.
    File file = new File(fileResource);

      if (file.isDirectory()) {
          file = new File(staticPath + &quot;/index.html&quot;);
      }

      if (!file.exists()) {
          throw new IllegalArgumentException(&quot;Not found static file&quot;);
      }

      return file;
}

IDE로 실행할 경우  
Static path = /Users/woowatech14/Desktop/java-was
Static file getAbsolutePath: /Users/woowatech14/Desktop/java-was/out/production/resources/static

build/libs에서 실행할 경우 - 에러 !
Static path = /Users/woowatech14/Desktop/java-was/build/libs
Static file getAbsolutePath: file:/Users/woowatech14/Desktop/java-was/build/libs/java-was-1.0-SNAPSHOT.jar!/static

바탕화면에서 jar를 실행할 경우 - 에러 !
Static path = /Users/woowatech14/Desktop
Static file getAbsolutePath: file:/Users/woowatech14/Desktop/java-was-1.0-SNAPSHOT.jar!/static</code></pre>
<p>둘 다 jar로 실행할 경우 resource 파일을 가져오지 못했다.
그러나 로그를 자세히 보면 절대 경로를 가져오는 코드로 Jar 파일로 실행하는 경우 <code>file:/Users/woowatech14/Desktop/java-was-1.0-SNAPSHOT.jar!/static</code></p>
<p>절대 경로가 반복되면서 jar! 라는 새로운 경로가 추가되었다. 바로 Jar에 대한 특별한 경로라고 한다.</p>
<p>그런데 왜 static 파일을 가져올 수 없는 것일까?</p>
<p>위의 Jar 파일의 정의를 다시 보자</p>
<blockquote>
<p>… 자바 애플리케이션을 구성하는 클래스와 관련 리소스들을 단일 파일로 묶어 <strong>압축된 형태</strong> …</p>
</blockquote>
<p>여기서 주목해야 할 건 압축된 파일이라는 것이다. 자바에서의 File 객체는 파일 시스템의 파일이나 디렉토리를 추상화한 것인데 File 객체는 실제 파일 시스템에 존재하는 파일이나 디렉토리에만 작동한다.</p>
<p>그러나 Jar 파일 내부의 경로는 실제 파일 시스템에 존재하지 않아서 File 객체로는 접근이 불가능하고 압축된 형태를 읽을 수 있는 특별한 방법을 사용해야 한다. 이를 보여주는 예시가 <code>java-was-1.0-SNAPSHOT.jar!/static</code> 이 부분이다. 실제 파일 시스템엔 !가 포함된 경로는 존재하지 않고 <code>java-was-1.0-SNAPSHOT.jar</code> 여기서 끝이 나버린다.</p>
<p>따라서 압축된 파일을 위해 자바에선 다양한 패키지를 제공하는데 <a href="https://docs.oracle.com/javase/8/docs/api/java/util/zip/package-summary.html#package.description">java.utiil.zip</a>부터 이를 상속한 <a href="https://docs.oracle.com/javase/8/docs/api/java/util/jar/package-summary.html#package.description">java.util.jar</a>까지 제공한다.</p>
<h2 id="jar-안-압축된-파일을-가져오는-방법">Jar 안 압축된 파일을 가져오는 방법</h2>
<p>결론까지 많은 지식을 보았다. 이제 Jar 안의 파일을 어떻게 가져올 것인가에 대해 얘기해볼 차례이다.</p>
<p>내가 선택한 방법은 클래스 로더를 사용하여 리소스를 읽는 것이다.</p>
<h3 id="클래스-로더를-사용하여-리소스-가져오기">클래스 로더를 사용하여 리소스 가져오기</h3>
<pre><code class="language-java">public static byte[] getStaticFiles(final String path) throws IOException {
     ClassLoader classLoader = StaticHandler.class.getClassLoader();
     String resourcePath = &quot;static&quot; + path;

       try (InputStream resourceAsStream = classLoader.getResourceAsStream(resourcePath)) {
          if (resourceAsStream == null) {
              throw new IllegalArgumentException(&quot;Static file not found&quot;);
          }

          return resourceAsStream.readAllBytes();
      }
}</code></pre>
<p>코드를 한줄 한줄 설명해보면 ClassLoader를 이용해서 애플리케이션 클래스 로더로 내가 작성한 클래스 패스에 있는 리소스를 로드하고 나의 클래스 패스에서 resourcePath를 검색한다.
<a href="https://inkyu-yoon.github.io/docs/Language/Java/ClassLoader">클래스 로더의 대한 자세한 설명은 여기로 가도록 하자.</a>
그 후 <code>classLoader.getResourceAsStream(resourcePath);</code> 이렇게 Stream으로 가져오는데 왜 <code>getResource()</code>로 가져오게 되면 URL로 반환하기 때문에 jar 파일의 !와 함께 표시되어 에러가 발생한다.</p>
<p>따라서 <code>getResourceAsStream()</code>로 읽어서 byte[]로 변환한다.</p>
<p>왜 이렇게 사용하는지는 아래를 보면 이해 할 수 있다.</p>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/579db222-ac23-4a11-9d28-9eb53dfb422b/image.png" alt=""></p>
<p><code>getResourceAsStream</code> 코드를 따라 가면 <code>openStream()</code>이 있는데 이를 내부적으로 추적하면 Openconnection을 다형적으로 처리한다.</p>
<p>이때 handler가 URL을 다형적으로 작동해 <code>JarURLConnection</code>으로 만들어서 URL이 <code>jar:file:</code>로 시작해서 파일에서도 jar에서도 잘 동작할 수 있다.</p>
<p>사실 <code>getResource()</code>를 사용하더라도 <code>openStream()</code>을 사용하면 jar에서도 잘 동작한다. 그러나 <code>getResource()</code>로 URL을 가져와 <code>File</code> 객체를 생성하면 <code>JarFile</code>로 다형적으로 작동되지 않아서 IDE에서만 되고 <code>InputStream</code>으로 사용하면 다형성이 아래와 같이 <code>openStream()</code>을 타면서 적용된다.</p>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/75de5472-7781-4be8-878b-3f5626e1bc40/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/ebf20952-7cce-4d64-874e-1d3caa04df6e/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/936527ed-a9a6-4c23-a397-5c24c32ee0ac/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/c45b1d22-aead-4112-bf12-dd5a7a3782e0/image.png" alt=""></p>
<ul>
<li>이런식으로 <code>JarURLInputStream</code>을 가져온다.</li>
</ul>
<h2 id="결론">결론</h2>
<p>결론은 Jar에 대한 이해 부족이 만들어진 에러였다. 매번 인텔리제이 또는 스프링부트의 도움으로 Jar를 실행했는데 이런 경우가 있는지 처음 알게 되었다. 이러한 툴에 의해서 매번 동작 원리도 모른체 썼던 것 같은데 이번 기회에 알게 되었으니,, 기본을 항상 잘 다지도록 하자.</p>
<p>이번 글을 쓰면서 Docs와 디버그를 활용하면서 한줄 한줄 들어갔을 때 굉장히 다형적인 코드가 신기했다. 또한 인텔리제이에서 Jar 파일을 디버그를 하거나 원격으로 디버그를 하는 기능도 알게 되었는데 잘 활용할 거 같다 !</p>
<ul>
<li>출처<ul>
<li><a href="https://homoefficio.github.io/2020/07/21/IDE-%EC%97%90%EC%84%9C%EB%8A%94-%EB%90%98%EB%8A%94%EB%8D%B0-jar-%EC%97%90%EC%84%9C%EB%8A%94-%EC%95%88-%EB%8F%BC%EC%9A%94-Java-Resource/#about">https://homoefficio.github.io/2020/07/21/IDE-에서는-되는데-jar-에서는-안-돼요-Java-Resource/#about</a></li>
<li><a href="https://docs.oracle.com/javase/8/docs/">https://docs.oracle.com/javase/8/docs/</a></li>
<li><a href="https://inkyu-yoon.github.io/docs/Language/Java/ClassLoader">https://inkyu-yoon.github.io/docs/Language/Java/ClassLoader</a></li>
</ul>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[우아한테크캠프 7기 합격 후기]]></title>
            <link>https://velog.io/@hyn_053/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BA%A0%ED%94%84-7%EA%B8%B0-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@hyn_053/%EC%9A%B0%EC%95%84%ED%95%9C%ED%85%8C%ED%81%AC%EC%BA%A0%ED%94%84-7%EA%B8%B0-%ED%95%A9%EA%B2%A9-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Fri, 14 Jun 2024 15:53:10 GMT</pubDate>
            <description><![CDATA[<p><img src="https://velog.velcdn.com/images/hyn_053/post/4c191b6b-ae5e-48d4-a6d7-46f8c3177aea/image.png" alt=""></p>
<h1 id="지원-과정43--410-접수">지원 과정(4/3 ~ 4/10 접수)</h1>
<p> 우아한테크캠프는 우아한형제들에서 하는 10주 간의 교육형 인턴이다.</p>
<p> 사실 시작은 우아한테크코스의 탈락이였다. 나는 우테코를 2번 지원했었는데 작년은 정말 열심히 했던터라 탈락했을 땐 정말 힘들었다. 그렇게 생각과는 빠르게 취업 시장에 본격적으로 뛰어들게 되었고 운이 좋게도 몇 번의 면접을 볼 수 있었다. </p>
<p>그러나 아쉽게도 합격하지 못했고 기간이 점점 길어질 수록 마음의 여유가 없어졌다. 그러던 중 잊고 있었던 우아한테크캠프의 모집 소식을 듣게 되었고 다음과 같은 이유로 지원하게 되었다.</p>
<ol>
<li><p><strong>좋은 멘토, 열정적인 동료</strong></p>
<p> 아무래도 프로세스 자체가 길고 거의 채용 절차와 비슷한 만큼 과정에 열정적인 동료들이 많을 것 같았다. 또한 점점 개발에 대한 열정을 잃어가고 있는 와중에 내가 개발자가 되고 싶었던 이유들을 다시 리마인드 할 수 있는 좋은 멘토분들이 있을 것 같았다.</p>
</li>
<li><p><strong>우아한형제들 채용 기회</strong></p>
<p> 내 가치관에 크게 영향을 끼친 우아한형제들에 들어가고 싶었다. 아무래도 우아한형제들 신입 개발자를 우아한테크코스와 우아한테크캠프로 뽑는다는 말을 들어왔어서 이번 기회를 놓치고 싶지 않았다.</p>
</li>
</ol>
<h1 id="1차-코딩-테스트413-417">1차 코딩 테스트(4/13, 4/17)</h1>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/545ccf88-d65f-417d-8439-2ddcd6806f0a/image.png" alt=""></p>
<p>기억 상으론 코딩 테스트 3문제, 객관식 5문제 였던 것 같다.</p>
<p> 코딩 테스트는 이전에 기업 및 소프트웨어 마에스트로로 쳐봤을 때 시간복잡도를 최적화하는 문제가 많아서 이를 줄여주는 알고리즘에 대해 중점적으로 봤는데 개인적으로는 다양한 알고리즘을 부분별로 풀어보고 입력에 대한 시간 복잡도를 계산해보는 연습이 도움이 많이 됐다.</p>
<p> 객관식은 단순히 CS를 묻는 질문이였던 것 같다.</p>
<p>1차 코딩 테스트는 8문제 전부 풀고 제출했다.</p>
<p>(프로그래머스 고득점 Kit, 프로그래머스 코딩테스트 책 정도 풀어보면 좋을듯 하다!)</p>
<h1 id="2차-과제-테스트420-58">2차 과제 테스트(4/20, 5/8)</h1>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/3f0f3356-af03-410c-b9a7-b503c9094451/image.png" alt=""></p>
<p> 2차 과제 테스트는 4시간으로 Java와 Spring Boot를 바탕으로 CRUD API를 구현하는 과정이였다.</p>
<p> 인증 및 인가와 같은 내용이 나왔고 JWT와 Spring Security를 사용해봤지만 내가 구현했던 방식과는 조금 달라 겨우 구현했던 것 같다.</p>
<p> 심지어 테스트 중에 주어진 과제 문서와 다른 테스트 코드 및 오타 때문에 옳게 했음에도 테스트가 통과하지 않아 많이 당황하며 고생했다.</p>
<p> 결국 테스트 케이스를 2개 통과하고 Create 코드까진 만들었지만 인증, 인가 코드가 이상하게 작동해서 테스트 통과는 하지 못했다.</p>
<p> 합격은 어렵겠구나 생각했지만 운이 좋게도 합격 메일을 받을 수 있었다.</p>
<h1 id="서류-제출--면접514-527--65">서류 제출 / 면접(5/14, 5/27 ~ 6/5)</h1>
<p>2차 과제 테스트 합격 후 서류를 제출하고 2주 뒤 면접을 보게 되었다.</p>
<p> 서류가 10000자가 최대 글자 수 였는데 다 채우기 보다는 담백하게 채웠다. 내가 개발자가 되기로 결심했던 과정, 개발자가 되기 위해서 어떤 과정을 거쳤는지, 이를 바탕으로 나의 가치관이 어떻게 바뀌게 되었는지에 대해 1500~3000자 정도로 채웠던 것 같다. 과거의 경험을 차근차근 돌아보는 과정을 솔직하게 담는 경험이 다시끔 나를 돌아 볼 수 있게 되어서 너무 좋았다.</p>
<p> 면접은 운이 좋게도 이전 면접 경험을 바탕으로 준비하는데 수월했다. 지원서를 바탕으로 인성 질문을 준비했고 프로젝트에 사용했던 기술과 Java, Spring Boot, MySQL과 같은 내 기술 스택에 대해서 전부 낱낱이 뜯어서 정리하며 준비했다. 동작 과정을 하나하나 뜯어보며 이에 대해 찾아 정리하면서 많이 성장해서 이 후 다른 면접 과정에서도 크게 도움이 되었다.</p>
<p> 면접은 비대면으로 진행되었는데 긴장을 풀 수 있게 물을 준비해서 마시면서 진행할 수 있도록 배려해주셔서 많이 긴장이 풀려 준비한대로 잘 얘기했다. 개인적으로는 이때까지 본 면접 중에 가장 좋은 경험이였던 것 같다.</p>
<p> 질문은 깊은 기술적 지식보단 거의 인성 질문이였는데 기술적인 부분에서 언어의 매우 기초와 관련된 질문을 하셔서 당황해서 대답을 횡설수설 했고 프로젝트에 대한 구성을 묻기도 하셔서 그 부분에 있어서 조금 답변이 만족스럽지 못했기 때문에 엄청난 경쟁률에 합격하지 못하더라도 좋은 경험이였다고 생각하기로 했다.</p>
<p> (개인적으로 면접을 준비하면서 사용하는 기술 스택 및 CS에 대해 유튜브 및 좋은 기술 블로그, 공식 문서를 바탕으로 프로젝트 코드 및 아키텍처에 대해 하나하나 설명해보는 것을 추천한다 !)</p>
<h1 id="합격614">합격(6/14)</h1>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/90a73198-9c1d-4c08-ac2f-d4b6a6b2f592/image.png" alt=""></p>
<p> 합격 당일 5시 발표로 공지했기에 외출을 하다가 차 안에서 우아한형제들 측에서 온 전화를 받고 알게 되었다. 공지 시간보다 빠르게 왔기에 전화를 받고 정말 어안이 벙벙했다. 전화가 끝난 후 확인해보니 메일이 와있었다.</p>
<p> 졸업 후, 짧을 수도 있지만 나에겐 너무 긴 시간이였다. 나를 채찍질 하며 달려온 시간이였고 그 시간이 힘들지 않았다고 하면 거짓말이다. 보상 없이 실패만 맛보며 좋은 경험이라고 생각하기엔 질릴 때쯤 보상을 받게 된 것 같다.</p>
<p> 펄어비스 여름 인턴십 최종 결과를 기다리고 있었는데 다행이게도 우테캠에 합격해서 우테캠을 하기로 했다.</p>
<p> 하지만 이제 첫 발을 딛은 것이고 이제는 우아한테크캠프라는 부스트를 달고 성장할 수 있도록 해야겠다. 개발할 날들이 벌써 기대된다😄</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿼리 병목 확인을 위한 쿼리 로깅 하기]]></title>
            <link>https://velog.io/@hyn_053/%EC%BF%BC%EB%A6%AC-%EB%B3%91%EB%AA%A9-%ED%99%95%EC%9D%B8%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%BF%BC%EB%A6%AC-%EB%A1%9C%EA%B9%85-%ED%95%98%EA%B8%B0</link>
            <guid>https://velog.io/@hyn_053/%EC%BF%BC%EB%A6%AC-%EB%B3%91%EB%AA%A9-%ED%99%95%EC%9D%B8%EC%9D%84-%EC%9C%84%ED%95%9C-%EC%BF%BC%EB%A6%AC-%EB%A1%9C%EA%B9%85-%ED%95%98%EA%B8%B0</guid>
            <pubDate>Fri, 10 May 2024 17:37:58 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-상황">문제 상황</h1>
<p>요즘 다시 지구미를 디벨롭 해나가는 상황에서 성능에 대한 관심이 높아져서 쿼리가 몇 번 나가는지 체크하고 실제 WAS에서 요청이 나가기 까지 얼마나 걸리는지 측정하고자 했다.</p>
<p>이전에는 SQL 로그와 포스트맨을 통해서 쿼리를 세고, 실행 시간을 측정했는데 보다 효율적인 방법을 생각하게 되었다.</p>
<h1 id="hibernate-statementinspector">Hibernate StatementInspector</h1>
<p>하이버네이트는 <code>StatementInspector</code>를 제공한다. 이것을 이용하면 JPA가 생성하는 SQL문을 검사하고 대신 사용할 다른 SQL을 반환할 수도 있다.</p>
<p><a href="https://docs.jboss.org/hibernate/orm/6.2/javadocs/org/hibernate/resource/jdbc/spi/StatementInspector.html">공식 문서</a>에 따르면 <code>null</code>을 반환하면 전달된 것과 동일한 SQL을 반환하는 것으로 해석된다고 한다.</p>
<p>이 인터페이스를 활용해서 쿼리를 셀 수 있다.</p>
<p>인터페이스는 <code>inspect</code>라는 메서드를 가지는데 하이버네이트가 생성한 SQL문을 가져와 조작할 수 있게 하고 반환하는 메서드이다.</p>
<h2 id="구현-코드">구현 코드</h2>
<pre><code class="language-java">@Component
@RequiredArgsConstructor
public class QueryInspector implements StatementInspector {

    private final QueryCounter queryCounter;

    @Override
    public String inspect(String sql) {
        if (isInRequestScope()) {
            queryCounter.increase();
        }

        return sql;
    }

    private boolean isInRequestScope() {
        return Objects.nonNull(RequestContextHolder.getRequestAttributes());
    }

    public int getQueryCount() {
        return queryCounter.getCount();
    }
}

@Component
@RequestScope
public class QueryCounter {

    private int count = 0;

    public void increase() {
        count++;
    }

    public int getCount() {
        return count;
    }
}</code></pre>
<h3 id="querycounter">QueryCounter</h3>
<ul>
<li><code>RequestScope</code>는 HTTP 요청이 들어오면 나갈 때 까지 유지되는 빈 스코프이다. 이것을 사용해서 HTTP request 별로 쿼리 실행을 센다.</li>
<li><code>StatementInspector</code> 구현체에 <code>@RequestScope</code>를 하면 안될까 생각할 수 있는데, <code>StatementInspector</code>는 <code>HibernateProperty</code>로 하나만 등록해서 공유해서 사용하기 때문에 안된다.</li>
<li>또한 Thread-Safe 하게 구현해야 한다.</li>
</ul>
<h3 id="requestcontextholder">RequestContextHolder</h3>
<ul>
<li><code>RequestContextHolder</code>는 스프링 웹 애플리케이션에서 현재 요청과 관련된 정보를 저장하고 있는 스태틱(정적) 메소드와 필드를 제공하는 유틸리티 클래스이다. <a href="https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/web/context/request/RequestContextHolder.html">공식문서</a></li>
<li>이 클래스는 현재 실행 중인 스레드(Thread)에 바인딩된 <code>RequestAttributes</code> 객체에 접근할 수 있게 해준다.</li>
<li><code>RequestContextHolder.getRequestAttributes()</code>이 true면 HTTP 요청 주기 안에 있다는 것이다.</li>
<li>따라서 Hibernate가 SQL을 보낼 때 QueryInspector를 거치게 되어 있는데 이 때 이 요청이 HTTP 요청인지 확인 후에 쿼리를 센다.</li>
<li>예를 들면 테스트 코드를 실행할 때 실행되는 SQL은 체크하지 않는다.</li>
</ul>
<p>근데 StatementInspector를 구현만 한다고 사용하는 것이 아니다.
구성 속성 &quot;hibernate.session_factory.statement_inspector&quot;를 통해 구현을 지정해야만 동작한다.
이는 yml, properties로 옵션을 사용해서 지정할 수도 있는데 <code>QueryCount</code>라는 우리가 직접 만든 빈을 사용해야 해서 빈이 초기화 된 후에 사용해야 한다.</p>
<h2 id="hibernatepropertiescustomizer">HibernatePropertiesCustomizer</h2>
<p>해결 방법으로는 다양한 방법이 있지만 Spring에서 지원하는 HibernatePropertiesCustomizer를 이용했다.</p>
<p><a href="https://docs.spring.io/spring-boot/docs/current/api/org/springframework/boot/autoconfigure/orm/jpa/HibernatePropertiesCustomizer.html">공식 문서</a>에는 다음과 같이 설명 되어 있다.</p>
<blockquote>
<p>Callback interface that can be implemented by beans wishing to customize the Hibernate properties before it is used by an auto-configured <code>EntityManagerFactory</code>.</p>
</blockquote>
<p>해석해보면 자동 구성된 EntityManagerFactory에서 사용하기 전에 Hibernate 속성을 사용자 지정하려는 Bean에서 구현할 수 있는 콜백 인터페이스이다.</p>
<p>이것을 이용해서 스프링 빈이 초기화 되고 난 후 설정을 등록하도록 구현했다.</p>
<p>함수형 인터페이스이므로 다음과 같이 사용했다.</p>
<pre><code class="language-java">@Configuration
@RequiredArgsConstructor
public class HibernateConfig {

    private final QueryInspector queryInspector;

    @Bean
    public HibernatePropertiesCustomizer hibernatePropertyConfig() {
        return hibernateProperties -&gt;
                hibernateProperties.put(AvailableSettings.STATEMENT_INSPECTOR, queryInspector);
    }
}</code></pre>
<h1 id="interceptor로-등록">Interceptor로 등록</h1>
<pre><code class="language-java">@Configuration
@RequiredArgsConstructor
public class InterceptorConfig implements WebMvcConfigurer {

    private final PerformanceLoggingInterceptor performanceLogging;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(performanceLogging);
    }
}</code></pre>
<p>위 쿼리가 날아간 횟수를 측정하는 코드 말고도 실행 시간을 쓰레드 로컬을 활용해 측정하는 클래스도 작성해서 두 클래스를 하나 묶어 <code>PerformanceLoggingInterceptor</code>를 만들어서 <code>Interceptor</code>로 등록했다.</p>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/14ac3915-2fa2-4167-b4a8-336c792aeb8b/image.png" alt=""></p>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/b98c930b-8b6b-4e47-bd5f-eab898397ff1/image.png" alt=""></p>
<p>잘 작동하는 모습이다.</p>
<h1 id="느낀점">느낀점</h1>
<p>최근 깊이가 부족한 느낌으로 학습을 통해 알고 있던 것들을 다시 다지고 새로운 내용을 배웠다. 
그러고 나서 다시 프로젝트를 리팩토링 하는 과정을 통해 부족했던 점, 돌아 가기만 하는 코드를 만든 부분들이 눈에 들어왔다.
일단 로깅을 제대로 해야 내가 잘하고 있는지에 대해 눈으로 볼 수 있다고 생각해서 로깅을 가장 처음 구현했다.
구현하면서도 스프링 부트, MVC Interceptor과 같은 기본적 내용과 그리고 위에 언급한 RequestHolder나 빈 등록 시점에 설정 등록 하는 법과 같은 많은 것들이 편리하게 지원되는구나 느꼈다.
API 로깅을 하나씩 확인하는데 N+1 문제가 발생하는 부분들이 보였다.
하나 하나 이제부터 고쳐야 할 생각에 너무 신나는 것 같다 !</p>
<h2 id="출처">출처</h2>
<p><a href="https://velog.io/@ohzzi/API%EC%9D%98-%EC%BF%BC%EB%A6%AC-%EA%B0%9C%EC%88%98-%EC%84%B8%EA%B8%B0-1-%ED%95%98%EC%9D%B4%EB%B2%84%EB%84%A4%EC%9D%B4%ED%8A%B8%EB%A5%BC-%EC%9D%B4%EC%9A%A9%ED%95%9C-%EC%B9%B4%EC%9A%B4%ED%8C%85">https://velog.io/@ohzzi/API의-쿼리-개수-세기-1-하이버네이트를-이용한-카운팅</a></p>
<p><a href="https://stackoverflow.com/questions/39112308/how-i-can-configure-statementinspector-in-hibernate">https://stackoverflow.com/questions/39112308/how-i-can-configure-statementinspector-in-hibernate</a></p>
<p><a href="https://chung-develop.tistory.com/64">https://chung-develop.tistory.com/64</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[JPA @OneToOne Lazy 로딩 전략 작동 이슈]]></title>
            <link>https://velog.io/@hyn_053/JPA-OneToOne-Lazy-%EB%A1%9C%EB%94%A9-%EC%A0%84%EB%9E%B5-%EC%9E%91%EB%8F%99-%EC%9D%B4%EC%8A%88</link>
            <guid>https://velog.io/@hyn_053/JPA-OneToOne-Lazy-%EB%A1%9C%EB%94%A9-%EC%A0%84%EB%9E%B5-%EC%9E%91%EB%8F%99-%EC%9D%B4%EC%8A%88</guid>
            <pubDate>Tue, 30 Jan 2024 09:14:09 GMT</pubDate>
            <description><![CDATA[<h1 id="문제-상황">문제 상황</h1>
<p>마커를 찍을 때 어떤 방법이 효율적인지 테스트를 하던 도중 엔티티를 가져와 어플리케이션에서 정제해 DTO로 반환하는 방법에서 Lazy 로딩이 제대로 작동하지 않고, 사용하지 않는 Goods와 연관된 엔티티 중 일부를 다 가져오는 상황이 일어났다. </p>
<p>왜 그런지 궁금증이 들어 이 글을 작성하게 됐다.</p>
<h2 id="문제-상황-이해">문제 상황 이해</h2>
<p>아래는 문제가 발생한 엔티티들이다.</p>
<pre><code class="language-java">@Entity
public class Goods extends BaseTimeEntity {
    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = &quot;category_id&quot;)
    private Category category;

    @OneToOne(mappedBy = &quot;goods&quot;, fetch = LAZY)
    private Sell sell;

    @OneToOne(mappedBy = &quot;goods&quot;, fetch = LAZY)
    private Board board;
}</code></pre>
<pre><code class="language-java">@Entity
public class Sell {
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;goods_id&quot;)
    private Goods goods;
}

@Entity
public class Board extends BaseTimeEntity {
    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = &quot;goods_id&quot;)
    private Goods goods;
}</code></pre>
<p>아래는 문제의 쿼리이다.</p>
<pre><code class="language-java">@Query(&quot;select co &quot;+
            &quot;from GoodsCoordinate co &quot; +
            &quot;join fetch co.goods g &quot; +
            &quot;where ST_CONTAINS(:area, co.coordinate) &quot; +
            &quot;and g.goodsStatus = :status&quot;)
    List&lt;GoodsCoordinate&gt; findMarkerListFromCoordinateV2(@Param(&quot;area&quot;) final Polygon area,
                                                 @Param(&quot;status&quot;) GoodsStatus goodsStatus);</code></pre>
<p>이렇게 작성하고 Postman을 이용해서 요청을 보내니 엄청난 양의 쿼리가 로그에 표시되었다.</p>
<p>로그를 천천히 보면서 확인하니 <code>Sell</code>과 <code>Board</code>가 Eager로 동작하고 있었고, 두 관계는 @OneToOne이라는 공통점이 있었다.</p>
<p>도대체 왜 이렇게 동작한 것일까?</p>
<h1 id="이유">이유</h1>
<p>결론적으로 바로 말하자면 다음과 같은 이유에서 였다.</p>
<p><strong>@OneToOne 양방향 연관 관계에서 연관 관계의 주인이 아닌 쪽 엔티티를 조회할 때, Lazy로 동작할 수 없다.</strong></p>
<p>왜 일까?</p>
<p>기본적으로 연관 관계 엔티티를 LAZY로 조회하기 위해서는 JPA가 ‘프록시’를 만들어 이를 이용한다.</p>
<p>JPA는 null 값이 가능한 상황에서 해당 값을 프록시 객체로 감싸는 것은 불가능하다. ****</p>
<p>만약 null 값이 가능한 @OneToOne 연관관계를 지닌 엔티티를 프록시 객체가 참조한다면 그 순간 null이 아닌 프록시 객체를 리턴하는 상황이 발생하게 된다. </p>
<p>그러면 NPE를 제대로 던질 수 없기 때문에 나중에 어떤 오류가 발생할지 모른다.</p>
<p>따라서 JPA는 연관 관계 엔티티에서 null 혹은 프록시 객체 둘 중 하나가 반드시 할당되어야 한다.</p>
<p>자 이제 다시 곰곰히 생각해보자. </p>
<p>JPA에서 연관 관계의 주인이 FK를 가지고 있고 연관 관계의 주인이 아닌 엔티티는 FK를 가지고 있지 않다.</p>
<p>따라서 연관 관계의 주인이 아닌 엔티티를 조회 할 때에는 그 엔티티와 연결되어 있는 엔티티가 있는지 아니면 Null인지 알 수 없다. </p>
<p>Goods 테이블이다.</p>
<img src="https://velog.velcdn.com/images/hyn_053/post/9715f71e-c1da-48c2-801c-469b736c366e/image.png" width="300">

<p>Goods 테이블만 조회하면 연관 관계에 있는 엔티티가 Null인지 확인할 수 없다.</p>
<p>왜나면, 해당 테이블에 다른 엔티티에 대한 정보가 하나도 없기 때문이다.</p>
<p>따라서 JPA가 연결된 엔티티가 있는지 없는지 모르기 때문에 null 혹은 프록시 객체 중 어떤 객체를 넣어야 할지 몰라서 Eager로 동작해 실제 연결된 엔티티가 있는지 없는지 확인하는 것이다.</p>
<p>그럼 @ManyToOne, @OneToMany은 어떻게 LAZY 로딩이 되는 것일까?</p>
<p>ManyToOne은 당연하게도 연관 관계의 주인이 Many기 때문에 해당 테이블을 조회하면 FK로 연결된 엔티티가 있는지 확인하기 때문에 LAZY 로딩이 가능하다.</p>
<p>OneToMany가 문제인데 OneToMany는 왜 LAZY 로딩이 가능 한 것일까? FK를 가지고 있지도 않은데 말이다.</p>
<p>답은 컬렉션에 있다. 컬렉션은 null을 표현할 방법이 있다.</p>
<p>그래서 프록시 객체를 만들어 놓고 실제 조회 시에 빈 컬렉션을 반환하여 null임을 표시할 수 있기에 LAZY로 동작이 가능하다.</p>
<h1 id="해결방안">해결방안</h1>
<p>해결 방안은 간단하게는 fetch join으로 처음부터 다 가져와서 사용하는 방법이 있고 연관관계의 구조를 바꾸거나  byte code instrument을 이용하는 방법이 있다. </p>
<p>이 방법에서도 CTW(compile time weaver), LTW(load time weaver) 두 가지 방식으로 나뉜다. 그리고 Hibernate의 내부에 이미 구현된 FieldHandler를 사용하는 방법이 있다.</p>
<p>자세한 건 검색을 참고해보자.</p>
<p>난 join fetch와 테이블 설계를 변경하는 법을 이용했다. 지도에 마커를 찍는 것은 Projection을 이용해서 Dto로 반환 받는 것이 유리했고,</p>
<p>마커 기능을 제외하고 Goods가 필요한 경우 애플리케이션 구조 상 board와 sell, goods가 동시에 필요한 경우가 많다고 판단해 이 비즈니스 관점에서 테이블 분석 후 테이블 설계 변경으로 해결했다.</p>
<h1 id="느낀-점">느낀 점</h1>
<p>트러블 슈팅은 일어날 때 마다 매번 깊게 공부해야 한다는 점을 명심하게 된다. 이번에도 JPA 작동 원리에 대해 얕게 알고 있던 내 잘못이였다.</p>
<p>프록시로 작동한다는 사실은 알고 있었지만, 연관 관계의 주인과 DB적으로 조금 깊게 생각하고 JPA 프록시에 작동 방식에 대해 얕은 지식을 가지고 문제를 찾았기 때문에 시간도 조금 걸렸던 것 같다.</p>
<p>그리고 테스트는 역시 몇 번을 해도 부족하지 않은 것 같다.</p>
<p>아무튼 무지성으로 양방향을 쓰지 말고 연관 관계의 주인, DB, 프록시에 대해 생각하면서 써보자 !</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[MySQL 좌표 다루기]]></title>
            <link>https://velog.io/@hyn_053/MySQL-%EC%A2%8C%ED%91%9C-%EB%8B%A4%EB%A3%A8%EA%B8%B0</link>
            <guid>https://velog.io/@hyn_053/MySQL-%EC%A2%8C%ED%91%9C-%EB%8B%A4%EB%A3%A8%EA%B8%B0</guid>
            <pubDate>Wed, 17 Jan 2024 16:21:29 GMT</pubDate>
            <description><![CDATA[<p>지구미 프로젝트를 진행하면서 구현한 기능 중에 하나는 지도에서 내 주변 공동 구매 정보를 지도에 마커를 찍어 표시한다.</p>
<p>그래서 좌표 데이터를 다루게 되었는데, 좌표를 다룰 수 있는 방법이 공간형 데이터 타입 spatial를 이용하거나, Decimal을 이용해서 다루는 2가지 방법이 있다는 것을 알았다.</p>
<p>어떻게 저장 하는 것이 좋을지 알아보자 !</p>
<h1 id="mysql-공간-데이터-개요">MySQL 공간 데이터 개요</h1>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/403cc237-29f3-4e60-aceb-3e76a1b7b3fb/image.png" alt=""></p>
<table>
<thead>
<tr>
<th>타입</th>
<th>정의</th>
<th>예시</th>
</tr>
</thead>
<tbody><tr>
<td>Point</td>
<td>좌표 공간의 한 지점</td>
<td>POINT(10 10)</td>
</tr>
<tr>
<td>LineString</td>
<td>다수의 Point를 연결해주는 선분</td>
<td>LINESTRING(10 10, 20 20, 30 30)</td>
</tr>
<tr>
<td>Polygon</td>
<td>다수의 선분들이 연결되어 닫혀있는 상태</td>
<td>POLYGON((10 10, 10 20, 20 10, 10 10))</td>
</tr>
<tr>
<td>Multi-Point</td>
<td>다수의 Point 집합</td>
<td>MULTIPOINT(10 10, 20 20)</td>
</tr>
<tr>
<td>Multi-LineString</td>
<td>다수의 LineString 집합</td>
<td>MULTILINESTRING((10 10, 20 20), (15 15, 25 25))</td>
</tr>
<tr>
<td>Multi-Polygon</td>
<td>다수의 Polygon 집합</td>
<td>MULTIPOLYGON(((10 10, 10 20, 20 10, 10 10)), ((40 40, 30 30, 40 40)))</td>
</tr>
<tr>
<td>GeomCollection</td>
<td>모든 공간 데이터들의 집합</td>
<td>GEOMETRYCOLLECTION(POINT(10 10), LINESTRING(20 20, 30 30))</td>
</tr>
</tbody></table>
<p>MySQL이 지원하는 공간 데이터의 종류이다.</p>
<p>단일 타입으로는 Point, LineString, Polygon이 있고, 나머진 이 세가지 타입의 조합이다.</p>
<p>공간 함수는 <a href="https://dev.mysql.com/doc/refman/5.7/en/spatial-function-reference.html">MySQL Docs</a>를 참고하자 !</p>
<h1 id="r-tree">R-Tree</h1>
<p>공간 데이터를 다루면 필히 인덱스를 적용하게 된다. 그럼 공간 데이터의 인덱스도 B-Tree로 다룰까?
아니다. 공간 인덱스 R-Tree 라는 자료 구조를 이용한다.</p>
<p>R-Tree 는 점, 선, 면(다각형)과 같은 다차원 정보를 효율적으로 저장하기 위한 트리 형태의 자료구조이다.</p>
<p>R-Tree는 MBR을 알아야 하는데, 그래서 MBR이 뭘까?</p>
<p>그림을 보면서 이해를 해보자.</p>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/62b3ae9e-eb12-4af2-af99-f14b45bf58e3/image.png" alt=""></p>
<p>위의 그림을 보면 점, 선, 어떤 도형을 기준으로 그 도형을 포함한 사각형이 만들어졌고, 이 모든 도형을 포함하는 사각형이 만들어졌다.</p>
<p>MBR 은 Minimun bounding rectangle로 특정 도형을 감싸는 최소 크기의 사각형을 의미한다.</p>
<p>그래서 이를 바탕으로 R-Tree를 구성하는데 아래의 그림을 보면 더 이해가 쉬울 것이다.</p>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/e2446ce4-bf0d-439c-aecf-874afc6530cd/image.png" alt=""></p>
<p>R-Tree 자료구조는 B-Tree 와 흡사한 형태로 구성되어있다.
각 노드에 저장할 수 있는 도형의 개수는 사전에 지정되는데, 최대 M개에서 최소 m(M/2)개 저장할 수 있다.</p>
<p>또한 B-Tree 처럼 리프 노드에 데이터(도형)를 저장하고, 리프 노드가 아닌 노드는 MBR 간의 포함관계를 표현한다.</p>
<p>탐색은 Top-down 방식으로 진행되며 루트 노드부터 리프 노드까지 내려간다.</p>
<p>이때 MBR 간에 중첩된 영역이 많을수록 탐색 성능은 떨어진다.</p>
<p>그 이유는 중첩된 영역이 이 많을수록 탐색할 노드가 많아지기 때문이다.</p>
<p>탐색 과정은 다음과 같다.</p>
<ol>
<li>루트 노드부터 모든 하위 노드들을 순회하며 MBR 을 이용하여 검색 영역 내에 들어오는지 검사</li>
<li>리프 노드를 찾으면 포함된 도형들이 검색 영역에 포함되는지 확인</li>
</ol>
<p>따라서 이를 바탕으로 공간 인덱스를 이용해 탐색하는 방법을 알아보자.</p>
<h1 id="공간-인덱스를-이용한-탐색">공간 인덱스를 이용한 탐색</h1>
<p>아래는 내가 테스트를 위한 DDL이다.</p>
<pre><code class="language-sql">CREATE TABLE test.goods
(
    point POINT NOT NULL,
    created_date              datetime(6)                null,
    goods_id                  bigint auto_increment
        primary key,
    modified_date             datetime(6)                null,
);
ALTER TABLE test.goods MODIFY point POINT NOT NULL SRID 5181;
CREATE SPATIAL INDEX point_spatial_index ON test.goods(point);</code></pre>
<p>여기서 SRID는 공간 참조 식별자로 평면 지구 매핑 또는 둥근 지구 매핑에 사용되는 특정 타원을 기반으로 하는 공간 참조 시스템에 해당한다.
나는 카카오맵을 쓰기 때문에 카카오 맵의 SRID인 EPSG: 5181을 썼다.</p>
<p>이 값을 넣지 않을 경우 default 값인 0이 들어가게 되는데, 이 경우에 Insert 하게 되면 데이터도 default 값이 0으로 들어가게 된다. </p>
<p>그 후 5181로 조회한다면 인덱스를 타지 않고 테이블 풀스캔을 하게 된다. 또한 Insert 된 데이터도 다 날리고 다시 넣어야 SRID가 제대로 설정 되기 때문에 조심하자.</p>
<h2 id="데이터가-밀집되어-분포되어-있을-때">데이터가 밀집되어 분포되어 있을 때</h2>
<p>더미 데이터를 반복문을 이용해서 일정하게 분포되게 넣었다. 
JPQL이 작성한 쿼리를 바탕으로 SQL 문을 만들어 날려보니, </p>
<pre><code class="language-sql">SELECT g1_0.goods_id
FROM test.goods g1_0
WHERE ST_INTERSECTS(
              ST_SRID(
                      ST_GeomFromText(&#39;POLYGON((128.5 128.5, 128.6
128.5, 128.6 128.6,
128.5 128.6, 128.5 128.5))&#39;),
                      5181),
              g1_0.point)
ORDER BY g1_0.created_date DESC;</code></pre>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/f10c68ab-159b-490d-9c65-f5f795018bee/image.png" alt=""></p>
<p>잘 타는 모습이다. Decimal, 공간 타입의 실제 운영의 차이를 보자 !</p>
<h3 id="decimal-vs-공간-타입">Decimal vs 공간 타입</h3>
<p>처음 구현을 하게 됐을 때, 이전 MySQL이 공간 타입을 지원하지 않을 때 좌표 값을 Decimal 같은 숫자로 저장했다는 사실을 알게 되었다. 나도 물론 공간 타입이라는 걸 몰랐기 때문에, Decimal로 아래와 같이 구현했다.</p>
<pre><code class="language-sql">create table test.goods
(
    mapx                      decimal(17, 13)            null,
    mapy                      decimal(17, 13)            null,
    created_date              datetime(6)                null,
    goods_id                  bigint auto_increment
        primary key,
    modified_date             datetime(6)                null
);

create index goods_mapy_mapx_index
    on jigume.goods (mapy, mapx);</code></pre>
<p>위와 똑같이 더미 데이터 10000건을 넣고 JPQL이 작성한 쿼리를 바탕으로 SQL을 다음과 같이 날려보았다.</p>
<pre><code class="language-sql">select g1_0.goods_id
from jigume.goods g1_0
where g1_0.mapx &gt;= 128.5
  and g1_0.mapx &lt;= 128.6
  and g1_0.mapy &gt;= 128.5
  and g1_0.mapy &lt;= 128.6
order by g1_0.created_date desc;</code></pre>
<ul>
<li>Decimal 타입으로 Index를 타는 경우</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/b07d2c7f-fd2b-4766-a586-3a71ebfc4dcd/image.png" alt=""></p>
<ul>
<li>공간 타입으로 Index를 타는 경우</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/e9308565-daf3-4ba2-ae66-130f5d1ce922/image.png" alt=""></p>
<p>Cost는 공간 인덱스를 타는게 미미한 차이로 싸고, 실행 시간은 Decimal 타입이 3배 정도 빠르다.</p>
<h2 id="데이터-분포를-랜덤하게-했을-때">데이터 분포를 랜덤하게 했을 때</h2>
<p>이제 데이터를 랜덤하게 넣어서 측정해보자.</p>
<p>더미 데이터를 반복문과 RAND() 함수를 이용해서 랜덤하게 분포되게 넣었다. </p>
<p>(데이터 크기를 완벽하게 같게 할 수는 없어서 비슷한 값이 나오도록 했다.)</p>
<h3 id="decimal-vs-공간-타입-1">Decimal vs 공간 타입</h3>
<p>아래는 쿼리 실행 계획을 분석했다.</p>
<ul>
<li>Decimal 타입</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/c5d99c1b-8318-40fc-b054-58b7b3b0a059/image.png" alt=""></p>
<ul>
<li>공간 타입</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/ce981076-191b-46a8-8675-13b88337e30e/image.png" alt="">
(R-Tree 특징처럼 확실히 데이터 분포도가 퍼져있는게 빠른 듯 싶다.)</p>
<p>Cost는 공간 인덱스를 타는게 6~7배 효율적이고, 실제 걸린 시간은 Decimal이 미세하게 빠르다.</p>
<p>완벽히 정확하게 논할 수 없겠지만, 그래도 압도적으로 공간 인덱스가 빠를거라고 생각했는데 이게 어떻게 된 일일까?</p>
<p>추정한 바로는 2가지의 이유가 있었다.</p>
<ol>
<li>인덱스 크기: 공간 인덱스는 일반적으로 B-Tree 인덱스보다 크기가 크다. 여기선 길이가 34였고, Decimal index는 18이였다. 따라서 인덱스 키의 길이 때문에 쿼리 실행 시간을 늘어났다.</li>
<li>R-Tree의 복잡성: B-Tree보다 R-Tree가 더 복잡한 구조를 가지고 있기 때문에, 공간 인덱스를 통해 데이터를 검색할 때 이는 실행 시간이 늘었다. </li>
</ol>
<h2 id="결과">결과</h2>
<p>당장은 실행 시간이 우세한 Decimal이 유리해보인다. 그럼에도 공간 데이터를 쓰는 이유는 무엇일까?</p>
<p>공간 데이터를 쓰게 된다면 장점은 공간 데이터 타입을 사용하면 점, 선, 면 등 다양한 형태의 공간적 객체를 표현할 수 있다. 또한, 이러한 객체들 사이의 공간적 관계(예: 겹침, 교차, 인접 등)를 정확하게 표현하고 계산할 수 있다.</p>
<p>즉, 두 좌표 사이의 거리를 정확히 구하거나 복잡한 도형의 영역에 포함되는지 지원하는 공간 함수를 사용할 수 있다.</p>
<p>또한, 데이터가 커지게 되면 공간 인덱스는 데이터의 공간적 관계를 고려하여 인덱싱을 수행해서 데이터가 밀집되어 있어도 공간적 분포를 고려한 검색이 가능하다. </p>
<p>반면에, decimal 인덱스는 각 좌표를 개별적으로 인덱싱하므로, 좌표 간의 공간적 관계를 고려하지 않는다.</p>
<p>결국 모든 상황을 고려해보고 선택하는게 좋지만, 추후 우리 서비스가 나와의 거리가 가까운 순으로 정렬하거나 공간에 관한 다양한 기능을 넣게 된다면 유지, 보수에 있어 이점을 챙길 수 있을 것으로 보여 공간 인덱스를 도입하기로 했다!</p>
<h1 id="느낀-점">느낀 점</h1>
<p>요즘 이력서를 쓰고 서류를 넣다보니 프로젝트를 오랜만에 시작하게 됐다. 이번 글을 쓰면서 RDB, SQL 튜닝, JPA, 테스트 도구 활용에 대해 학습하고 적용해보면서 CS가 부족하다는 사실을 뼈저리게 느꼈다.</p>
<p>그리고 SQL과 RDB 책을 도서관에서 빌려 빠르게 읽고 적용했는데 백문이불여일견이라고 앉아서 책만 읽을 땐 죽어도 안읽히던 것들이 실제로 하나 하나 해보면서 찾아보니 금방 느는 것 같다.</p>
<p>빨리 프로젝트 고도화 작업을 끝내고 실제 서비스를 사용자들에게 선보이고 싶다..!</p>
<h2 id="레퍼런스">레퍼런스</h2>
<p><a href="https://kong-dev.tistory.com/245#google_vignette">https://kong-dev.tistory.com/245#google_vignette</a></p>
<p><a href="https://sparkdia.tistory.com/24?category=1114027">https://sparkdia.tistory.com/24?category=1114027</a></p>
<p><a href="https://dev.mysql.com/doc/refman/8.0/en/spatial-index-optimization.html">https://dev.mysql.com/doc/refman/8.0/en/spatial-index-optimization.html</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[구름톤 7기 후기]]></title>
            <link>https://velog.io/@hyn_053/%EA%B5%AC%EB%A6%84%ED%86%A4-7%EA%B8%B0-%ED%9B%84%EA%B8%B0</link>
            <guid>https://velog.io/@hyn_053/%EA%B5%AC%EB%A6%84%ED%86%A4-7%EA%B8%B0-%ED%9B%84%EA%B8%B0</guid>
            <pubDate>Mon, 11 Sep 2023 10:46:05 GMT</pubDate>
            <description><![CDATA[<blockquote>
<p>구름톤 7기 2023.09.05 ~ 2023.09.08에 대한 후기입니다!</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/f300a934-1af5-4f59-8a62-ee9c7e4ba587/image.png" alt=""></p>
<h2 id="구름톤-참가-과정">구름톤 참가 과정</h2>
<p>구름톤은 2기부터 알게 되었다. 해커톤은 이미 나가본 경험이 있지만 제주도에서 디자이너와 기획자가 함께 하는 특별한 경험은 구름톤에서 밖에 할 수 없을 것 같아, 신청하게 되었다.</p>
<p>2기부터 계~속 떨어져 이번이 4수였던 터라, 이번 구름 톤은 큰 기대를 안 했다. 그래도 내심 캘린더에 합격 날짜도 적어놨다.</p>
<p>그렇게 까먹고 지내다가 점심을 먹는 도중 갑자기 날아온 문자</p>
<center><img src = "https://velog.velcdn.com/images/hyn_053/post/58abff73-9b85-4739-906c-111d8e70a57e/image.jpeg" width = "300" height = "300"></center>

<p>원래 발표일보다 3일 일찍 날아온 합격소식.. ‘처음엔 또 떨어졌구나.’ 하다가 참가 신청 링크를 보고 됐구나.. 싶었다. 개인적으로 이번에 붙을 수 있었던 이유는 지원서의 글자 수 제한이 생기면서 글을 쓸 때 잘 구조화해서 임팩트 있게 쓰려고 노력해서 그런 것 같다. 글자 수 제한이 없었을 땐, 하고 싶은 말을 너무 중구 난방으로 적어서 떨어졌지 않았을까..</p>
<h3 id="제주도로">제주도로</h3>
<p>9/1 ~ 9/3까지 일본 여행을  갔던터라, 매우 피곤했지만 바로 9/4 제주도로 출발했다. 첫 날 미리 오신 분들끼리 저녁을 먹으셨지만, 프로젝트 회의가 있어 참여 하지는 못했다. 회의가 끝난 후 제주도를 걸으면서 다음날을 기대하며 기다렸는데, 어떻게 보면 온전히 제주도를 즐길 수 있었던 몇 없던 날이 아니였을까 싶다.</p>
<p>가신다면 해커톤 기간때는 매우 피곤하기에 미리가서 첫날에 맘껏 즐기시는 것을 추천 !</p>
<h2 id="구름톤-시작">구름톤 시작</h2>
<h3 id="1일차">1일차</h3>
<center><img src = "https://velog.velcdn.com/images/hyn_053/post/6d6e3461-7cfa-4417-b54e-3724994e7d40/image.JPG" width = "300" height = "300"></center>

<p>1일차는 자기 소개와 아이스 브레이킹 타임을 포함해 사람들과 잠깐의 인사를 나누고 스페이스 닷원으로 넘어가서 교육을 듣게 되는 날이다.</p>
<p>처음엔 제주 테크노 파크에서 잠깐의 자기 소개와 아이스 브레이킹 타임을 가졌다.</p>
<center><img src = "https://velog.velcdn.com/images/hyn_053/post/0097c5b9-98cf-4127-b111-889793f65662/image.JPG" width = "200" height = "200"></center>

<p>아이스브레이킹 시간에는 조를 짜서 스파게티와 마시멜로를 이용해서 가장 높이 쌓아 올리는 팀에게 상품을 주셨는데, <strong>우리 조가 1등을 했다 !!</strong></p>
<p>1등 상품은 다양한 구름 굿즈가 들어있는 상품을 받게 되었는데 구름 키캡도 포함 되어 있어서 키보드를 반드시 사야하지 않을까 싶다. <del>배보다 배꼽이 큼</del></p>
<p>간단한 아이스 브레이킹 타임이 끝나고 스페이스 닷원으로 넘어가 식사를 했다. 아쉽게도 구내 식당을 이용하진 못했지만, 스페이스 닷원에 들어가 코딩 하르방을 보고 나니.. 감격이였다.</p>
<center><img src = "https://velog.velcdn.com/images/hyn_053/post/96f3fefc-1f4c-48ac-96c1-cc5875e0a236/image.JPG" width = "500" height = "500"></center>

<blockquote>
<p>타타탁..ㅌ..타타탁..</p>
</blockquote>
<p>그 후는 교육을 듣게 되었는데, 크램폴린 IDE와 GDS에 대한 교육을 나눠 듣고, 또 많은 강사님들이 와서 다양한 강의를 하셨다. 나는 모노님의 크램폴린 IDE 강의를 듣게 되었는데, 도커와 쿠버네티스를 몰라도 실제 보면서 간단한 배포까지 해보면서 되게 신기했던 것 같다. </p>
<p>교육이 끝난 후에 본격적으로 해커톤 주제와 간단한 과제가 주어진다. 다음날 자기 PR 혹은 주제에 맞는 기획 중에 선택해 간단하게 ppt 한 페이지로 발표 자료를 만들어 발표하는 과제인데, 다 같이 밥을 먹고 카페에 가서 PR 자료를 쓰게 되었다. </p>
<center><img src = "https://velog.velcdn.com/images/hyn_053/post/46a13b14-1682-4389-9d68-c1d458254b45/image.JPG" width = "400" height = "400"></center>


<p>개인적으로 자랑할 수 있는 스펙이 없다고 생각했기도 하고 해커톤에서는 ‘<strong>잘하기보단 재밌게 해야 한다.</strong>’ 라고 생각해 ppt를 구성해서 내고 숙소로 돌아갔다.</p>
<h3 id="2일차">2일차</h3>
<p>스페이스 닷원으로 모여서 전 날 과제로 받은 자기PR 및 기획 발표를 통해 팀빌딩을 진행하게 되었다. 마지막 순서로 자기 PR 발표를 하게 되었는데, 앞에 하시는 분들 발표를 보는데 다들 대단하신 분들이였다. </p>
<p>한 페이지를 포트폴리오와 다양한 기술로 가득채우신 ppt를 본 순간 속으로 ‘큰일 났구나..’ 했는데, 막상 발표 할 때는 다들 잘 웃어주셔서 다행이였다. 그 후 기획을 발표했는데, 역시 다들 너무 잘하셔서 깜짝 놀랬다. 그 중 눈에 띄는 아이디어를 보고 ‘저 분과 같이 팀을 하겠다.’ 마음을 먹고 기획 발표 후 팀빌딩 시간에 찾아가 팀을 이루게 되었다 !</p>
<p><strong>팀빌딩이 끝난 후 식사하고 성산 플레이스 캠프로 넘어가 본격적으로 해커톤이 시작 되었다.</strong></p>
<blockquote>
<p>지도형 공동 구매 플랫폼 - 지구미</p>
</blockquote>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/2b9c6db2-0f10-4713-80cf-bd076c456553/image.png" alt=""></p>
<p>아이디어는 팀장이시자 디자이너 서희님의 아이디어로 출발하였다.</p>
<p>아이디어는 제주도의 추가 배송비를 바탕으로 제주도만의 공동 구매 플랫폼이라는 아이디어 였다. 제주도라는 지역 특성상 거의 모든 상품이 제주도만 추가 배송비를 붙는다는 문제를 파고들어 제주도민이 공동 구매를 통해 배송비를 아끼자는 컨셉의 아이디어였다.</p>
<p>워낙 좋은 아이디어라 많은 얘기가 필요없었고, 수승님의 기획, 서희님의 디자인이 매우 빠른 속도로 이루어졌다. 아이디어를 고도화하는 과정에서 기획과 디자인 관점 그리고 개발 관점에서 제한된 시간에서 할 수 있는 일과 없는 일을 분리하고, 팀규칙을 만들고, 스프린트를 정하는 과정까지 짧은 시간이였지만, 협업의 진가를 느껴볼 수 있었다. </p>
<blockquote>
<p>개인적으로는 최근 프로젝트를 하면서 기획 단계에서 많은 난항을 겪어 개발 단계에 차질을 빚는 상황이 여러 번있었는데, 이번에 수승님의 기획덕에 개발이 편하니 기획의 필요성을 확실히 느끼게 되었다.
디자인적인 부분도 역시..👍👍  서희님 최고..</p>
</blockquote>
<p>그 후 기획에 따른 DB 설계를 하고, <strong>구름톤의 하이라이트 비어 파티를 하게 되었다.</strong></p>
<center><img src = "https://velog.velcdn.com/images/hyn_053/post/82723437-8791-48fe-86fe-3df496d8a316/image.JPG" width = "300" height = "300"></center>

<blockquote>
<p>개발을 잊고 온전히 즐길 수 있는 마지막 시간</p>
</blockquote>
<p>비어 파티는 조끼리 모여 친해지는 시간을 가지고, 그 후 다른 조분들, 멘토 분들과도 얘기하는 시간이 되었다.</p>
<p>그 중 기억에 남는 시간은 역시 멘토 분들과의 시간이였다. 멘토 분들에게 현재 내가 가지고 있는 고민을 상담해볼 수 있는 좋은 시간 이였다. 더욱 열심히 해야겠다는 마음을 또 충전하게 되었다.</p>
<p>비어 파티가 끝나고는 다시 모여서 개발을 하게 되었는데, 이 때는 앉아있다가 새벽 3시쯤 들어가 자버렸다. 
거의 우리 팀을 포함한 모든 팀의 디자이너 분들과 기획자분들은 밤을 새시더라.. 우리팀도 서희님과 수승님만 남고 나를 포함 개발자분들은 들어가서 내일을 위해 휴식을 취했다.</p>
<h3 id="3일차---개발-지옥-시작">3일차 - 개발 지옥 시작</h3>
<p>8시쯤 일어나 다시 개발을 시작했다. 일어나고 나니 거의 모든 디자인과 기획이 다되어있어 정말 만들기만 하면 되었다. 그 때부터 시작된 개발 지옥..</p>
<p>전날 DB 설계를 바탕으로 API 구축을 했고, FE(혜연님, 도경님)은 디자인을 바탕으로 카카오 지도 API를 사용해, 지도를 그리고 다양한 페이지를 개발하게 되었다.</p>
<center><img src = "https://velog.velcdn.com/images/hyn_053/post/eb0cde41-aec2-40a6-83ff-92c9c0d449e6/image.png" width = "300" height = "300"></center>

<blockquote>
<p>모두가 영혼을 갈아넣은 메인 페이지..</p>
</blockquote>
<p>개발을 하면서 어떤 플로우를 진행할 때 이해가 되지 못하거나 애매한 경우 바로바로 피드백을 통해 수정하면서 진행했다.</p>
<p>예를 들면 상품 구매 폼에서 몇 번째 구매리드인지 표시해야 할지 부터 배송비 절감은 어떻게 표시하는게 눈에 띄는지, 상품 등록 플로우가 어떻게 하면 직관적일지, 믿을 수 있는 거래를 위해 어떤 방법이 가장 좋을지 모두가 의견을 적극적으로 제시하고 적극적으로 수용 및 토론이 이루어졌다.
 기획과 디자인 관점, 개발 관점에서 다양한 의견이 오갔는데 다들 편견없이 잘 들어주시고 피드백을 주셔서 굉장히 수월하게 진행되었고, 기획단계에서 기능의 우선 순위를 정해서 짧은 기간 선택과 집중을 잘할 수 있게 되었던 것 같다.</p>
<p><strong>그 후 개발을 계속.. 계속 지옥에 빠져버렸다..</strong> </p>
<p>개발을 계속 하면서 밤까지 지옥에 빠졌다. 대략적인 API를 만들고 나서 구름을 이용한 배포가 가산점이 상당했기에 배포를 우선적으로 두어 배포를 시작하게 되었다.</p>
<h3 id="4일차---멘토님과-제로부터-시작하는-배포-데이트-시작">4일차 - <strong>멘토님과 제로부터 시작하는 배포 데이트 시작~!</strong></h3>
<p>도커와 쿠버네티스를 전혀 써보지 않아서 정말 0부터 시작하게 되었다. 무작정 크램폴린 강의를 맡으신 모노님을 찾아가 정말 1부터 100까지 배우게 되었다. 진짜 기초적인 내용부터 심화 내용까지 4시간 가까이 되는 시간동안 친절하게 설명해주시고, 에러 해결을 도와주셨다. 새벽 타임 피곤하실 수도 있는데 주석까지 달아주시며 최대한 설명해주시려는 모습에 감동받았다..</p>
<p><strong>Shout out to ‘The GOAT’ 모노…</strong></p>
<p><strong>그렇게 배포를 성공하고 보니 아침이 찾아왔다…</strong></p>
<p>배포를 하는 동안 발표자료가 완성되고 이제 프론트 분들도 거의 완성 하셔서 합쳐서 사소한 버그들을 고치기 시작했다. 그렇게 시간이 지나고 11시 데드라인이 다가오면서 마지막까지 개발을 하고 겨우 제출을 해서 해피 엔딩으로 끝나나 싶었지만.. </p>
<blockquote>
<p>아 참, 로컬 CORS만 해결했다..</p>
</blockquote>
<p>갑자기 정신이 번뜩 들었다. 1시에 발표여서 시간은 남았지만, 쿠버네티스를 써보지 않아서 혹시 재배포시를 하게 되면 DB가 날아갈까 고민하다가 모노님이 StatefulSet에 대한 설명을 해준 기억을 번뜩 떠올려 다행히 빠르게 버그를 고치고 재배포를 통해 해결하게 되었다. 그 과정에서 CI/CD의 참맛을 알게 되었다.</p>
<p><strong>운명의 시간</strong></p>
<p>발표 순서는 우리팀이 첫 번째가 되었다. 가장 먼저 서희님의 ‘매도 먼저 맞는게 낫다.’ 는 신념 아래 발표를 시작하게 되었고, 정말 발표를 잘하셨다. 그 후 다른 팀 발표를 지켜보면서 다양한 컨셉과 서비스를 보고 감탄했다.</p>
<p>인상 깊었던 점은 모든 조의 기획자분들이 심사위원 분들의 질문에 정말 당황하지 않고 말을 잘하시는 것을 보고 깜짝 놀랬다. 이러한 점이 정말 결과를 하나도 예측하지 못하게 만들었다.
내심 상을 기대했지만, 누가 받아도 이상하지 않을 정도였기에 결과 발표를 기다렸다.</p>
<blockquote>
<p>결과는..?!</p>
</blockquote>
<center><img src = "https://velog.velcdn.com/images/hyn_053/post/2dad6aeb-9669-4842-b878-d7a62a7b8adc/image.JPG" width = "300" height = "300"></center>

<p><strong>대상은.. 지구팀!</strong></p>
<p>대상이다.. 무려 대상… 믿기지 않았다.. 대상 전까지 불리지 않아 반쯤 포기상태였다. 대상 발표 때는 아무 생각 없었는데, 대상 발표가 나자마자 모두가 놀랬다. 수상소감을 발표했다.</p>
<blockquote>
<p>못난 백엔드 두신 팀원들 고생 많으셨고, 이런 기회를 주신 카카오와 구름에게 감사드립니다.</p>
</blockquote>
<p>개인적으로 진짜 아이스 브레이킹 1등 상품부터 대상까지 이번 구름톤에서 받을 수 있는 모든 것을 받았다.</p>
<p><strong>인생 첫 대상이 구름톤이라니..</strong></p>
<h2 id="후기">후기</h2>
<p>후회없고 정말 정말 재밌는 경험이였다. 몇가지 나눠 소개해보자면,</p>
<h3 id="협업의-참맛">협업의 참맛</h3>
<p>구름톤을 통해 기획자, 개발자, 디자이너가 모여 짧지만 기초 기획부터 배포까지 개발의 한 싸이클을 제대로 느낄 수 있었다. 기획적인 관점과 디자인적인 관점, 프론트 개발의 관점 다양한 사람들이 모여 하나의 공통 목표를 가지고 개발하면서 서로에 대한 이해를 할 수 있게 되었던 게 가장 큰 것같다. 서로가 가진 이해관계를 수평적인 의견 교환과 적극적인 의견 수렴을 통해 서로에 대해 이해하며, 계속 디벨롭 해 나가는 경험은 구름톤이 아니였다면 쉽게 경험할 수 없을 경험이였다.</p>
<h3 id="네트워킹">네트워킹</h3>
<p>사실 어떤 대외 활동도 하지 않았던 나였기에, 사람들을 많이 만나기 위해 구름톤에 참가하게 되었다.
개발자를 목표로 두게 되고 꿈꾸게 된 결정적인 이유는 주변 좋은 사람들 덕이였다.</p>
<p>주변 좋은 사람들과 내가 가진 고민, 예를 들어 ‘컴퓨터 공학을 나와 단지 흐르듯이 개발자를 하는 것이 아니라 내가 정말 개발자가 되고 싶은 구체적인 이유가 무엇일까?’, ‘어떤 궁극적인 목표를 두고 커리어 뿐만 아니라 삶을 살아가면서 이루고자 노력해야 할까?’ 같은 근본적인 의문에 대해 의견을 얘기하다 보니 개발자가 되고 싶다는 생각을 하게 되었다.</p>
<p>이번 구름톤에서는 이제 곧 취준생이 되는 입장에서의 고민을 다양한 사람들과 멘토님들과의 대화를 통해 한층 다양한 피드백을 조언을 듣게 되어 너무 좋았던 시간 이였다. 예를 들어 내가 가져야 할 태도나 앞서 내가 개발자가 되고 싶었던 이유에 대해 얘기하며 의견을 여쭈봤는데 멘토님들이 앞으로 나아가야할 방향에 대해 조언을 들었던게 가장 기억이 남았고, 또 멘토님들의 현업 얘기를 듣다보면 시간 가는지 몰랐던 것 같다.</p>
<p> 멘토님들 뿐만 아니라 다른 참가자 분들도 모두 좋으신 분들이라 끝나고 정말 좋은 사람들을 많이 얻게 되었던 좋은 경험이였다.</p>
<h3 id="내가-앞으로-추구해야할-방향과-성장">내가 앞으로 추구해야할 방향과 성장</h3>
<p>모든 활동에서 그런듯 나의 부족한 점이 절실히 느껴졌던 것 같다. 나름 열심히 했다고 생각했는데, 더 열심히 해야겠다는 생각이 드는 건 해커톤이 주는 교훈이 아닐까.. </p>
<p>모두가 엄청난 실력자이셨기에 보고 배우면서 개발 실력을 좀 더 늘이고, 소프트웨어 적인 부분을 많이 향상 시켜야겠다고 생각했다.</p>
<p>그리고 내가 앞으로 추구해야할 방향에 대해 확실하게 생각하게 되었던 계기 같다. 근본부터 차근 차근 쌓아올리는 깊이 있는 학습에 대해 더욱 절실하게 느껴졌고, 더불어 인프라 기술을 배워보고 싶다는 생각을 하게 되어서 열심히 해볼 생각이다 !</p>
<h2 id="끝맺으며">끝맺으며</h2>
<p> 개인적으로 구름톤이 개발 인생에 있어 또 다른 큰 터닝 포인트가 된 것 같다. 그정도로 잊지 못할 경험이였다. 
제주도라는 특별한 공간에서 다양한 사람들이 모여 하나의 목표를 가지고 실제 서비스 개발의 한 싸이클을 경험하며, 팀끼리도 서로가 경쟁로 느끼는 것이 아닌 모르거나 힘든 부분은 적극적으로 도와주는 모습들이 내가 개발의 매력이라고 생각했던 부분을 다시 한번 느끼게 되었던 것 같다. </p>
<p>이런 기회를 주신 카카오와 구름에게 너무 감사하고 이 글을 보시는 분 중 참여를 고민하시는 분들이 있다면 꼭 추천드린다 !</p>
<p>이제 프로젝트를 계속 디벨롭하는 과정이 기다려진다 !</p>
<center><img src = "https://velog.velcdn.com/images/hyn_053/post/d1770a8c-f1f6-4925-afc0-b043bb6be929/image.jpeg
" width = "300" height = "300"></center>

<p><strong>프론트엔드 도경님, 혜연님, 디자이너 서희님, 기획자 수승님 저희 팀 모두와 멘토님들, 구름, 카카오에게 감사드립니다. 
앞으로도 지구미 파이팅~!</strong></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[어떤 크롤링 도구를 선택해야 할까?]]></title>
            <link>https://velog.io/@hyn_053/%EC%96%B4%EB%96%A4-%ED%81%AC%EB%A1%A4%EB%A7%81-%EB%8F%84%EA%B5%AC%EB%A5%BC-%EC%84%A0%ED%83%9D%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C</link>
            <guid>https://velog.io/@hyn_053/%EC%96%B4%EB%96%A4-%ED%81%AC%EB%A1%A4%EB%A7%81-%EB%8F%84%EA%B5%AC%EB%A5%BC-%EC%84%A0%ED%83%9D%ED%95%B4%EC%95%BC-%ED%95%A0%EA%B9%8C</guid>
            <pubDate>Sat, 22 Jul 2023 12:23:19 GMT</pubDate>
            <description><![CDATA[<aside>
💡  최근 자바를 이용한 크롤링을 할 일이 있어서, 크롤링 도구에 대해서 학습하게 되었다.
하지만 크롤링을 위한 라이브러리는 굉장히 많아서 어떤 라이브러리를 사용해야할 지 매우 혼란이 왔는데, 그래서 각 대표 라이브러리들은 무슨 차이가 있고, 어떤 상황에서 어떤 도구를 선택해야 할까?

</aside>

<hr>
<h2 id="대표-라이브러리---jsoup">대표 라이브러리 - Jsoup</h2>
<hr>
<h3 id="jsoup이란">Jsoup이란?</h3>
<p>맨 처음으로 대표 라이브러리인 jsoup을 접하게 되었다. </p>
<blockquote>
<p>jsoup은 HTML 문서에 저장된 데이터를 구문 분석, 추출 및 조작하도록 설계된 오픈 소스 Java 라이브러리입니다.</p>
</blockquote>
<ul>
<li>위키백과<blockquote>
</blockquote>
</li>
<li>Jsoup은 자바로 작성된 HTML 파싱및 조작 라이브러리이다. 이번에 활용한 기능은 아래와 같다.<ol>
<li>HTML 파싱: HTML 문서를 파싱하여 <strong>DOM 트리</strong>를 생성하여, HTML 문서의 요소에 쉽게 접근할 수 있다.</li>
<li>요소 검색: CSS 선택자를 사용하여 HTML 요소를 검색하여 데이터에 접근하거나, 추출할 수 있다.</li>
</ol>
<ul>
<li>DOM 트리란 DOM(Documen Object Model) 트리는 HTML, XML 또는 다른 마크업 언어의 문서를 표현하는 트리 구조이다.</li>
</ul>
</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/7d968d0d-7934-444b-943b-f13f8b2c2b98/image.png" alt=""></p>
<pre><code>- 이런 식으로 계층적인 트리 형식으로 표현할 수 있다. 
이를 통해 각 구조에 쉽게 접근하고 조작할 수가 있다. 
부모 노드 - 자식 노드를 가진 트리의 형태 이므로, 탐색및 순회가 용이하다.</code></pre><p> 그래서 Jsoup을 쓰는 이유는 뭘까?</p>
<h3 id="jsoup은-왜-쓸까">Jsoup은 왜 쓸까?</h3>
<p> 우린 여기서 정적인 웹페이지와 동적인 웹페이지, 즉 <strong>서버 사이드 렌더링</strong>과 <strong>클라이언트 사이드 렌더링</strong>에 대해 알아야 한다. 
  이 글은 이에 대한 주제가 아니기 때문에 간단하게 설명하자면,</p>
<ul>
<li><strong>서버 사이드 렌더링(Server Side Rendering, SSR)</strong>은 웹 페이지를 서버에서 렌더링을 해서 클라이언트에게 전달하는 것이다.</li>
<li><strong>클라이언트 사이드 렌더링(Client Side Rendering, CSR)</strong>은 웹 페이지 초기 로딩시 레이아웃과 자바 스크립트 코드를 전달하고 이후 브라우저가 자바 스크립트를 실행하여 데이터를 불러와 동적으로 웹 페이지를 생성한다.</li>
</ul>
<p> Jsoup은 SSR을 통해 미리 렌더링된 정적인 HTML 코드를 반환한다. Jsoup은 Http Request를 통해 서버에서 보내오는 HTML 문서를 그대로 받아와 사용하고 처리한다. </p>
<p>따라서, <strong>정적인 웹페이지</strong>를 크롤링하는데 다음과 강점을 가지고 있다.</p>
<ul>
<li><p>간편하고 직관적인 API</p>
</li>
<li><p>CSS 선택자를 사용해 요소를 선택하고 추출 ex) <strong><code>select</code></strong> 메서드</p>
</li>
<li><p>빠르고 경량화되어 좋은 성능을 보유</p>
</li>
<li><p>DOM 트리 형태로 HTML 문서 구조 접근에 용이하고, 이를 바탕으로 한 다양한 메서드들을 제공한다. ex) <strong><code>getElementById</code></strong>, <strong><code>getElementsByClass</code></strong>, <strong><code>getElementsByTag</code></strong></p>
<p>그럼 <strong>동적인 웹페이지</strong>에선 Jsoup이 왜 안좋을까? 여기엔 다음과 같은 이유가 있다.</p>
</li>
</ul>
<ol>
<li><p>Jsoup은 Http Request를 통해 서버에서 보내오는 HTML 문서를 그대로 받아와 사용하고 처리한다. </p>
</li>
<li><p>하지만 동적인 웹페이지에선 CSR 방식으로 웹페이지 초기 로딩시 레이아웃과 자바 스크립트 코드를 전달하고, 이후 브라우저가 자바 스크립트를 실행하여 데이터를 불러와 동적으로 웹 페이지를 생성한다. </p>
</li>
<li><p>때문에 웹 페이지가 로딩되고 난 후 웹 브라우저에서 자바스크립트가 실행되어야 하기 때문에 Http 요청만으로는 완전한 구조를 가져올 수 없기 때문에, Jsoup이 좋지 않다.</p>
<p>따라서, 동적인 웹페이지 처리하는 도구가 필요하다. 그래서 Selenium과 같은 라이브러리를 사용한다.</p>
</li>
</ol>
<h2 id="대표적인-동적-웹-페이지-도구-selenium">대표적인 동적 웹 페이지 도구 Selenium</h2>
<hr>
<blockquote>
<p>셀레늄은 웹 애플리케이션 자동화 및 테스트를 위한 포터블 프레임워크이다. 셀레늄은 테스트 스크립트 언어를 학습할 필요 없이 기능 테스트를 만들기 위한 플레이백 도구를 제공한다.
- <a href="https://ko.wikipedia.org/wiki/%EC%85%80%EB%A0%88%EB%8A%84_(%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4)">위키백과</a></p>
</blockquote>
<p> Selenium을 이용하면 동적 웹 페이지를 크롤링 할 수 있는데, Selenium의 동작 방식은 다음과 같다.</p>
<ol>
<li>웹 드라이버를 설정한다. 이는 각 브라우저별로 제공되는 독립 실행형 실행파일로, 각 브라우저와 통신 할 수 있게 도와준다.</li>
<li>웹 드라이버를 사용해 웹 브라우저를 열어 자동으로 제어할 수 있는 객체를 얻는다.</li>
<li>웹 페이지를 로드하고, DOM 트리를 구성한다.</li>
<li>자바스크립트를 하고 AJAX 요청을 처리해 동적으로 웹 페이지를 구성한다.</li>
<li>웹 페이지가 완전히 로드되면, 데이터를 추출한다.</li>
</ol>
<p> 동적인 웹페이지에선 <strong>브라우저</strong>가 자바 스크립트를 실행하여 데이터를 불러와 동적으로 웹 페이지를 생성한다.</p>
<p> 즉, Selenium은 웹 드라이버를 사용해 웹 브라우저를 활용해 자바 스크립트를 실행하여 웹 페이지를 그리는 것이다 !</p>
<h2 id="🤔-그래서-뭘-써야-할까">🤔 그래서 뭘 써야 할까?</h2>
<hr>
<p> 앞서 살펴본 동작방식에서 차이가 있다. 그래서 어떤 상황에서 무엇을 써야할까?</p>
<p> 정적인 웹페이지를 크롤링 해야되는 상황에는 Jsoup과 같은 라이브러리를 사용하면 좋은 이유는 다음과 같다.
 Jsoup은 웹 브라우저를 사용하지 않아 브라우저 자체의 로딩 시간과 자바스크립트 실행에 필요한 시간이 없다. URL을 이용해 웹 페이지를 로드하거나, 직접 HTML 코드를 입력해 로드가 가능하기 때문에, 빠르고 경량화 된 것이다.</p>
<p>동적인 웹페이지를 크롤링 해야되는 상황에는 Selenium과 Puppeteer와 같은 라이브러리를 사용하면 좋고, 이유는 다음과 같다.</p>
<p> Jsoup은 Http Request를 통해 서버에서 보내오는 HTML 문서를 그대로 받아와 사용하고 처리한다. 그래서 CSR 상황에는 적절하지 않고, 브라우저를 이용해 웹 페이지를 그리고 가져와야 하기 때문 Selenium과 Puppeteer와 같은 라이브러리를 사용하는 것이 좋다.</p>
<h2 id="알게-된-점">알게 된 점</h2>
<hr>
<p> 위와 같이 학습 내용을 정리하면서, 아래의 항목에 대해 깨닫게 되었다.</p>
<ul>
<li><p>CSR과 SSR의 차이</p>
</li>
<li><p>Jsoup과 Selenium의 동작 방식</p>
</li>
<li><p>DOM 트리</p>
</li>
<li><p>어떤 상황에서 어떠한 도구를 선택하는게 적절한지</p>
<p>크롤링을 처음하면서, 많은 내용을 학습하게 되었다. 예전부터 라이브러리를 선택할 때, 왜 그 라이브러리를 선택했고 어떤 방식으로 동작하는지 다른 라이브러리들과 차이점이 뭔지에 대해서 항상 고민하고 아는 것이 중요하다고 들었다. 이번 기회에 이와 같은 학습을 하면서 무지성으로 import 했던 자신에 대해 반성하게 되었던 것 같다.</p>
<p>앞으로는 라이브러리를 선택할 땐 적어도 왜 쓰는진 알고 선택해보자 !</p>
</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[리눅스와 쉘]]></title>
            <link>https://velog.io/@hyn_053/%EB%A6%AC%EB%88%85%EC%8A%A4%EC%99%80%EC%89%98</link>
            <guid>https://velog.io/@hyn_053/%EB%A6%AC%EB%88%85%EC%8A%A4%EC%99%80%EC%89%98</guid>
            <pubDate>Fri, 14 Jul 2023 09:18:28 GMT</pubDate>
            <description><![CDATA[<aside>
💡 쉘 스크립트에 관해 공부하면서 정리한 내용입니다.
</aside>


<h2 id="리눅스와-유닉스">리눅스와 유닉스</h2>
<hr>
<h3 id="유닉스">유닉스</h3>
<ul>
<li>다중 사용자, 다중 작업 환경에서 동작하는 운영 체제(OS)이다.<ul>
<li>CLI을 특징으로 하며, 터미널 기반의 작업을 지원한다.</li>
<li>강력한 기능과 안정성, 보안성을 갖춘 운영체제이다.<ul>
<li>권한과 접근제어를 강력하게 제어하고, 뛰어난 이식성과 표준화는 안정성과 보안성을 보장한다.</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="리눅스">리눅스</h3>
<ul>
<li>유닉스 계열의 오픈 소스 운영 체제이며, 리누스 토발즈에 의해 개발되었다.</li>
<li>오픈 소스 기반으로 개발되어, 누구나 소스 코드에 접근하고 수정할 수 있다.</li>
</ul>
<h3 id="차이점">차이점</h3>
<table>
<thead>
<tr>
<th>유닉스</th>
<th>리눅스</th>
</tr>
</thead>
<tbody><tr>
<td>소스 코드가 주로 사유적이다.</td>
<td>오픈 소스이다.</td>
</tr>
<tr>
<td>다수의 금융인프라 그리고 많은 가용솔루션의 뼈대를 이루는 인프라에 사용된다.</td>
<td>모바일폰, 태블릿 컴퓨터 그리고 비디오 게임 콘솔에서부터 메인프레임이나 슈퍼컴퓨터까지 다양한 컴퓨터 하드웨어에 설치가 가능하다.</td>
</tr>
<tr>
<td>OS X, Solaris 등</td>
<td>Ubuntu, Fedora, Red Hat, Debian 등</td>
</tr>
</tbody></table>
<h3 id="리눅스-명령어">리눅스 명령어</h3>
<ol>
<li><p><code>$ifconfig</code>: 네트워크를 구성하는 인터페이스를 확인하는 명령어로써 <strong>활성화</strong>된 인터페이스의 세부사항을 표시</p>
<ol>
<li><code>lo</code>: 루프백 인터페이스로 내부 통신용 네트워크 인터페이스이다. 자기 자신과의 통신을 위한 가상 이더넷 장치이다.</li>
<li><code>&lt;global&gt;</code>: 전역 IPv6 주소로, 글로벌 범위로 라우터를 통해 인터넷에 연결된 장치에 할당되는 주소이다. 이는 인터넷 상에서 고유한 주소로 사용되며, 인터넷으로부터 직접 접근 가능한 주소를 의미한다.</li>
<li><code>enp0s1</code>: Linux 시스템에서 인텔 이더넷 카드의 첫 번째 포트를 나타내는 네트워크 인터페이스의 이름이다. 이 네트워크를 이용해 ssh 접속을 한다. <code>&lt;global&gt;</code>을 이용해도 접속이 가능하다.</li>
</ol>
</li>
<li><p><code>$chmod [옵션] 모드 파일명:</code> chmod(change mode)의 줄임말로, 파일 및 디렉토리의 권한(permissions)을 변경하는 명령어이다.</p>
<ol>
<li><p>모드는 권한을 나타내는 숫자나 기호로 표현된다. 권한으로는 read, write, execute 3가지가 있다.</p>
<pre><code class="language-bash"># 숫자 모드 : 각 숫자 자리는 소유자, 그룹, 기타 사용자의 권한을 나타낸다.
# rwx -&gt; 4 2 1 로 7은 read, write, execute 3가지의 권한을 의미한다.
chmod 755 {파일명}
#기호 모드 : u(소유자), g(그룹), o(기타 사용자), a(모든 사용자), +(추가), -(제거), =(설정)
chmod u+rx {파일명}</code></pre>
</li>
</ol>
</li>
</ol>
<h2 id="쉘shell과-쉘-스크립트shell-script란">쉘(Shell)과 쉘 스크립트(Shell Script)란?</h2>
<hr>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/67f3af74-e981-460f-b8bc-63d984fb9168/image.png" alt="">리눅스의 구조(<a href="https://wogh8732.tistory.com/75">https://wogh8732.tistory.com/75</a>)</p>
<h3 id="쉘shell이란">쉘(Shell)이란?</h3>
<ul>
<li>운영체제의 커널과 사용자 간의 인터페이스로 동작하는 프로그램이다.</li>
</ul>
<aside>
💡 커널이란? 
운영 체제의 핵심 부분으로, 하드웨어와 응용 프로그램 사이의 인터페이스 역할을 한다.
시스템의 자원 관리, 프로세스 스케줄링, 메모리 관리, 입출력 처리 등 다양한 핵심 기능을 수행한다.

</aside>

<ul>
<li>사용자는 쉘을 통해 운영 체제에 명령을 내리고 프로그램을 실행할 수 있다.</li>
<li>초기화 파일을 이용해 사용자의 환경을 설정할 수 있다.<ul>
<li>환경 변수를 이용</li>
</ul>
</li>
<li>프로그래밍 기능이 내장되어 프로그램이 작성 가능하다. 쉘 프로그램을 <strong>쉘 스크립트</strong>라고 한다.</li>
</ul>
<h3 id="환경변수">환경변수</h3>
<ul>
<li>시스템 또는 사용자 환경에서 사용되는 변수, 프로그램이 실행되는 동안 값이 유지되는 데이터이다.</li>
<li>쉘 환경에서 정의되며, 주로 시스템 설정, 사용자 설정, 프로그램 설정 등을 저장하고 전달하는 데 사용된다.</li>
<li>환경 변수를 사용하면 시스템과 응용 프로그램의 설정을 유연하게 관리하고 다양한 환경에서 일관성을 유지할 수 있다. 또한, 동일한 환경 변수를 다른 시스템 또는 사용자 간에 공유하여 설정 값을 일관되게 유지할 수도 있다.</li>
</ul>
<p><img src="https://velog.velcdn.com/images/hyn_053/post/84d86e73-f600-48b4-a498-08c12111620b/image.png" alt="">
Shell 시작 및 로그인시 환경변수를 읽어들이는 순서(<a href="https://velog.io/@dhkim1522/%EB%A6%AC%EB%88%85%EC%8A%A4-%ED%99%98%EA%B2%BD%EB%B3%80%EC%88%98">https://velog.io/@dhkim1522/리눅스-환경변수</a>)</p>
<ul>
<li>환경 변수의 동작 범위<ul>
<li>로컬 환경 변수: 현재 세션에서만 동작하는 변수</li>
<li>사용자 환경 변수: 특정 사용자에 대해서만 정의된 환경 변수로 로컬 터미널 세션 또는 원격 로그인 세션을 사용하여 로그인 할 때만 로드된다.<ul>
<li>관련 파일: .bashrc, .bash_profile, bash_login, .profile</li>
</ul>
</li>
<li>시스템 환경 변수: 해당 시스템에 존재하는 모든 사용자가 사용할 수 있는 환경 변수로 시스템 전원이 켜져 있고 모든 사용자가 원격 또는 로컬로 로그인 할 때마다 로드된다.<ul>
<li>관련 파일: /etc/environment, /etc/profile, /etc/profile.d, /etc/bash.bashrc</li>
</ul>
</li>
</ul>
</li>
</ul>
<h3 id="쉘-스크립트shell-script란">쉘 스크립트(Shell Script)란?</h3>
<ul>
<li>쉘에서 실행되는 스크립트로, 쉘 명령어와 제어 구조를 사용하여 작성된 일련의 명령어 집합이다.</li>
<li>자동화된 작업을 수행하거나 반복적인 작업을 자동으로 처리 할 수 있다.</li>
<li>쉘 스크립트는 쉘 프로그래밍 언어로 작성된다.<ul>
<li>ex) bash, zsh, sh, csh, ksh, …</li>
</ul>
</li>
</ul>
<h3 id="bash와-zsh-차이">bash와 zsh 차이</h3>
<ul>
<li>이번에 사용한 쉘 스크립트인 bash와 zsh의 공통점, 차이를 알아보겠다.<ul>
<li>기본적으로 bash와 zsh은 명령어 인터프리터로서 사용자와 운영 체제 간의 상호 작용을 가능하게 하는데, zsh이 bash 보다 좀 더 많고 강력한 기능과 많은 커스터마이징을 지원한다.</li>
</ul>
</li>
</ul>
<table>
<thead>
<tr>
<th>bash</th>
<th>zsh</th>
</tr>
</thead>
<tbody><tr>
<td>리눅스의 표준 쉘이다.</td>
<td>bash 쉘의 확장 버전으로 많은 새로운 기능과 플러그인, 테마를 지원한다.</td>
</tr>
<tr>
<td>기본적인 자동 완성 제공</td>
<td>다양한 자동 완성 기능 제공(경로 완성, 명령어 옵션 완성, 변수 및 함수 이름 완성, …)</td>
</tr>
<tr>
<td>기본적인 프롬프트 외관과 동작을 변경 할 수 있는 설정 제공</td>
<td>다양한 프롬프트 테마와 옵션 설정 가능</td>
</tr>
</tbody></table>
<h2 id="쉘-스크립트-문법에-대한-간단한-예시">쉘 스크립트 문법에 대한 간단한 예시</h2>
<hr>
<ul>
<li><code>#!/bin/bash</code>: 쉘 스크립트의 첫 줄에 위치하는 특별한 주석으로, 스크립트를 실행하기 위해 사용할 쉘(Shell)의 경로를 지정한다.</li>
<li>변수<ul>
<li>쉘 스크립트의 변수에는 타입이 없다.</li>
<li><code>var=123</code> 이런식으로 변수를 설정한다.</li>
</ul>
</li>
<li>쉘 스크립트 연산자</li>
</ul>
<pre><code>| = | 변수에 값을 할당하는 연산자 | -d | 디렉토리의 존재 여부를 확인 |
| --- | --- | --- | --- |
| == | 문자열 비교 연산자 (같은지 여부를 확인) | -r | 읽기 권한 여부를 확인 |
| != | 문자열 비교 연산자 (다른지 여부를 확인) | -w | 쓰기 권한 여부를 확인 |
| -eq | 숫자 비교 연산자 (같은지 여부를 확인) | -x | 실행 권한 여부를 확인 |
| -ne | 숫자 비교 연산자 (다른지 여부를 확인) | -s | 파일이 비어있지 않은지 여부를 확인 |
| -lt | 숫자 비교 연산자 (작은지 여부를 확인) | -lt, -gt | 숫자 비교 연산자로 사용되어 숫자의 크기를 비교할 때 사용 |
| -gt | 숫자 비교 연산자 (큰지 여부를 확인) | -le, -ge | 숫자 비교 연산자로 사용되어 숫자의 크기 또는 같음을 비교할 때 사용 |
| -le | 숫자 비교 연산자 (작거나 같은지 여부를 확인) | -eq, -ne | 숫자 또는 문자열 비교 연산자로 사용되어 숫자나 문자열이 같은지 또는 다른지 여부를 비교할 때 사용 |
| -ge | 숫자 비교 연산자 (크거나 같은지 여부를 확인) | ! | 논리 NOT 연산자 (명령의 결과를 부정) |
| -z | 문자열 길이 비교 연산자 (빈 문자열인지 확인) | -a | 파일 및 디렉토리의 존재 여부를 확인하는 AND 연산자 |
| -n | 문자열 길이 비교 연산자 (빈 문자열이 아닌지 확인) | -o | 파일 및 디렉토리의 존재 여부를 확인하는 OR 연산자 |
| &amp;&amp; | 논리 AND 연산자 (앞의 명령이 성공하면 뒤의 명령 실행) | -e | 파일 또는 디렉토리의 존재 여부를 확인 |
| || | 논리 OR 연산자 (앞의 명령이 실패하면 뒤의 명령 실행) | -f | 일반 파일의 존재 여부를 확인 |</code></pre><ul>
<li>for문(반복문)</li>
</ul>
<pre><code class="language-bash">#기본 문법
for 변수 in 범위(배열, 리스트, ...)
do
        명령
done

#예제: 1..4로 특정 범위 특정 간격으로 증가 {1..4}로 하면 순차적으로 1씩 증가
for var in {1..4..2}
do 
    echo var
done
#출력
1
3

#예제: 배열을 이용한 예제
array=(1 2 3 4)
for var in &quot;${array[@]}&quot;
do
    echo var
done
#출력
1
2
3
4</code></pre>
<ul>
<li>if문</li>
</ul>
<pre><code class="language-bash">#기본 문법
if [값1 조건식 값2]; then
    수행문
else
    수행문
fi

#예제
if (($var = 1)); then
    echo &quot;value is 1&quot;
else
    echo $var
fi</code></pre>
<h2 id="개발환경">개발환경</h2>
<hr>
<ul>
<li>로컬에서 작업하던 코드나 프로젝트를 다른 물리적, 논리적 환경에서 실행할 필요가 있다.<ul>
<li>어플리케이션을 배포할 때, 다른 개발자와 협업시, 실제 운영 환경으로 이동해야 할 때 등과 같이 많은 이유가 있다.</li>
</ul>
</li>
<li>다른 컴퓨터에서 나의 개발 환경과 비슷하게 어떻게 만들 수 있을까?<ul>
<li>도커를 이용한 컨테이너, 가상 머신을 이용한 가상화를 통한 개발 환경 격리, 공유</li>
<li>환경 설정 및 패키지 관리 도구를 이용해 개발 환경을 비슷하게 만듦</li>
<li>환경 변수 관리, … 다양한 방법이 있다.</li>
</ul>
</li>
</ul>
<h2 id="마무리">마무리</h2>
<hr>
<p>리눅스와 쉘(Shell)에 대한 이해를 바탕으로 환경 변수가 로컬, 사용자, 시스템의 동작 범위에서 어떠한 실행 순서를 가지고 어떤 파일에서 설정하거나 가지고 있는지 알게 되었다. 또한 환경 변수를 사용하면 시스템과 응용 프로그램의 설정을 유연하게 관리하고 다양한 환경에서 일관성을 유지할 수 있다는 점 또한 알게 되었다.</p>
<p>또한, 일련의 동작을 구현하기 위한 쉘 스크립트로 프로그래밍을 하는 과정에서, 쉘 스크립트에 대한 내용과 다양한 리눅스 명령어들을 알게 되었다.</p>
<p>이번 학습 내용을 정리하면서 기존으로 알고 있던 내용들이 있었는데, 다시 한번 더 정리하면서 새롭게 알게 되는 내용이 있었다. 학습 시에 좀 더 깊게 파고드는 습관을 가지도록 노력해야겠다!</p>
]]></description>
        </item>
    </channel>
</rss>