<?xml version="1.0" encoding="utf-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom">
    <channel>
        <title>reading-snail17.log</title>
        <link>https://velog.io/</link>
        <description>책읽는 달팽이 || 공학도에서 개발자로!  || 결국 과거의 흐름을 이해했을 때 지금의 것들을 통찰력있게 바라볼 수 있다고 믿습니다.</description>
        <lastBuildDate>Thu, 21 Mar 2024 08:02:27 GMT</lastBuildDate>
        <docs>https://validator.w3.org/feed/docs/rss2.html</docs>
        <generator>https://github.com/jpmonette/feed</generator>
        <image>
            <title>reading-snail17.log</title>
            <url>https://velog.velcdn.com/images/reading-snail17/profile/e86f72bc-a97c-4936-9192-8d25666f15a2/image.jpeg</url>
            <link>https://velog.io/</link>
        </image>
        <copyright>Copyright (C) 2019. reading-snail17.log. All rights reserved.</copyright>
        <atom:link href="https://v2.velog.io/rss/reading-snail17" rel="self" type="application/rss+xml"/>
        <item>
            <title><![CDATA[조합형(NFD) vs 완성형(NFC)]]></title>
            <link>https://velog.io/@reading-snail17/%EC%A1%B0%ED%95%A9%ED%98%95NFD-vs-%EC%99%84%EC%84%B1%ED%98%95NFC</link>
            <guid>https://velog.io/@reading-snail17/%EC%A1%B0%ED%95%A9%ED%98%95NFD-vs-%EC%99%84%EC%84%B1%ED%98%95NFC</guid>
            <pubDate>Thu, 21 Mar 2024 08:02:27 GMT</pubDate>
            <description><![CDATA[<p>사내 사이드 프로젝트를 하던 도중 한글명을 갖는 파일들의 이름의 자음 모음이 분리되는 현상이 일어났습니다. 맥북이나 리눅스 노트북으로 확인하였을 때는 이상이 없었으나, 윈도우로 확인하였을 때 증상이 나타났습니다.</p>
<p>해당 문제는 한글의 입력 방식의 차이로 인해 발생하는 문제였습니다.</p>
<p>맥북 = <strong>조합형(NFD)</strong> + 완성형(NFC)</p>
<p>리눅스 =  조합형(NFD) + <strong>완성형(NFC)</strong></p>
<p>윈도우 = <strong>완성형(NFC)</strong></p>
<p>위의  나열한 것 처럼 맥북의 경우 조합형으로 작성하지만 완성형도 지원합니다. 윈도우의 경우 조합형은 지원하지 않고 완성형만 지원하며 작성도 완성형으로 이루어집니다.</p>
<p>이러한 차이로 인해 맥북에서는 모두 호환이 되다 보니 이상이 없었지만 윈도우의 경우 조합형을 인식하지 못하여 문제가 발생한 것이었습니다.</p>
<p>해결방법은 첨부파일을 업로드하기 이전에 NFD로 확인되는 파일은 NFC로 변환하여 업로드 하는 방법이었습니다. 단, 포맷이지만 사용자의 파일을 수정한다는 문제가 있어서 인지 구글드라이브에서는 이러한 기능이 추가가 안된 것 같습니다. 꼭, 도입을 해야되는가에 대해서는 비즈니스 로직과 사용자의 환경에 따라 다를 것 같습니다.</p>
<pre><code class="language-java">import java.text.Normalizer; 

public class Main { public static void main(String[] args) { 
    String input = &quot;NFD 를 NFC로 변환해주는 java 코드&quot;; 
    String normalized = normalizeToNFC(input); 
    System.out.println(&quot;NFD를 NFC로 변환한 결과: &quot; + normalized); 
} 

public static String normalizeToNFC(String input) { 
    return Normalizer.normalize(input, Normalizer.Form.NFC); 
    }                   
}</code></pre>
<p><a href="https://peterica.tistory.com/500">https://peterica.tistory.com/500</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[직열화 & 역직렬화]]></title>
            <link>https://velog.io/@reading-snail17/%EC%A7%81%EC%97%B4%ED%99%94-%EC%97%AD%EC%A7%81%EB%A0%AC%ED%99%94</link>
            <guid>https://velog.io/@reading-snail17/%EC%A7%81%EC%97%B4%ED%99%94-%EC%97%AD%EC%A7%81%EB%A0%AC%ED%99%94</guid>
            <pubDate>Tue, 05 Mar 2024 06:44:53 GMT</pubDate>
            <description><![CDATA[<h3 id="직렬화serialization란">직렬화(Serialization)란?</h3>
<p>직렬화는 객체를 바이트 스트림으로 변환하는 과정을 말합니다. 이러한 바이트 스트림은 파일로 저장하거나 네트워크를 통해 전송할 수 있습니다. 자바에서는 <code>Serializable</code> 인터페이스를 구현하여 객체를 직렬화할 수 있습니다.</p>
<h3 id="역직렬화deserialization란">역직렬화(Deserialization)란?</h3>
<p>역직렬화는 직렬화된 바이트 스트림을 다시 객체로 변환하는 과정을 말합니다. 이는 파일에서 읽어오거나 네트워크로부터 전송된 데이터를 다시 객체로 변환하는 데 사용됩니다.</p>
<h3 id="왜-사용하지-않는-것을-추천하는가">왜 사용하지 않는 것을 추천하는가?</h3>
<p>직렬화는 편리한 기능이지만 몇 가지 문제점이 있습니다. 일반적으로 직렬화된 데이터는 버전 관리, 보안 및 성능 문제로 인해 피하는 것이 좋습니다.</p>
<p>Effective Java의 저자는 무려 3개의 챕터에 걸쳐 직렬화를 사용하지 말 것을 당부합니다. 그리고 레거시 프로젝트를 다뤄야 되는 경우가 아니라면 사용할 필요가 없다고 단언합니다.</p>
<ol>
<li><p><strong>버전 관리</strong>: 직렬화된 데이터의 구조를 변경할 때 문제가 발생할 수 있습니다. 이는 애플리케이션 업데이트나 객체 구조 변경 시에 큰 문제가 될 수 있습니다.</p>
</li>
<li><p><strong>보안</strong>: 직렬화된 데이터는 보안에 취약합니다. 악의적인 사용자가 데이터를 변조하거나 악용할 수 있습니다.</p>
</li>
<li><p><strong>성능</strong>: 직렬화 및 역직렬화 작업은 비용이 많이 들 수 있습니다. 특히 대규모 데이터나 복잡한 객체의 경우 더욱 그렇습니다.</p>
<h3 id="대안은-무엇인가">대안은 무엇인가?</h3>
</li>
</ol>
<p>직렬화의 대안으로는 다음과 같은 것들이 있습니다:</p>
<ol>
<li><p><strong>JSON 또는 XML 직렬화</strong>: JSON 또는 XML과 같은 텍스트 기반 형식을 사용하여 데이터를 직렬화할 수 있습니다. 이는 버전 관리 및 보안에 더 안전하며, 다양한 플랫폼에서 호환성을 보장합니다.</p>
</li>
<li><p><strong>Protobuf 또는 Thrift</strong>: Google의 Protocol Buffers 또는 Apache Thrift와 같은 바이너리 직렬화 프레임워크를 사용할 수 있습니다. 이들은 직렬화된 데이터의 크기를 줄이고 성능을 향상시킬 수 있습니다.</p>
</li>
<li><p><strong>직렬화 대신 직접 데이터 처리</strong>: 필요한 경우 직렬화 대신 데이터를 직접 처리하는 방법을 고려할 수 있습니다. 이는 보안 문제를 회피하고 성능을 향상시킬 수 있습니다.</p>
</li>
</ol>
]]></description>
        </item>
        <item>
            <title><![CDATA[RESTAPI 명세서에 의문을 품다. (feats. HATEOAS)]]></title>
            <link>https://velog.io/@reading-snail17/RESTAPI-%EB%AA%85%EC%84%B8%EC%84%9C%EC%97%90-%EC%9D%98%EB%AC%B8%EC%9D%84-%ED%92%88%EB%8B%A4.-feats.-HATEOAS-7nshqmvz</link>
            <guid>https://velog.io/@reading-snail17/RESTAPI-%EB%AA%85%EC%84%B8%EC%84%9C%EC%97%90-%EC%9D%98%EB%AC%B8%EC%9D%84-%ED%92%88%EB%8B%A4.-feats.-HATEOAS-7nshqmvz</guid>
            <pubDate>Tue, 27 Feb 2024 08:24:58 GMT</pubDate>
            <description><![CDATA[<p>사이드 프로젝트 Yaksok을 진행하면서 처음으로 RESTAPI를 기반으로 프론트앤드와 백엔드의 업무가 완전히 분리된 개발 경험을 하고 있습니다.</p>
<p>모두 열정은 가득하지만 경험은 많지 않아 삽질의 연속이었는데 여정을 요약하면 아래와 같습니다.</p>
<p>열정 가득한 우리는 무엇이 필요한지도 모른 채 열정을 표출하며 각자 할 줄 아는 것들을 해나가기 시작했습니다. FE는 BE가 잘 데이터를 보내주겠지, BE는 FE가 데이터를 잘 받겠지라는 공상을 하며 각자 할 줄 아는 것들부터 먼저 해나갔습니다.</p>
<p>그러던 중, 뒤늦게 우리의 작업하는 모습을 본 3년차 개발자 분께서 보시고는... 이게 무슨일인가? 라고 외치는 사태가 벌어졌습니다. 동료분의 진단은 RESTAPI 명세서가 필요하다는 부분이었습니다. 프론트와 백의 긴밀한 소통은 기본적으로 이러한 명세서로 이루어져야만 하는 것인데 이런 인터페이스가 없으니 방향을 못잡고 있다는 것이었습니다. 우리 모두 용감했지만 경험이 없었던 개발과정을 복기 하며 명세서 작성이 들어갔습니다.
<a href="https://wonit.tistory.com/454">https://wonit.tistory.com/454</a>
명세서 작성은 GoolgeDocs로 이루어졌습니다. 동시 작성이 가능하다는 점에서 모두 함께 빠르게 작성해 나갈 수 있었습니다. 각자 작성을 시작했고, 의문이 남는 부분은 회의 시간에 해소해 나갔습니다. 그렇게 API문서의 작성이 완료되었을 때 의문이 들었습니다. </p>
<p>RESTAPI 명세서를 작성하였지만, 프론트엔드에서는 그래서 어떤 요청을 보내야 되는 것인가에 대한 물음이 여전히 들려왔습니다. API의 구조는 알겠으나 세부적으로 어떤 값을 보내주어야 되는지에 대해서 명확하지 않아 어렵게 느껴진다는 것이었습니다. 서로의 경험이 부족하여 이런 풀리지 않는 의문이 있구나 하는 생각이 먼저 들었지만 한편으로는 어떤 개발자가 와도 이해할 수 있고, 바로 코드를 구현해 나갈 수 있는 환경과 방법이 더 좋은 방향이라는 생각이 머리를 떠나지 않았습니다.</p>
<p>그러한 의문점을 갖고 있었는데 오늘 나름의 개선할 수 있는 방법에 대한 힌트를 찾게 되었습니다.
해당 개념은 HATEOAS 였습니다. HATEOAS는 <strong>H</strong>ypermedia <strong>A</strong>s <strong>T</strong>he <strong>E</strong>ngine <strong>o</strong>f <strong>A</strong>pplication <strong>S</strong>tate의 약자로 결국 Link라는 키 값에 프론트에서 요청할 수 있는 API나 여러 페이지 및 리소스들의 경로는 함께 제공한다는 개념입니다. 사실 RESTful에 있어서 어느 정도의 입지를 갖고 있는 개념이지만 보통 메서드와 URI 네이밍 규칙에 가려 잘 고려되지 않는 개념이었습니다.</p>
<p>HATEOAS를 구현한 HAL-JSON은 아래와 같습니다. 
백엔드에서는 응답을 보낼 때, 프론트에서 필요할 수 있는 모든 요청 URI를 links라는 속성에 담아 함께 보내줍니다. 프론트에서는 쉽게 해당 URI를 담고 있는 key를 사용하여 다음 요청을 보낼 수 있게 하는 구조입니다.</p>
<p>모든 URI를 보내주어야 된다는 사실에서 오는 공수가 있기 때문에 아주 보편적으로 사용되지는 않는 것 같습니다. 현재 진행중인 프로젝트에서도 사용할지 말지에 대한 고민도 더 필요할 듯 해 보입니다. 하지만 프론트와 백의 소통에 있어서 좋은 가이드가 될 수 있는 기능이라는 생각이 들었습니다.</p>
<pre><code>{
&quot;data&quot;: 
  { // HAL JSON의 리소스 필드 
    &quot;id&quot;: 1000, 
    &quot;name&quot;: &quot;게시글 1&quot;, 
    &quot;content&quot;: &quot;HAL JSON을 이용한 예시 JSON&quot; }, 

    &quot;_links&quot;: { // HAL JSON의 링크 필드 
    &quot;self&quot;: { &quot;href&quot;: &quot;http://localhost:8080/api/article/1000&quot;}, // 현재 api 주소 
    &quot;profile&quot;: { &quot;href&quot;: &quot;http://localhost:8080/docs#query-article&quot;}, // 해당 api의 문서  
    &quot;next&quot;: { &quot;href&quot;: &quot;http://localhost:8080/api/article/1001&quot;}, // article 의 다음 api 주소  
    &quot;prev&quot;: { &quot;href&quot;: &quot;http://localhost:8080/api/article/999&quot;} // article의 이전 api 주소  
  } 

}</code></pre><p>참조: <a href="https://wonit.tistory.com/454">https://wonit.tistory.com/454</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[캐시 관리]]></title>
            <link>https://velog.io/@reading-snail17/%EC%BA%90%EC%8B%9C-%EA%B4%80%EB%A6%AC</link>
            <guid>https://velog.io/@reading-snail17/%EC%BA%90%EC%8B%9C-%EA%B4%80%EB%A6%AC</guid>
            <pubDate>Tue, 13 Feb 2024 11:21:42 GMT</pubDate>
            <description><![CDATA[<p>회사에서 운영 테스트 중 캐시에 대한 문제가 대두 되었습니다. 캐시가 클라이언트들의 브라우저에 저장 되어있어 최신 소스로 재배포 해도 좀 처럼 반영이 되지 않았고, 고객들에게 캐시 비우기 및 강력 새로고침을 방법을 알려주는 메뉴얼을 배포하여하는 해프닝이 벌어졌습니다.</p>
<p>&lt;이게 맞나?&gt; 라는 생각이 들었고, 캐시 관련 내용을 찾아보았습니다.</p>
<p>먼저는 블로그, GPT, 도서를 모두 읽어가면서 찾아보았으나 각각의 설정들의 의미가 모호하다고 느껴지는 부분이 있었고, 해서 최종적으로 StackOverFlow에서 세부적인 설정을 보면서 이해할 수 있었습니다.</p>
<h3 id="캐시를-관리는-http-프로토콜">캐시를 관리는 HTTP 프로토콜!</h3>
<p>HTTP 헤더에는 Cache-Control 이라는 설정이 있으며 하위 설정 가능 항목으로는 세 가지가 있습니다.</p>
<ol>
<li>no-store: 캐시를 저장하지 않음</li>
<li>no-cache: 캐시를 저장하지만 서버에서 최신본인지 검증 후 사용</li>
<li>must-validate: max-age에 설정 되어있는 유효기간이 지나지 않았으면 캐시를 바로 사용하고, 유효기간이 지나면 서버에서 최신소스를 검증 후 사용.
no-cache 와 must-validate의 의미의 차이를 파악하기가 가장 어려웠는데 결론은 둘 다 서버에서 최신소스를 검증하지만 no-cahce는 항상 검증하고, must-validate는 유효기간 이후에 검증하게 됩니다.</li>
</ol>
<h3 id="그러나-결국-표준은-없다">그러나 결국 표준은 없다.</h3>
<p>그러나 표준에 모호한 부분이 많은 http 프로토콜 결국 상황에 따라 다르게 설정하게 됩니다.
이유는 브라우저마다 호환이 다르고, 버그도 종종 있기 때문입니다. 세부적인 내용은 아래의 링크를 참조하면 좋을 것 같습니다. 단, 예시로 정리되어있는 내용들은 캐시를 절때로 생성되지 않기 위한 설정값들 이므로 필요에 따라 처리해야 될 것입니다. </p>
<ul>
<li><a href="https://stackoverflow.com/questions/49547/how-do-we-control-web-page-caching-across-all-browsersCashe">How do we control web page caching across all browsers (link)</a></li>
</ul>
<h3 id="apache-htaccess-file">Apache <code>.htaccess</code> file:</h3>
<p>현재 프로젝트의 경우 아파치 서버를 사용하고 있기 때문에 아래와 같은 설정이 적용되었다면 오픈기간 중에 발생한 혼란이 최소화 되지 않았을까 싶습니다.</p>
<ul>
<li>HTTP1.1 이상을 사용하는 환경이므로 Expires 0 x</li>
<li>캐시가 필요는 하기 때문에 no-strore 설정 x</li>
<li>IE 호환이 필요 없으므로 Pragma 설정 x </li>
</ul>
<pre><code class="language-xml">&lt;IfModule mod_headers.c&gt;
    Header set Cache-Control &quot;&lt;!--no-store,--&gt; no-cache, must-revalidate&quot;
    &lt;!-- Header set Pragma &quot;no-cache&quot;--&gt;
    &lt;!-- Header set Expires 0 --&gt;
&lt;/IfModule&gt;</code></pre>
]]></description>
        </item>
        <item>
            <title><![CDATA[@RequestParam vs @RequestPart 파일 업로드]]></title>
            <link>https://velog.io/@reading-snail17/RequestParam-vs-RequestPart-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C</link>
            <guid>https://velog.io/@reading-snail17/RequestParam-vs-RequestPart-%ED%8C%8C%EC%9D%BC-%EC%97%85%EB%A1%9C%EB%93%9C</guid>
            <pubDate>Sat, 10 Feb 2024 09:15:16 GMT</pubDate>
            <description><![CDATA[<h3 id="오잉-requestparam이-파일-전송이-가능하다고">오잉, @RequestParam이 파일 전송이 가능하다고?</h3>
<p><code>@RequestParam</code>이 파일 전송이 가능할 것이라는 생각은 처음에 하지 못했습니다. 왜냐하면, get방식 처럼 ? 뒤에 오는 query 내용을 다루는 애노테이션이라고 생각하여 4000 또는 8000자 제한이 있는 URL을 사용하기는 불가능하다고 생각하였기 때문입니다. 그러나 블로그들에 많은 예시가 보였고, 좀 더 찾아보니 <code>@RequestParam</code>은 단순히 url의 query 말고도 form 방식으로 담겨온 body에 application/x-www-form-urlencoded 방식으로 담긴 내용도 다룬다는 것을 알게되었습니다.</p>
<p>해당 내용들을 요약하면 아래와 같습니다.</p>
<h3 id="requestparam">@RequestParam:</h3>
<ul>
<li><strong>특징</strong>:<ul>
<li>HTTP 요청의 쿼리 매개변수 또는 폼 데이터를 수신할 때 사용됩니다.</li>
<li>주로 단일 값 또는 파일이 하나만 업로드 되는 경우에 사용됩니다.</li>
<li><code>Content-Type: application/x-www-form-urlencoded</code> 또는 <code>Content-Type: multipart/form-data</code>와 함께 사용됩니다.</li>
</ul>
</li>
<li><strong>장점</strong>:<ul>
<li>단일 파일 또는 값에 대한 간단한 업로드를 처리하기에 적합합니다.</li>
<li>단일 파일의 경우에는 추가 설정이 필요하지 않습니다.</li>
</ul>
</li>
<li><strong>단점</strong>:<ul>
<li>멀티파트 요청에서 여러 파일을 동시에 업로드하는 데 적합하지 않습니다.</li>
<li>멀티파트 데이터의 한 부분만을 처리할 수 없습니다.</li>
</ul>
</li>
</ul>
<h3 id="requestpart">@RequestPart:</h3>
<ul>
<li><strong>특징</strong>:<ul>
<li>멀티파트 요청(Multipart Request)에서 한 부분으로부터 데이터를 수신할 때 사용됩니다.</li>
<li>주로 파일 업로드를 처리하는 데 사용됩니다.</li>
<li>일반적으로 <code>Content-Type: multipart/form-data</code>인 요청에서 사용됩니다.</li>
</ul>
</li>
<li><strong>장점</strong>:<ul>
<li>멀티파트 요청에서 파일 및 기타 파트의 데이터를 동시에 처리할 수 있습니다.</li>
<li>멀티파트 데이터를 각각의 파라미터로 매핑하기 쉽습니다.</li>
</ul>
</li>
<li><strong>단점</strong>:<ul>
<li>파라미터가 한 개 이상인 경우 <code>@RequestPart</code>를 사용하여 여러 개의 파일을 동시에 업로드하는 것은 복잡할 수 있습니다.</li>
</ul>
</li>
</ul>
<h3 id="요약">요약:</h3>
<ul>
<li><code>@RequestPart</code>는 멀티파트 요청에서 한 부분으로부터 데이터를 수신하고, 파일 업로드와 멀티파트 데이터 처리에 적합합니다.</li>
<li><code>@RequestParam</code>은 단일 값 또는 파일 업로드에 사용되며, 단일 파일의 경우 간편하게 사용할 수 있습니다.</li>
<li>선택은 요청의 형식과 업로드하는 데이터의 종류에 따라 달라집니다. 하나의 파일을 업로드하는 경우에는 <code>@RequestParam</code>을 사용하고, 여러 파일 또는 파일과 다른 데이터를 동시에 업로드하는 경우에는 <code>@RequestPart</code>를 사용하는 것이 좋습니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[properties 파일 스캐닝 규칙]]></title>
            <link>https://velog.io/@reading-snail17/properties-%ED%8C%8C%EC%9D%BC-%EC%8A%A4%EC%BA%90%EB%8B%9D-%EA%B7%9C%EC%B9%99</link>
            <guid>https://velog.io/@reading-snail17/properties-%ED%8C%8C%EC%9D%BC-%EC%8A%A4%EC%BA%90%EB%8B%9D-%EA%B7%9C%EC%B9%99</guid>
            <pubDate>Sat, 10 Feb 2024 09:05:28 GMT</pubDate>
            <description><![CDATA[<p>스프링 부트의 파일 자동 스캐닝 기능을 오해하여 헤매는 경험을 하였습니다. 설정에 따른 정확한 기능과 차이를 기록해주려고 합니다</p>
<p>우선 잘못 알고 있었던 것은 자동 스캐닝은 .properties 파일을 자동으로 스캐닝 하는 것이 아닌 application.properties 파일을 기본 경로인 classpath:/ 와 classpath:/config 에서 자동으로 스캐닝 하는 것이었습니다.</p>
<p>하여 application-database.properties 또는 database.properties 파일이 자동으로 위의 경로에 저장되어 있더라도 스캐닝이 되지 않는 것이 정상이었습니다.</p>
<p>설정 파일 스캐닝 관련 설정들을 정리해보면 아래와 같습니다.</p>
<ul>
<li>스프링 부트는 application.properties / application.yml 파일을 classpath:/ / classpath:/config 에서 자동으로 스캐닝 한다.</li>
<li>자동 스캐닝할 경로를 추가로 지정해주고 싶다면 spring.config.location으로 경로를 지정하면 된다. classpath:/ / classpath:/config 에 추가로 다른 경로를 설정한다고 생각하면 된다.</li>
<li>단, 위의 경로 지정으로 파일의 설정 값을 가져오는 것은 아니다. 추가적으로 spring.config.import로 파일이름을 지정해주어야 된다. 그러면 spring.config.location 또는 기본 경로에서 해당 파일의 설정값을 불러오게 된다.</li>
</ul>
<p>다시 돌이켜보면 당연한 사실들인데 하나에 꽂히고 나니 생각의 틀에서 벗어나기 어려웠다.</p>
<p>.properties 형식을 사용한다면 여러 파일을 추가할 때 spring.config.import을 매번 추가해주어 가독성을 올려주면 좋을 듯 합니다. .yml은 자체로 가독성이 높을 수 있게 여러 파일을 나열 할 수 있어 괜찮은 것 같습니다.</p>
<p>P.S. 설정이 적용이 되었는지 확인하는 과정에 db설정 값을 사용했는데 h2 의존성이 추가 되어있어 서버 실행 시 database가 지정되어있지 않다는 오류를 뱉어내지 않는 경우들이 있어 더 오해가 커졌었습니다. 의존성 설정에 따라 설정 값이 달라지는 스프링 부트의 특징을 잘 알고 사용해야 될 듯 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[커스텀 애노테이션: @Retension]]></title>
            <link>https://velog.io/@reading-snail17/Retension-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%95%A0%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98</link>
            <guid>https://velog.io/@reading-snail17/Retension-%EC%BB%A4%EC%8A%A4%ED%85%80-%EC%95%A0%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98</guid>
            <pubDate>Tue, 06 Feb 2024 13:55:04 GMT</pubDate>
            <description><![CDATA[<p>우선, 애노테이션은 애노테이션 자체로서는 주석 그 이하도 이상도 아님을 알아야 합니다. 단, 일반적인 주석과 달리 그 대상이 컴퓨터가 읽는 주석이라고 생각하면 되는 부분입니다.</p>
<p>애노테이션에서 @Retension은 애노테이션의 유효한 범위를 알려줍니다.  즉, 스코프 설정입니다.</p>
<p>3가지 다른 설정이 가능한데 각각의 유효한 시점이 다릅니다.
source &lt; class &lt; runtime 순으로 범위가 넓어집니다.</p>
<ol>
<li>source: 소스에서만 유효합니다. 대표적인 예시는 lombok의 @Getter/@Setter로 java 파일의 소스를 변경할 때 사용됩니다.</li>
<li>class: 컴파일 시점까지 유효합니다. 외부 라이브러리 파일들에 영향을 받을 경우 source에서 확장하여 class로 지정해야합니다.</li>
<li>runtime: 웹을 예시로 들면 tomcat과 같은 서버에서 프로그램이 실행중일 때도 유효합니다. @Service, @Controller와 같이 빈 스캐닝 기능이 필요할 때 사용 될 수 있습니다.</li>
</ol>
<p>즉, Runtime의 경우 모든 범위를 포괄하기에 항상 사용될 수 있지만 자원의 절약 및 최적화를 위해 source와 class를 코드의 의도에 맞게 함께 사용해주어야 합니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git 수련기..]]></title>
            <link>https://velog.io/@reading-snail17/Git-%EC%88%98%EB%A0%A8%EA%B8%B0</link>
            <guid>https://velog.io/@reading-snail17/Git-%EC%88%98%EB%A0%A8%EA%B8%B0</guid>
            <pubDate>Thu, 01 Feb 2024 13:36:20 GMT</pubDate>
            <description><![CDATA[<p>git이라는 툴이 그냥 사용하다 보면 익혀지지 않을까라는 생각으로 몇차례 사용해보았습니다. 그러나 좀처럼 git이라는 툴에 대한 이해도는 올라오지 않았고, 사내 프로젝트는 SVN을 사용하다 보니 Git과는 더 멀어졌다는 위기감을 느끼게 되었습니다.</p>
<p>해서 본격적으로 git에 대해서 학습하고, 실제로 사용해보며 체득해보려고 합니다</p>
<h1 id="git">Git</h1>
<p>먼저, &#39;팀개발을 위한 Git,GitHub를 시작하기&#39;를 읽으며 머리 속에 파편화 되어있거나 비어있었던 git의 사용 방법과 세부적인 내용들을 익혔습니다. 특히 와닿았던 부분은 아래와 같습니다.</p>
<ul>
<li>git clone &lt;address&gt; . <br>. 을 clone 시 마지막에 붙일 경우 현재 디렉토리를 기준으로 clone을 받아올 수 있어 해당 폴더에 한번 더 들어가서 git을 사용하지 않아도 된다.</li>
<li>git을 커밋할 때 가장 중요한 개념은 stages 개념이다. 원하는 변경사항만 골라 무대에 올리고 스냅샷어 커밋으로 기록합니다.</li>
<li>병합이 이루어지는 방식은 세 가지 입니다.<ol>
<li>fast-forword: 소스들이 부모자식 관계이므로 최신소스로 합쳐집니다.</li>
<li>merge: 각각의 파일의 소스가 다르지만 충돌지점이 없어 정상적으로 병합됩니다.</li>
<li>conflict: 충돌이 발생한 것이므로 해당 파일을 열어 소스를 정리해 주어야 합니다.</li>
</ol>
</li>
<li>병합을 하는 방법은 크게 세 가지로 나누어집니다.<ol>
<li>merge</li>
<li>rebase</li>
<li>cherry-pick</li>
</ol>
</li>
<li>git pull --rebase를 사용하면 remote 브랜치를 변경하지 않으면서 나의 브랜치에 깔끔하게 받아올 수 있습니다. (단, 역사를 부정하는 일이니 가급적 merge가 좋아보입니다)</li>
</ul>
<h1 id="git-branch">Git Branch</h1>
<p>git과 다른 vcs의 가장 큰 차이점 중 하나가 branch 입니다. 그리고 제가 이해한 branch는 commit과 같이 이해했을 때 그 효용성에 대한 이해가 극대화 될 수 있었습니다.</p>
<p>브랜치를 잘 활용하기 위해서 먼저  커밋을 사용자의 마음대로 옮기고, 합치는 등의 기능을 자유롭게 구사할 수 있어야 합니다. 커밋을 내 맘대로 지지고 볶을 수 있게 되면 브랜치를 사용하는 이유와 커밋에 대해서도 쉽게 이해할 수 있게 됩니다.</p>
<p>해서 먼저 선행되어야 되는 것은 git의 가장 기초적인 명령어들과 그중에서도 merge, rebase, cherry-pick에 대한 이해도를 높이는 것입니다. git 명령어들을 가장 직관적이고 빠르게 익힐 수 있는 방법은 <a href="https://learngitbranching.js.org/?locale=ko">Learning git branching</a> 라고 불리는 게임을 통한 학습입니다. 해당 게임은 직관적인 커밋과 브랜치의 구성과 이동 동작들을 동적으로 표현해주어  git의 브랜치와 커밋에 대한 이해도를 빠르게 올려줍니다.</p>
<h1 id="git-commit">Git Commit</h1>
<p>위의 게임을 하여 브랜치에 대한 이해도가 어느 정도 올라오면 실제 프로젝트에서 git을 사용해봅니다. </p>
<h3 id="1-기차-커밋-만들어-보기">1. 기차 커밋 만들어 보기</h3>
<p>브랜치를 통해서 먼저 기능들을 분리하여 시작해보기보다는 먼저는 하나의 브랜치에 커밋들을 기차처럼 줄줄이 생성해 봅니다. 대신 커밋들을 생성할 때, merge/rebase/cherry-pick을 사용하여 추후에 기능별로 별도의 브랜치로 분리할 것을 고려하여 생성합니다. 이런 기준을 잡고 시작하면 add . 명령어를 통해 모두 커밋 하기보다는 기능별로 분류하여 커밋하는 습관이 생기게 됩니다. 
(git 명령어로 하다 보면 자연스럽게 GUI 툴을 찾게 됩니다.)</p>
<h3 id="2-기차-커밋-쪼개서-브랜치-만들어보기">2. 기차 커밋 쪼개서 브랜치 만들어보기</h3>
<p>어느정도 기차가 늘어났으면 이제 시작점이 되는 커밋을 체크아웃 받아 기능별로 브랜치를 만들고, 커밋들을 분류하여 옮겨 닮습니다. 필요에 따라 rebase, cherry-pick을 사용합니다. 보통 별개의 기능들을 각각의 브랜치에 옮겨 닮는 것이므로 merge는 아직 사용할 일이 없을 것 같습니다.</p>
<h3 id="3-브랜치로-먼저-쪼개서-커밋들-생성하기">3. 브랜치로 먼저 쪼개서 커밋들 생성하기</h3>
<p>이러한 경험이 생기면 이제 git을 커밋하는 이유에 대한 이해도가 대폭 상승합니다.
브랜치에 대한 이해도 단단해집니다. 커밋과 브랜치에 대한 이해가 완료 되면 이제 다음 진행 사항들을 자연스럽게 브랜치를 먼저 나누고 시작하는 것이 가능해지게 됩니다. 또 만약 실수하더라도 복구할 수 있다는 자신감이 붙습니다.</p>
<p>결론은 &#39;커밋을 최대한 자주 하되 가급적 의미에 맞게 쪼갭니다&#39; 그러면 후에 처리하여 main에 merge 하기 수월 할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[PUT,DELTE 메서드가 위험하다고?]]></title>
            <link>https://velog.io/@reading-snail17/PUTDELTE-%EB%A9%94%EC%84%9C%EB%93%9C%EA%B0%80-%EC%9C%84%ED%97%98%ED%95%98%EB%8B%A4%EA%B3%A0</link>
            <guid>https://velog.io/@reading-snail17/PUTDELTE-%EB%A9%94%EC%84%9C%EB%93%9C%EA%B0%80-%EC%9C%84%ED%97%98%ED%95%98%EB%8B%A4%EA%B3%A0</guid>
            <pubDate>Thu, 01 Feb 2024 13:29:27 GMT</pubDate>
            <description><![CDATA[<p>HTTP 메서드의 PUT과 DELETE를 Tomcat과 Apache 서버에서 차단해달라는 요청이 있었습니다.
우선은 나름의 근거를 제시해왔고, 제작된 제품도 PUT, DELETE 메서드가 필수적인 RESTAPI방식이 아닌 SOAP 방식이었으므로 차단할 수 있는 설정을 추가하였습니다.</p>
<p>그러나 왜? 라는 질문이 남았습니다.</p>
<p>분명 너무나도 많은 사람들이 이미 REST API를 사용하고 있으니 말입니다. 처음에는 스프링 시큐리티에서 추가적으로 관리하면 상관 없겠지라는 생각이 들었지만 서버단은 시큐리티보다도 전방에서 작동하는 기능이니 시큐리티 문제가 아니었습니다.</p>
<p>검색을 하다가 그저 와전되어 생겨난 낭설이라는 결론에 도달했습니다.</p>
<p>Tomcat이 아닌 C++ 진영의 서버인 IIS에서 모든 http 메서드를 허용할 경우 자동 설정되는 WebDev라는 기능에서 발생하는 취약점을 임시적으로 막기 위해 PUT, DELETE를 차단하던 것이 와전되어 모든 서버들에 대한 권고사항이 되어버린 것이었습니다.</p>
<p>만약 사용하지 않는다면 굳이 http 메서드들을 허용할 필요는 없겠지만 이 부분 때문에 REST API를 포기할 필요도 없다는 결론이었습니다.</p>
<p>모든 것에는 이유가 있는 것 같습니다. 때로는 회사 내 분위기와 제품의 성능 상의 문제가 없을 경우  갑론을박을 굳이 할 필요는 없겠지만, 왜 해야 되는지. 꼭 해야 되는지는 짚고 넘어가야 되는 부분인 것 같습니다.</p>
<p><a href="https://blog.naver.com/PostView.nhn?blogId=ks2414e&amp;logNo=222147820057&amp;parentCategoryNo=&amp;categoryNo=93&amp;viewDate=&amp;isShowPopularPosts=true&amp;from=search">https://blog.naver.com/PostView.nhn?blogId=ks2414e&amp;logNo=222147820057&amp;parentCategoryNo=&amp;categoryNo=93&amp;viewDate=&amp;isShowPopularPosts=true&amp;from=search</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Git/GitHub 정리]]></title>
            <link>https://velog.io/@reading-snail17/GitGitHub-%EC%A0%95%EB%A6%AC</link>
            <guid>https://velog.io/@reading-snail17/GitGitHub-%EC%A0%95%EB%A6%AC</guid>
            <pubDate>Thu, 25 Jan 2024 13:49:54 GMT</pubDate>
            <description><![CDATA[<h2 id="다시-git으로">다시 Git으로..</h2>
<p>회사에서는 SVN을 사용하다보니 git을 사용할 기회가 점차 적어졌습니다.
그러다가 다음 프로젝트에서 사용할 표준을 만드는 과정에서 git을 적극적으로 도입하게 되었습니다.
이전에 git을 사용한 적은 있지만 본격적으로 이해해서 사용하기보다는 야생적으로 그때그때 휘뚜루마뚜루 사용했던 기억이 많습니다. 몇몇 명령어를 이해하고는 있었으나 큰 틀에서 각각의 개념을 잡지 못하고 있다보니 다시 사용해보면서 지식부채에서 오는 한계를 느끼게 되었습니다.</p>
<p>&#39;팀개발을 위한 Git,GitHub 시작하기&#39;를 읽는 것으로 학습을 시작하였고, 현재 해당 도서와 도서에서 추천한 &#39;Learning Git Branching&#39; 게임을 완료한 단계에 있습니다. 이제 작업 중인 프로젝트에 적극적을 활용만 해보면 됩니다.</p>
<p>아래의 내용을 학습과정에 중요하다고 느낌 부분에 대한 정리입니다.</p>
<h2 id="팀개발을-위한-gitgithub-시작하기">&#39;팀개발을 위한 Git,GitHub 시작하기&#39;</h2>
<h3 id="git-clone--">git clone &lt;&gt; .</h3>
<p>클론을 받아올 때마다 디렉토리가 한 단계 더 깊이 들어갔고, 이 때문에 불편함을 느끼고 있었습니다. 책을 읽다보니 이는 불가항력적인 부분이 아닌 clone 구문에서 마지막 부분에 . 을 추가하는 것으로 현재 디렉토리를 기준으로 한다는 사실을 명시해 주어 해결 할 수 있는 부분이었습니다.</p>
<h3 id="스냅샷과-stage">스냅샷과 stage</h3>
<p>SVN은 델타 방식으로 접근하여 모든 변화된 내용을 누적하여 저장 했다가 필요시 모두 불러와 합쳐서 전달해주는 방식입니다. 이는 속도가 느리고, 브랜치를 사용하기 어렵다는 단점이 있습니다. 반면에 git은 스냅샷이라는 개념으로서 모든 순간순간을 통으로 사진 찍듯이 저장하여 사용합니다.</p>
<p>이는 Stage라는 명령어 및 표현에서 대두 되는데 스냅샨을 stage에 올려놓은 파일들을 찍어 기록한다는 의미가 됩니다. 작업해 놓은 파일을 git add 명령어를 사용하여 stage에 올릴 수도 있고, git reset을 사용하여 내릴 수도 있습니다.</p>
<h3 id="merge-상황">Merge 상황</h3>
<p>merge를 진행하면 세가지 중 하나의 상황에 부딪히게 됩니다.</p>
<ol>
<li>merge commit: 각각의 브랜치에 충돌이 발생할 교집합이 없어 이상없이 병합됩니다. </li>
<li>fast-forward: 한쪽의 브랜치가 다른한쪽의 부분집합관계로 더 큰 집합으로 병합됩니다.</li>
<li>conflict: 두 브랜치 사이에 교집합이 있어 충돌이 발생한 상황으로 직접 코드를 정리해주어야 합니다.</li>
</ol>
<h3 id="merge-명령어">Merge 명령어</h3>
<p>git에서는 브랜치들 간의 병합하는 방법이 3가지 입니다.</p>
<ol>
<li>merge : A브랜치와 B브랜치를 별도의 A&#39;브랜치에 합쳐 병합합니다.</li>
<li>rebase : A브랜치를 통으로 뽑아 내가 원하는 커밋 위치에 연결합니다.</li>
<li>cherry-pick : 커밋들을 받을 브랜치에서 원하는 커밋들을 뽑아 하나하나 연결합니다.</li>
</ol>
<h2 id="learning-git-branching">&#39;Learning Git Branching&#39;</h2>
<p>독서를 통해 학습한 이론을 간단한 예시를 보면서 학습할 수 있어서 너무나도 만족스러운 학습이었습니다. 특히 여러명이서 동시의 사용하는 과정에서 만나게 되는 상황들의 경우 재현하기 어려워 연습해보기 어려웠고, 막상 그렁 상황이 생겨도 이론과 관계 지어가며 내재화 되는 학습이 어려웠는데, 이 게임이 도움이 많이 되었습니다.</p>
<h3 id="git-pull---rebase">git pull --rebase,</h3>
<p>git fetch -&gt; git merge -&gt; git rebase로 원격 저장소의 커밋을 받아온 후 충돌을 해결하는 방식의 명령어 입니다. 단순히 하나의 명령어가 아닌 내부적인 동작 원리를 알 수 있어 이해가 쉬웠습니다.</p>
<h3 id="merge-vs-rebase-vs-cherry-pick">merge vs rebase vs cherry-pick</h3>
<p>여러 병합 방법들을 여러차례 연습하게 해주어 차이에서 오는 장단점을 명확하게 느낄 수 있었습니다. 저는 local 브랜치에서는 rebase를 선호하되, 오류 발생시 cherry-pick을 적극적으로 사용며 원격 저장소를 고려해야 되는 상황에서는 merge 방식을 사용하게 되지 않을까 싶습니다.</p>
<h3 id="책에서도-없던-head-head">책에서도 없던 HEAD^ HEAD~</h3>
<p>HEAD^의 경우 부모를 찾되 뒤의 숫자가 올 경우 부모가 여럿인 상태에서 선택할 수 있습니다.
HEAD~의 경우 부모와 조부모에 이어 해당 숫자만큼 위의 조상님들 만나러 가게 됩니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[Optional<T>]]></title>
            <link>https://velog.io/@reading-snail17/OptionalT</link>
            <guid>https://velog.io/@reading-snail17/OptionalT</guid>
            <pubDate>Wed, 03 Jan 2024 11:10:31 GMT</pubDate>
            <description><![CDATA[<p>물건을 찾을 때, 그 물건이 있을 수도 있고 없을 수도 있는 상황을 생각해보면 Optional&lt;T&gt;를 쉽게 이해 할 수 있습니다. 물건을 찾을 때, 해당 물건이 있으면 &quot;여기 있어요&quot;라고 말하고, 없다면 &quot;죄송하지만 없네요&quot;라고 말할 수 있습니다. </p>
<ul>
<li><p>Optional 객체 생성:</p>
<ul>
<li><code>Optional.of(value)</code>: 값이 있는 경우 Optional 객체를 생성합니다.</li>
<li><code>Optional.empty()</code>: 값이 없는 경우 빈 Optional 객체를 생성합니다.</li>
</ul>
</li>
<li><p>값 접근:</p>
<ul>
<li><code>orElse(defaultValue)</code>: 값이 있으면 그 값을 반환하고, 없으면 기본값을 반환합니다.</li>
<li><code>ifPresent(consumer)</code>: 값이 존재하는 경우에만 주어진 동작(consumer)을 수행합니다.</li>
</ul>
</li>
<li><p>값 확인 및 추출:</p>
<ul>
<li><code>isPresent()</code>: 값의 존재 여부를 확인합니다.</li>
<li><code>get()</code>: 값이 있는 경우 그 값을 반환합니다. 
주의: 값이 없는 경우 <code>NoSuchElementException</code>을 발생시킬 수 있으므로 조심해야 합니다.</li>
</ul>
</li>
</ul>
<p><code>Optional&lt;T&gt;</code>을 사용하면 값이 존재하지 않을 수 있는 상황을 명시적으로 다룰 수 있습니다. 이를 통해 NullPointerException과 같은 예외를 방지하고 안전한 코드를 작성할 수 있습니다. 값을 다룰 때 매번 null 체크를 하지 않고 <code>Optional</code>을 사용하여 코드를 간결하게 작성할 수 있습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[@Autowired vs private final]]></title>
            <link>https://velog.io/@reading-snail17/Autowired-vs-private-final</link>
            <guid>https://velog.io/@reading-snail17/Autowired-vs-private-final</guid>
            <pubDate>Wed, 27 Dec 2023 13:52:10 GMT</pubDate>
            <description><![CDATA[<h3 id="autowired를-사용한-수정자-주입">@Autowired를 사용한 수정자 주입</h3>
<ul>
<li><p><strong>특징</strong>:</p>
<ul>
<li>Spring Framework의 의존성 주입 방법 중 하나입니다.</li>
<li>유연성이 높고 선택적인 의존성 처리가 가능합니다.</li>
<li>객체 생성 후에도 의존성을 변경할 수 있습니다.</li>
</ul>
</li>
<li><p><strong>예시</strong>:</p>
<pre><code class="language-java">  public class MyClass {
      @Autowired
      private SomeDependency dependency;
  }</code></pre>
</li>
</ul>
<h3 id="allargsconstructor와-private-final을-사용한-생성자-주입">@AllArgsConstructor와 private final을 사용한 생성자 주입</h3>
<ul>
<li><p><strong>특징</strong>:</p>
<ul>
<li>Lombok 라이브러리의 기능으로 생성자를 자동으로 생성합니다.</li>
<li>모든 필드를 파라미터로 받는 생성자를 만들어줍니다.</li>
<li>객체의 불변성을 높입니다.</li>
</ul>
</li>
<li><p><strong>예시</strong>:</p>
<pre><code class="language-java">  import lombok.AllArgsConstructor;

  @AllArgsConstructor
  public class MyClass {
      private final SomeDependency dependency;
  }</code></pre>
</li>
</ul>
<h3 id="선택-시-고려-사항">선택 시 고려 사항</h3>
<ul>
<li><p><strong>@Autowired를 사용한 수정자 주입</strong>:</p>
<ul>
<li>유연성과 선택적인 의존성 처리가 필요한 경우에 적합합니다.</li>
<li>객체 생성 후에도 의존성을 변경해야 할 때 유용합니다.</li>
</ul>
</li>
<li><p><strong>@AllArgsConstructor와 private final을 사용한 생성자 주입</strong>:</p>
<ul>
<li>생성자는 한번만 호출이 가능하기 때문에 불변성과 안정성을 유지하고 싶을 때 적합합니다.</li>
<li>한 번 생성된 객체를 변경할 수 없도록 보장하고 싶을 때 유용합니다.</li>
</ul>
</li>
</ul>
<h3 id="선택-방법">선택 방법</h3>
<ul>
<li>프로젝트 요구사항과 설계 목표에 따라 선택하면 됩니다.</li>
<li>때에 따라 두 가지 방법을 혼합하여 사용하기도 하며, 일부 필드는 생성자로 주입하고 일부는 수정자 주입으로 처리하는 등의 방식으로 유연하게 활용 가능합니다.</li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[동기/비동기, Blocking/NonBlocking]]></title>
            <link>https://velog.io/@reading-snail17/%EB%8F%99%EA%B8%B0%EB%B9%84%EB%8F%99%EA%B8%B0-BlockingNonBlocking</link>
            <guid>https://velog.io/@reading-snail17/%EB%8F%99%EA%B8%B0%EB%B9%84%EB%8F%99%EA%B8%B0-BlockingNonBlocking</guid>
            <pubDate>Wed, 13 Dec 2023 13:43:18 GMT</pubDate>
            <description><![CDATA[<p>이 개념들은 여러 개의 함수/메서드가 있는 환경에서 각각의 함수/메서드를가 시행 될 때, 상호 간의 어떤 관계를 가질지에 대한 부분입니다.</p>
<h1 id="sync-vs-async">Sync vs Async</h1>
<p>호출 되는 함수의 작업 완료 여부를 확인 합니다.</p>
<h2 id="sync">Sync</h2>
<p>작업이 완료 될 때까지 기다립니다
메서드 A가 메서드 B를 호출 하였을 때, 메서드 A는 B가 완료될 때까지 지속적으로 확인하며 기다립니다. 메서드 B가 완료되면 반환 된 리턴 값을 가져옵니다.</p>
<h2 id="async">Async</h2>
<p>작업이 완료될 떄까지 기다리지 않습니다. 대신 호출을 할 때 콜백 함수도 함께 보냅니다.
메서드 A는 메서드 B에 실행하라는 호출과 함께 콜백을 전송합니다. 메서드 B는 호출 명령을 받아 실행을 완료한 후 마지막으로 콜백 함수를 실행하여 작업을 완료합니다 이때 메서드 A는 메서드 B의 완료 여부를 신경 쓰지 않습니다.</p>
<h1 id="blocking-vs-non-blocking">Blocking vs Non-Blocking</h1>
<p>이 둘의 개념은 제어권을 어떻게 다루냐에 따라 다르게 정의 됩니다. 즉, 메서드를 호출하는 메서드가 중지 될 지에 대한 내용입니다.</p>
<h2 id="blocking">Blocking</h2>
<p>메서드A가 메서드B를 호출 할 때 제어권도 함께 넘겨줍니다. 제어권이 없는 메서드A는 다음 로직을 실행시키지 못하고 메서드B가 완료 된 후 제어권을 돌려줄 때까지 기다립니다.</p>
<h2 id="non-blocking">Non-Blocking</h2>
<p>함수를 호출 할 때 제어권은 넘겨주지 않습니다. 하여 메서드A가 메서드B를 호출하더라도 메서드A는 계속해서 실행이 되어집니다.</p>
<p>위의 각각의 두 가지의 경우의 수로 인해 총 4가지의 상황이 가능합니다</p>
<h1 id="syncasync---blockingnon-blocking">Sync/Async + # Blocking/Non-Blocking</h1>
<h2 id="sync--blocking">Sync + Blocking</h2>
<p>Sync: 리턴 값 필요
Blocking: 제어권 전달
처음에 제어권을 전달하여 메서드A는 메서드B과 완료될 때까지 기다립니다. 메서드B의 실행이 완료되면 리턴 값과 제어권을 함께 반환하게 되고, 제어권을 받은 메서드A도 남은 코드를 실행하게 됩니다.</p>
<h2 id="sync--non-blocking">Sync + Non-Blocking</h2>
<p>Sync: 리턴 값 필요
Non-Blocking: 제어권 전달하지 않음
제어권이 전달되지 않았으므로 메서드A는 계속해서 실행합니다. 대신 리턴 값을 필요로 하기 때문에 주기적으로 메서드B에게 리턴값을 요구하게 됩니다.</p>
<h2 id="async--non-blocking">Async + Non-Blocking</h2>
<p>Async: 리턴 값 필요 없음
Non-Blocking: 제어권 전달햐지 않음
제어권을 전달하지 않았으며 메서드B에서 반환하는 리턴 값도 필요로 하지 않기 때문에 메서드A와 메서드B는 독립적으로 실행 됩니다.</p>
<h2 id="async--blocking">Async + Blocking</h2>
<p>Async: 리턴 값 필요 없음
Blocking: 제어권 전달
제어권을 전달하여 메서드B가 완료될 때까지 기다리게 되지만, 그에 따르는 리턴 값과 같은 보상이 없습니다. 성능적으로 sync-blocking와 비교하여 이점이 없어 잘 사용되지 않습니다.</p>
<p>참조:
<a href="https://velog.io/@nittre/%EB%B8%94%EB%A1%9C%ED%82%B9-Vs.-%EB%85%BC%EB%B8%94%EB%A1%9C%ED%82%B9-%EB%8F%99%EA%B8%B0-Vs.-%EB%B9%84%EB%8F%99%EA%B8%B0">https://velog.io/@nittre/%EB%B8%94%EB%A1%9C%ED%82%B9-Vs.-%EB%85%BC%EB%B8%94%EB%A1%9C%ED%82%B9-%EB%8F%99%EA%B8%B0-Vs.-%EB%B9%84%EB%8F%99%EA%B8%B0</a></p>
]]></description>
        </item>
        <item>
            <title><![CDATA[DTO Getter/Setter는 꼭 필요한가? ]]></title>
            <link>https://velog.io/@reading-snail17/DTO-GetterSetter%EB%8A%94-%EA%BC%AD-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80</link>
            <guid>https://velog.io/@reading-snail17/DTO-GetterSetter%EB%8A%94-%EA%BC%AD-%ED%95%84%EC%9A%94%ED%95%9C%EA%B0%80</guid>
            <pubDate>Wed, 13 Dec 2023 13:42:23 GMT</pubDate>
            <description><![CDATA[<p>DTO는 기본적으로 자료구조라고 하였습니다. 그렇다면 getter/setter를 사용하지 않고 각각의 필드 값에 직접적으로 접근하는 것이 더 맞는 방향성이 아닌가? 라는 질문이 생겼습니다.</p>
<p>DTO에서 Getter/Setter를 사용하는 이유는 아래와 같습니다.</p>
<ol>
<li>getter/setter를 사용하면 하나의 단계를 더 거치기 때문에 내부적인 변경이 있더라도 필드 속성이 아닌 getter/setter를 변경할 수 있습니다. 즉, 변경에 더 유연합니다.</li>
<li>getter만 제공하여 read-only를 만들 수 있습니다. 자료구조로 제공 될 경우 읽기 전용으로 만들 수 없습니다.</li>
<li>DTO를 bean으로 설정하기 위해서 getter/setter는 필수 입니다.</li>
</ol>
<p>위의 사항들이 아닐 경우 완전한 자료구조로서의 DTO도 충분히 가능합니다.</p>
<p>좀더 실질적인 Getter가 필요한 예시는 Jackson을 사용하여 JSON 형식으로 객체를 변환할 때 입니다. 내부적으로 getter를 사용하여 역/직렬화를 거치기 때문입니다.</p>
<p>참고:</p>
<ul>
<li><a href="https://stackoverflow.com/questions/16539238/public-fields-in-a-data-transfer-object">https://stackoverflow.com/questions/16539238/public-fields-in-a-data-transfer-object</a></li>
<li><a href="https://velog.io/@kevin_/%EB%82%B4%EA%B0%80-DTO%EC%97%90-Getter-Setter%EB%A5%BC-%EC%A0%81%EC%9A%A9%EC%8B%9C%EC%BC%B0%EB%8D%98-%EC%9D%B4%EC%9C%A0">https://velog.io/@kevin_/%EB%82%B4%EA%B0%80-DTO%EC%97%90-Getter-Setter%EB%A5%BC-%EC%A0%81%EC%9A%A9%EC%8B%9C%EC%BC%B0%EB%8D%98-%EC%9D%B4%EC%9C%A0</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[쿠키+세션+웹스토리지]]></title>
            <link>https://velog.io/@reading-snail17/%EC%BF%A0%ED%82%A4%EC%84%B8%EC%85%98%EC%9B%B9%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80</link>
            <guid>https://velog.io/@reading-snail17/%EC%BF%A0%ED%82%A4%EC%84%B8%EC%85%98%EC%9B%B9%EC%8A%A4%ED%86%A0%EB%A6%AC%EC%A7%80</guid>
            <pubDate>Wed, 13 Dec 2023 13:39:36 GMT</pubDate>
            <description><![CDATA[<p>아래의 저장소들을 connectionless와 stateless를 지향하는 HTTPS 통신 프로토콜의 한계를 보완하기 위해 나온 것들입니다. 즉, 서버가 각각의 클라이언트들을 구별하고, 식별하기 위해 데이터를 저장하는 것입니다.</p>
<h1 id="httpwebinternetbroswer-쿠키">HTTP/WEB/Internet/Broswer 쿠키</h1>
<ul>
<li>서버에서 생성 후 클라이언트에서 관리</li>
<li>허용 용량이 작고,</li>
<li>텍스트만 저장이 가능</li>
<li>설정 시간동안 유지</li>
<li>Request Header에 저장되어 Request시 항상 서버로 전송</li>
<li>필요시 HTTPonly를 설정하여 js를 조작하는 XSS로부터의 보안을 향상 가능
e.g. 장바구니, 자동완성<h1 id="세션http-session">세션(HTTP Session)</h1>
</li>
<li>서버에서 관리 -&gt; 쿠키보다 보안이 좋음, 사용자 증가 시 메모리 부하</li>
<li>브라우저를 종료할 때까지 또는 설정 시간 동안 유지</li>
<li>JS의 원시 자료형을 모두 지원<h1 id="웹스토리지-로컬--세션-스토리지">웹스토리지 (로컬 + 세션 스토리지)</h1>
</li>
<li>클라이언트에서 관리</li>
<li>HTML5부터 추가 된 저장소</li>
<li>쿠키 vs 웹 스토리지</li>
<li>용량이 크고,</li>
<li>자동전송 하지 않고,</li>
<li>CORS 적용으로 CSRF로부터 안전</li>
<li>만료일자를 사용을 안할 수 있음. 즉, 영구 저장</li>
<li>로컬 vs 세션: 유효 범위와 보존 기간에 따라 분류<h2 id="로컬-스토리지">로컬 스토리지</h2>
</li>
<li>도메인당 하나씩 생성</li>
<li>삭제하지 않으면 영구 저장<h2 id="세션-스토리지">세션 스토리지</h2>
</li>
<li>새창/새탭/도메인 마다 별개의 세션을 갖음</li>
<li>e.g. 회원가입 입력폼, 일회성 로그인</li>
</ul>
<p>참조:</p>
<ul>
<li><a href="https://jh2021.tistory.com/13">https://jh2021.tistory.com/13</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[서비스 계층은 왜 인터페이스를 사용하는가?]]></title>
            <link>https://velog.io/@reading-snail17/%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B3%84%EC%B8%B5%EC%9D%80-%EC%99%9C-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80</link>
            <guid>https://velog.io/@reading-snail17/%EC%84%9C%EB%B9%84%EC%8A%A4-%EA%B3%84%EC%B8%B5%EC%9D%80-%EC%99%9C-%EC%9D%B8%ED%84%B0%ED%8E%98%EC%9D%B4%EC%8A%A4%EB%A5%BC-%EC%82%AC%EC%9A%A9%ED%95%98%EB%8A%94%EA%B0%80</guid>
            <pubDate>Wed, 13 Dec 2023 13:34:49 GMT</pubDate>
            <description><![CDATA[<p>두 질문의 정확한 의미를 이해하고 해답을 찾기 위해서는 먼저 어떤 조건에서라는 전제가 매우 중요해 보입니다. 토비의 스프링 3.1 vol.1의 후반부에 아키텍처에 대해 설명하는 부분에서 저는 나름의 해답을 찾은 것 같습니다. 데이터 중심 아키텍처와 오브젝트 중심 아키텍처 중 어떤 방식을 따른 것인가의 문제 인것 같습니다.</p>
<ul>
<li>데이터 중심 아키텍처의 경우 대부분의 비즈니스 로직이 SQL과 프로시저에서 이루어지기에 서비스 계층가 갖는 역할이 작은 것 같습니다. 그렇다보니 중복되는 코드도 없고, 테스트할 코드도 없으니 추상화 하는 것이 오히려 낭비인것 같습니다. (YAGNI: You Aint Gonna Need it)</li>
<li>오브젝트 중심 아키텍처의 경우 비즈니스로직이 도메인 (계층) 또는 서비스 계층으로 옮겨옴에 따라 서비스 계층의 역할이 더 증가합니다. 중복되는 코드가 생기니 추상화하거나 AOP를 적용할 코드도 더 많을 것이고, 뿐만 아니라 테스팅도 SQL구문과 달리 더 적극적으로 가능하기에 여러 장점들을 가져가기 위해 인터페이스로 구현하기를 선호 하는 것 같습니다.
또한 오브젝트 중심 아키텍처는 테스트가 매우 중요한 가치입니다. 서비스를 테스트하기 위해서는 인터페이스로 구현하였을 때 목 오브젝트와 같은 추가적인 기능들을 함께 사용할 수 있어 필수적입니다.</li>
</ul>
<p>여러 포럼에서 사람들마다 입장이 다른 것도 그 사람들마다 익숙해져있는 아키텍처가 서로 달랐기 때문인 것 같습니다. 자신이 익숙한 방식으로 설명하게 되니 서로의 이게맞다 저게맞다 갈렸던 것 같습니다.</p>
]]></description>
        </item>
        <item>
            <title><![CDATA[SQL 파라미터 입력 방식에 따른 속도 저하]]></title>
            <link>https://velog.io/@reading-snail17/SQL-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%EC%9E%85%EB%A0%A5-%EB%B0%A9%EC%8B%9D%EC%97%90-%EB%94%B0%EB%A5%B8-%EC%86%8D%EB%8F%84-%EC%A0%80%ED%95%98</link>
            <guid>https://velog.io/@reading-snail17/SQL-%ED%8C%8C%EB%9D%BC%EB%AF%B8%ED%84%B0-%EC%9E%85%EB%A0%A5-%EB%B0%A9%EC%8B%9D%EC%97%90-%EB%94%B0%EB%A5%B8-%EC%86%8D%EB%8F%84-%EC%A0%80%ED%95%98</guid>
            <pubDate>Wed, 13 Dec 2023 13:24:45 GMT</pubDate>
            <description><![CDATA[<h1 id="mybatis-파라미터-바인딩-오류">MyBatis 파라미터 바인딩 오류?</h1>
<p>Mybatis에서 #을 사용하여 파라미터를 넘길 경우 ${}을 사용할 때 보다 약 60배 넘는 속도 차이가 발생하는 현상이 발견되었습니다. ${}의 경우 SQL Injection에 취약하기에 원인을 파악하여 #{}을 사용하는 방법으로 바꾸어야 했습니다.</p>
<p>처음에는 DB 매니저 프로그램을 사용해서 값을 직접 입력해도 속도가 느리게 나와 문제의 원인을 발견하기 매우 어려웠습니다. ${} 사용하여 일부 쿼리가 생략되는 것 같은 이상 현상 때문에 소요시간이 줄어든 것으로 느껴졌기 때문입니다.</p>
<p>그러다 혹시 몰라 직접 값을 해당 SQL에 파라미터를 하드코딩 하여 실행해 보았습니다. 실행 결과 ${}을 적용하였을 때와 비슷한 속도가 나왔습니다. 즉, SQL이나 다른 문제가 아닌 파라미터를 preparedStatement에 바인딩 할 때, 무언가가 문제가 있어 속도 저하가 발생한다는 결론에 이를 수 있었습니다.</p>
<p>좀 더 조사를 한 결과, 파라미터를 바인딩 할 때 형 변환을 거치게 되는데 이때 형 변환이 잘못되거나 어떤 타입으로 형 변환을 할지 정확하게 지정되지 않아 발생하는 이슈로 판단되었습니다. 특히, 문제가 발생한 내용은 WHERE 절에 조건으로서 날짜를 비교하는 구문이었습니다. 테이블에서 불러오는 값은 타입이 VARCHAR이었고, 파라미터의 값은 당연히 STRING이라고 판단하여 VARCHAR로 변형 될 것으로 기대 되었습니다. 하지만 실제로는 OBJECT 타입으로 숫자만 담은 값만 넘어오는 코드였고, VARCHAR가 아닌 NUMBER로 변형된 것으로 보였습니다.</p>
<p>해결책은 분명해 보였습니다. TO_CHAR()로 해당 파라미터를 감싸서 강제로 VARCHAR로 형변환시켜 주는 방법이었습니다. 값을 비교하기 위해 사용된 부등호 양쪽에 타입이 같아질 수 있었습니다. 최종적으로 속도 저하는 사라졌고 ${}를 사용했을 때와 비슷한 정상적인 소요 시간으로 돌아왔습니다.</p>
<h1 id="앗-sql-튜닝-문제">앗, SQL 튜닝 문제?</h1>
<p>MyBatis가 문제가 아닌 것 같다는 피드백이 있었습니다. 
비교를 명확하게 하기 위해서 Map&lt;String,Object&gt;를 Map&lt;String,String&gt;으로 변환하고 테스트를 해봤습니다. 결과는 기대와 달리 느렸습니다. 즉, 피드백 대로 MyBatis 문제가 아니었습니다. 그리고 #{}을 사용할 경우 따옴표가 붙어 어떤 값도 결국 String 값으로 입력된다는 사실도 알게 되었습니다.</p>
<p>속도 차이가 나는 두 쿼리(TO_CHAR()를 사용하는 것과 아닌 것)를 SQL 매니저를 돌려 플랜을 확인해 봤습니다. Range Scan과 Skip Scan에서 분명한 속도 차이가 발생하는 것을 확인했습니다.
관련 내용을 더 조사해보니 정확한 속도 저하의 원인는 SQL에서 묵시적으로 일어난 타입 변환 때문이라는 사실을 알게 되었습니다. 묵시적 타입 변환의 경우 인덱스가 생략되어 속도 저하가 발생될 수 있다는 경고였습니다. 이를 해결하는 방법은 TO_CHAR, TO_NUMBER와 같은 명시적 타입변환을 사용하여 인덱스를 정확하게 탈 수 있게 변경해주는 것이었습니다.</p>
<h1 id="되돌아보며">되돌아보며..</h1>
<p><img src="https://velog.velcdn.com/images/reading-snail17/post/6a9b5076-29fb-4886-aa97-9e32231b829d/image.png" alt=""></p>
<p>머리가 프로펠러처럼 돌아가는 비둘기 밈은 개발자들의 돌아만 간다면 괜찮다, 오히려 만졌다가 더 큰일 만들기보다는 날아다니니 괜찮다는 의미를 비꼬는 내용으로 많이 사용 되는 듯 합니다. 동작만 하는 프로그램은 폭탄과 같다고 생각합니다. 더 훌륭한 개발자는 정확한 원인을 진단하고, 더 근본적인 해결책을 제공해줘야 된다고 생각합니다. </p>
<p>참조:</p>
<ul>
<li><a href="https://velog.io/@gyrbs22/SQLDP-%EB%AC%B5%EC%8B%9C%EC%A0%81%EC%95%94%EC%8B%9C%EC%A0%81-%ED%98%95%EB%B3%80%ED%99%98">https://velog.io/@gyrbs22/SQLDP-%EB%AC%B5%EC%8B%9C%EC%A0%81%EC%95%94%EC%8B%9C%EC%A0%81-%ED%98%95%EB%B3%80%ED%99%98</a></li>
</ul>
]]></description>
        </item>
        <item>
            <title><![CDATA[1-2 테스트]]></title>
            <link>https://velog.io/@reading-snail17/1-2-%ED%85%8C%EC%8A%A4%ED%8A%B8</link>
            <guid>https://velog.io/@reading-snail17/1-2-%ED%85%8C%EC%8A%A4%ED%8A%B8</guid>
            <pubDate>Tue, 05 Dec 2023 07:37:52 GMT</pubDate>
            <description><![CDATA[<p>DI 다음으로 스프링에서 가장 강조하고 있는 개념은 테스트입니다. 테스트는 개발자로 하여 안정감을 갖을 수 있게 해줍니다. JUnit과 TDD 개념에서 언급 되듯이 코드를 수정했지만 발 뻗고 잘 수 있는 안정감 말입니다.</p>
<h1 id="단위-테스트">단위 테스트</h1>
<p>하나의 프로그램 전체를 테스트하기란 사실상 불가 합니다. 왜냐하면 기능들이 상호 간 연관되어 있기에 모든 기능이 다 구현되었을 때야 비로소 테스트가 가능하기 때문입니다. 그래서 테스트는 기본적으로 기능 단위로 쪼개서 테스트하는 단위 테스트가 기본이 됩니다.</p>
<p>스프링에서의 테스트가 추구하는 바도 동일합니다. JUnit 프레임워크와 TDD에서 강조되는 것과 같이 스프링에서도 단위 테스트를 중심으로 하여 개발하도록 권장하고 있습니다.</p>
<h1 id="단언검증">단언/검증</h1>
<p>일반적인 테스트에서는 로그를 찍는 방식으로 테스트할 것입니다. System.out.println()과 같은 메서드를 사용하게 되는데 JUnit과 스프링에서는 단언/검증이라고 불리는 방법을 사용하게 됩니다. assertThat()과 같은 메서드는 기대값과 결과값을 비교하여 테스트의 성공과 실패를 알려줍니다. 또한, 실패하더라도 결과값 등 여러 데이터를 제공하여 개발자로하여 코드를 수정할 수 있도록 돕습니다. 이러한 성공과 실패로 결과를 내는 방식은 테스트를 자동화 시키는 것을 돕습니다. 여러 개의 테스트를 실행해도 어떤 테스트가 실패한 테스트를 가려내는 것을 도와줍니다.</p>
<h1 id="junit-과-특징">JUnit 과 특징</h1>
<p>스프링의 내장된 테스트는 JUnit에 뿌리를 둡니다. 
테스트 코드는 몇가지 특징을 갖는데 아래와 같습니다.</p>
<ul>
<li>메서드가 public이여야 합니다</li>
<li>@Test 애노테이션으로 테스트 코드임을 알립니다.</li>
<li>assertThat() 으로 검증 합니다.<h1 id="테스트의-일관성">테스트의 일관성</h1>
테스트는 항상 동일한 코드에서 동일한 결과를 만들어내야 합니다. 이를 위협할 수 있는 대표적인 예시가 데이터베이스와 상호 작용하게 되는 부분 입니다. 이러한 오류를 막기 위해서는 테스트 실행 후 입력된 데이터를 초기화하거나 롤백하여 데이터에 변경이 없을 수 있도록 유도해야 합니다. 또는 목 오브젝트를 사용하여 데이터베이스를 직접 사용하지 않도록 하는 것도 좋습니다. 이는 서비스 추상화에서 다시 언급됩니다.<h1 id="예외-테스트">예외 테스트</h1>
결과 값 뿐만 아니라 발생할 것으로 기대되는 예외도 테스트 할 수 있습니다. 예를 들어, @Test(Expected=exception.class) 와 같은 구문을 사용하여 발생될 것으로 기대되는 예외가 발생하였을 때만 성공이라는 피드백을 받을 수 있도록 할 수 있습니다.<h1 id="tdd테스트-주도-개발">TDD(테스트 주도 개발)</h1>
테스트 코드를 먼저 만든 후 코드를 만들어나가는 방법입니다. 항상 테스트는</li>
<li>조건: 어떤 조건을 가지고</li>
<li>행위: 무엇을 할 때</li>
<li>결과: 어떤 결과가 나온다
라는 접근을 가지고 있어야 합니다. 구현이 되기전에 테스트는 당연히 실패합니다. 실패한 코드를 성공으로 테스트를 실패로 시작해서 성공으로 끝났을 때 코드의 구현도 끝나는 것입니다.<h1 id="junit의-로직">JUnit의 로직</h1>
</li>
</ul>
<ol>
<li>@Test, public, void, 파라미터 없는 메서드 탐색</li>
<li>테스트 클래스의 오브젝트 생성</li>
<li>@Before 수행</li>
<li>@Test 수행</li>
<li>@After 수행</li>
<li>모든 테스트 메서드를 @Before-&gt;@Test-&gt;@After순으로 수행</li>
<li>종합 결과를 반환
Fixture: 테스트에서 필요한 정보<h1 id="테스트-applicationcontext">테스트 ApplicationContext</h1>
@RunWith(SpringJUnit4ClassRunner.class): 어떤 테스트 프레임워크를 사용할지 선택
@ContextConfig(locations=“/application.xml”) 테스트에 사용할 어플리케이션 컨텍스트 설정 정보
@Autowired: DI를 통해 객체 주입
스프링의 객체를 컨텍스트에서 관리하므로 매번 객체를 생성할 필요가 없어 테스트 시간을 줄여줍니다.</li>
</ol>
<h1 id="런타임-도중-스프링-컨테이너-조작">런타임 도중 스프링 컨테이너 조작</h1>
<p>@DirtiesContext를 사용하면 테스트 도중에 스프링 컨테이너에 조작을 가할 수 있다. 그러나 권장되는 방법은 아닙니다. </p>
<p>당연히 Autowired와 같은 DI를 필수로 사용하지 않아도 된다. 생성자를 사용해서 컨테이너 없이 객체를 생성하는 것도 당연히 가능합니다.</p>
<h1 id="테스트-방법-선택">테스트 방법 선택</h1>
<ol>
<li>스프링 컨테이너 없이 테스트 할 수 있는 방법을 먼저 고민해야 됩니다. 빠르고, 단순하다.</li>
<li>여러 오브젝트를 불러와야 할 경우 DI를 사용하면 편리하다. 테스트 어플리케이션 컨텍스트를 사용하단다면 별도의 Configuration을 만들어 관리해도 좋습니다.</li>
<li>예외적이 의존관계가 필요할 경우 DI 받아온 오브젝트에 new로 생성한 오브젝트를 다시 주입하는 방식을 사용할 수 있다. 이때는 @DirtiesContext를 붙여야 합니다.<h1 id="학습-테스트">학습 테스트</h1>
자신이 작성하지 않은 코드들에 대해서 테스트를 추가하고, 이해도를 높이는 학습 방법입니다.</li>
<li>다양한 조건들에 따라 어떻게 동작하는지 알 수 있습니다.</li>
<li>개발 도중에 참고할 수 있습니다</li>
<li>업그레이드 시 호환성 테스트를 도와줍니다.</li>
<li>테스트 코드 작성 훈련을 할 수 있습니다.<h1 id="버그테스트">버그테스트</h1>
버그로 인해 실패하는 테스트를 먼저 작성한 후 이를 성공할 수 있게 변경하는 방법의 테스트 방법입니다.</li>
<li>불충분 했던 테스트를 추가하여 완성도를 높입니다.</li>
<li>버그 내용을 완벽하게 분석해준다.</li>
<li>기술적인 부족을 해결하는 데 도움을 줍니다.</li>
</ol>
<blockquote>
<p>&quot;토비의 스프링 3.1&quot; - 이일민</p>
</blockquote>
]]></description>
        </item>
        <item>
            <title><![CDATA[CleanCode]]></title>
            <link>https://velog.io/@reading-snail17/CleanCode</link>
            <guid>https://velog.io/@reading-snail17/CleanCode</guid>
            <pubDate>Tue, 05 Dec 2023 07:24:48 GMT</pubDate>
            <description><![CDATA[<p><em>Robert C. Martin</em></p>
<blockquote>
<p>P.S. 클린코드 책에 대한 많은 사람들의 우려를 잘 알고 있습니다. 해서 맹신 하지 않고, 저자의 의도를 비판적으로 바라보려고 노력하고 있습니다.! 맹신하게 된다면 빠르게 변하는 개발 업계에서 잘못된 방식의 접근이 될 수도 있는 책인 것 같습니다. 그러나 저자의 의도를 더 깊게 받아들인다면 스택이 변하여도 가치가 변치 않을 수 있는 책이라고 생각합니다.</p>
</blockquote>
<h2 id="내가-그의-이름을-불러주었을-때">내가 그의 이름을 불러주었을 때</h2>
<p>그는 나에게 와서 꽃이 되었습니다.
결국 애매모호한 표현과 이름을 피해야 된다는 것입니다. 단순하지만 모든 것을 품을 수 있는 것들이어야 합니다. 글쓰기와 동일합니다. 나만 읽을 수 있거나 읽기 복잡하고 어려운 배려 없는 글은 훌륭한 글이 아니 듯이 코드도 동일합니다. 내가 어떻게 불러 주는지가 대상을 더 배려심 있고, 사랑스러운 존재로 만듭니다.</p>
<h2 id="함수는-쪼개고-분리해야-한다">함수는 쪼개고, 분리해야 한다</h2>
<p>소설과 같은 문학에서는 긴 문장의 글을 선호하기도 합니다. 더 깊은 묘사가 가능하기 때문입니다. 그러나 개발자에게 필요한 것은 리포트와 논문과도 같은 명백하고, 읽기 쉬운 글입니다. 한 문장이 2줄을 넘어가지 않는 그런 문장 말입니다. 
함수(메서드)는 간결하고, 짧아야 됩니다. 길다면 너무 많은 것을 담고 있는 것일지도 모릅니다. 때로는 함수에 클래스에 담겨야 할 명사적인 큰 존재를 담으려고 해서 넘칠 수도 있습니다. 함수(메서드)는 동사입니다. 동사는 하나의 동작만을 표현하는 것이 좋습니다.</p>
<h2 id="주석은-쓰레기일-수도-있다">주석은 쓰레기일 수도 있다.</h2>
<p>주석이 분명 필요할 때도 있습니다. 그러나 필요악입니다. 없앨 수 있다면 여러 방법을 동원해 우회 해야 됩니다. 주석과 코드는 서로 독립된 존재입니다. 코드 수정 후 주석도 수정할 것이라는 생각은 오만입니다. 또한 필요해 보이는 주석도 이제는 VCS의 기능들이 처리해줍니다. 그러므로 주석은 최소화 되어야 됩니다.
(내가 주석을 쓰지 않아도 될 정도로 코드를 짤 수 있는지 물어봐야 될 것입니다. 주석을 줄일 수 있을 정도로 명명백백한 코드를 짜기 위해 노력해야 된다는 의미에 더 가깝지 않을까?)</p>
<h2 id="구조가-잘-짜여진">구조가 잘 짜여진</h2>
<p>건물과 같은 구조물은 아름답고, 단순하다. 보기 좋고, 이해하기 편하다는 의미입니다. 코드도 그래야 합니다. 괄호와 띄어쓰기, 메서드의 위치들이 사람의 직관을 관통할 수 있어야 된다. 잘 짜여진 구조의 코드는 스크롤을 줄이고 이해하기 위해 소모되어질 시간을 아껴줍니다.</p>
<ul>
<li>2 + 2 와 2$*$ 2의 차이를 느낄 것 <h2 id="오류처리와-논리의-분리">오류처리와 논리의 분리</h2>
예외처리를 통해 논리코드와 오류처리 코드를 분리 할 수 있습니다. 이는 코드를 간결하게 해줍니다. 뿐만 아니라 정확한 예외처리는 프로그램 자체의 안정성까지 책임져줍니다. 잘쓸 글 = 잘쓴 코드였던 위의 내용과 일관되지만 예외처리는 좀 더 기술적인 방법들이 중요하게 부각됩니다..
아래는 예외처리 방법들입니다.</li>
<li>오류코드가 아닌 예외처리를 사용합니다.</li>
<li>Try-Catch-Finally문을 먼저 작성하여 구조를 세웁니다.</li>
<li>unchecked 예외를 사용합니다. (C++, C#에는 checked예외가 없으나 잘 작동한다. 아주 중요한 라이브러리를 작성할 떄만 checked 예외를 사용하도록 한다.)</li>
<li>오류를 정의하는 것이 아닌 오류를 잡아내는 것이 목표가 되어야 합니다. 죽, 문제재기를 넘어 전후 상황과 해결책을 제시해야 합니다.</li>
<li>Wrapper 클래스를 사용하여 오류를 분류해줍니다. </li>
<li>특수 사례 패턴: try-catch로 예외를 잡아 작동하는 코드는 상위 비즈니스 코드에서 처리합니다.</li>
<li>코드를 모호하게 만드는 Null을 반환하기보다는 빈 Collection과 같은 의미 있는 값을 반환하며 좋습니다. 예를 들어 Assert [조건문] : [&quot;메시지&quot;] 문을 사용하여 전달된 Null을 처리합니다.<h2 id="외부소스의-내부화">외부소스의 내부화</h2>
외부소스는 다른 사람과 기관이 만든 코드를 말합니다. 그래서 내부에서 사용할 방식에는 딱 들어맞지 않기 마련입니다. 그래서 Wrapper로 감싸거나 Adaptor를 사용하여 내부화 해야 됩니다.</li>
<li>학습테스트(TDD)를 활용하여 외부 API를 적용할 것을 추천합니다. 기록된 테스트들은 다른 외부 API로 변경하거나 사용하던 API가 업데이트 되었을 때 신속하고 안전하게 반영하도록 해줍니다.</li>
<li>Adaptor 패턴을 사용할 경우, 아직 구현되지 않은 API에 맞는 내부 코드를 미리 만들  수 있습니다. 구현되지 않았더라도 외부 API가 전달해줄 값의 의미는 유출 할 수 있습니다. 이를 근거로 하여 전면에는 Adaptor 인터페이스를 두고 FakeAdaptor를 임시로 구현하여 코드를 짤 수 있습니다.<h2 id="tdd">TDD</h2>
TDD는 한 문단으로 요약할 수 없는 또 하나의 개념과 프레임워크 규모의 내용입니다. 이 장을 읽고 요약하기 위해 키보드를 두들기기보다는 TDD관련 책을 읽는 것이 맞을 것 같습니다.<h2 id="유연한-클래스는-단순한-클래스이다">유연한 클래스는 단순한 클래스이다</h2>
단일 책임 원칙(SRP)에 의해서 클래스는 변경해야 할 이유가 단 하나여야 한다고 말합니다. 하나보다 많다면 클래스가 너무 커졌기 때문입니다. 하나의 책임만을 갖는 클래스는 단순하기에 변경이 용이해집니다. 또한, 추상 클래스와 인터페이스를 활용하여 의존성 역전 원칙(DIP)을 적용하면 외부의 변화로부터 유연한 코드를 만들 수 있습니다.<h2 id="시스템--작성-중"><del>시스템 : 작성 중</del></h2>
<h2 id="창발성--작성-중"><del>창발성 : 작성 중</del></h2>
</li>
</ul>
<h2 id="신은-디테일-속에-있다-동시성">신은 디테일 속에 있다. 동시성</h2>
<p>컴퓨터는 거짓말 할일이 없지만 거짓말한다고 느껴질 떄가 있습니다. 바로 동시성이 꺠졌을 때 입니다. 동시성 방어 원칙이라 할 수 있는 기본적으로 지켜볼 수 있는 동시성 문제를 해결 할 수 있을 것이 입니다. 그러나 스레드는 매우 섬세하고, 예민합니다. 그렇기 떄문에 더 근본적으로 깊게 스레드를 이해하는 방법 밖에 없습니다. TDD에서도 그랬 듯이 <strong>동시성 또한 책 몇권은 읽어야 되는 부분입니다.</strong></p>
<h4 id="동시성-방어-원칙">동시성 방어 원칙</h4>
<ul>
<li>SRP - 동시성 코드와 일반 코드를 분리 </li>
<li>Corollary (따름 정리)<ul>
<li>자료를 제한하라: 자료를 캡슐화하고, 공유 자료를 최대한 줄인다.</li>
<li>자료 사본을 사용하라: 자료를 복사해 읽기전용으로 사용 &lt;- 사용된 해결책</li>
<li>스레드는 가능한 독립적으로 구현히리: 쓰레드 간 동기화를 최소화한다.<h2 id="점진적-개선--작성중"><del>점진적 개선 : 작성중</del></h2>
</li>
</ul>
</li>
</ul>
<h2 id="junit-들여다보기--작성중"><del>JUnit 들여다보기 : 작성중</del></h2>
<h2 id="냄새와-휴리스틱--작성중"><del>냄새와 휴리스틱 : 작성중</del></h2>
<h2 id="동시성2--작성중"><del>동시성2 : 작성중</del></h2>
]]></description>
        </item>
        <item>
            <title><![CDATA[ServiceExcutor 리팩토링]]></title>
            <link>https://velog.io/@reading-snail17/ServiceExcutor-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-cf8rheqc</link>
            <guid>https://velog.io/@reading-snail17/ServiceExcutor-%EB%A6%AC%ED%8C%A9%ED%86%A0%EB%A7%81-cf8rheqc</guid>
            <pubDate>Tue, 05 Dec 2023 07:09:08 GMT</pubDate>
            <description><![CDATA[<p>프로젝트 내에 ServiceExecutor를 수정해보기로 했습니다.</p>
<h1 id="serviceexecutor">ServiceExecutor</h1>
<p>ServiceExecutor의 Outline은 다음과 같았습니다.</p>
<ul>
<li>필드<ul>
<li>로그인 정보 테이블에 대한 칼럼명 맵핑</li>
<li>RowType 초기값</li>
</ul>
</li>
<li>Autowired<ul>
<li>PasswordEncoder</li>
<li>EgovPropertyServiceImpl</li>
<li>COMMON_Mapper</li>
</ul>
</li>
<li>메서드<ul>
<li>getCommon()  -&gt; HandlerMethodArgumentResolver</li>
<li>ExceptionLogging() -&gt; HandlerExceptionResolver</li>
<li>getDsRowToMap() -&gt; 별도의 Class<h2 id="getcommon">getCommon()</h2>
</li>
</ul>
</li>
</ul>
<h3 id="handlermethodargumentresolver">HandlerMethodArgumentResolver</h3>
<p>HandlerMethodArgumentResolver 의 경우 3.1.버전 이전에는 WebArgumentResolver로 불리던 확장 포인트 입니다. 아래의 두 개의 메서드를 구현하는 방식으로 사용 할 수 있습니다.</p>
<ul>
<li>supportParameter: 리졸버를 적용할지를 결정합니다.</li>
<li>resolveArgument<ul>
<li>반환할 MethodArgument를 생성하는 부분</li>
<li>webRequest.getNativeRequest/Response()를 통해 일반적으로 사용할 HttpServeletRequest/Respose를 불러올 수 있습니다.</li>
</ul>
</li>
</ul>
<p>getCommon을 HandlerMethodArgumentResolver를 Implements 받아서 CommonArgumentResolver를 구현해 보았습니다. 그러나 문제점이 있었는데 넥사크로에 이미 구현해 놓은 NexacroMethodArgumentResolver가 문제였습니다. ArgumentResolver 사이의 관계는 상하 관계가 없는 형제(?) 관계이므로 값의 전달이 불가능 하다는 문제가 있었습니다. 그러다 보니 getCommon 내부에서 필요한 파라미터 값을 NexacroMethodArgumentResolver에서 받아올 방법이 없어 보였습니다.</p>
<h3 id="xml-request를-별도로-받아오기">XML Request를 별도로 받아오기</h3>
<p>이를 해결하기 위해 CommonArgumentResolver에서 NexacroMethodArgumentResolver와는 별도로 프론트에서 받아온 XML 포맷의 Request의 Body를 받아서 Parsing 할 생각을 했습니다.</p>
<p>이러한 방법은 또 다른 문제점이 있었습니다. requeset.getInputStream()의 경우 한번 밖에 호출이 불가능다보니 CommonArgumentResolver에서 호출할 때는 Steam Closed라는 경고를 발생 시켰습니다.</p>
<h3 id="인터셉터에서-받아와-저장하기">인터셉터에서 받아와 저장하기</h3>
<p>Stream을 어러번 할 수 있게 Filter 단계에서 별도로 request의 InputStream을 캐시에 저장해서 여러번 사용 할 수 있게 하는 방법을 시도해봤다.</p>
<p>근본적으로 넥사크로에서 제공되는 코드와 어떤 충돌이 발생할지 몰라 조심스러운게 많아지는 부분이어서 시도하다가 중단하게 되었습니다. 이미 진행 중인 프로젝트이므로 안정성이 무엇보다 중요하다고 생각했습니다.</p>
<h3 id="aop">AOP</h3>
<p>또 다른 방법은 Controller 단에서 CommonArgumentResolver를 포기하는 방법을 생각해 볼 수 있었습니다. 대신 기존의 Executor 방식을 큰 틀에서는 벗어나지 않지만 extends를 하지 않아 상호 간의 종속성을 줄이면서도 중복코드를 줄일 수 있다고 생각되었습니다. ServiceImp 클래스의 메서드 Before 단계에서 getCommon() 메서드를 코드를 실행하여 Map 형식의 변수를 전달하는 방법을 사용해보려고 했습니다.</p>
<h1 id="exceptionlogging">ExceptionLogging()</h1>
<h2 id="handlerexceptionresolver">HandlerExceptionResolver</h2>
<p>처음에는 간단해 보였습니다. Exception 관련 사항을 별도의 클래스로 분리하면 되는 문제 일줄 알았습니다. 그래서 해당 메서드에 집중하기 보다는. HandlerExceptionResolver와 같은 스프링 산하의 여러 예외처리 기능들에 집중해서 해결방법을 찾았었습니다.</p>
<p>그러나 문제는 다른 곳이 있었습니다.  ExceptionLogging() 메서드가 NexcroResult 객체에 종속적이라는 부분이 제일 큰 문제점이었습니다. 예외처리는 본디 예외를 만나는 순간 바로 ExceptionHandler를 거쳐 화면단에 UI 또는 log로 표시는 해줘야 되기 마련인데 NexacroResult를 return으로 꼭 반환해야 되다보니 해결할 방법이 없어 보였습니다. </p>
<h2 id="예외는-예외로-처리하기">예외는 예외로 처리하기</h2>
<p>막막해보였으나 대략적인 방향성을 발견했습니다. 처음에는 이 부분을 해결할 방법을 알지 못해 덮어두고 있었는데 설계 자체의 근본적인 문제임을 발견하게 되었습니다. CleanCode를 보면 예외처리는 비즈니스 코드와 분리되어야 한다고 강조합니다. 하지만 이 코드들을 NexacroException을 통해 예외처리를 하는 것이 아닌 비즈니스코드에 사용되는 NexacroResult의 부분 메서드인 setErrMsg(), SetErrCode() 메서드를 사용하여 이를 우회적으로 실현하고 있었습니다. 그래서 예외처리 로직이 비즈니스 로직의 일부인 NexacroResult 객체에 종속적일 수 밖에 없었던 것이었습니다.</p>
<p>예외처리 로직에서 NexacroResult를 제거하고 NexacroException 객체로 처리할 수 있게 변경한다면 또한  Exceptionlogging() 내부적으로 이루어지고 있던 예외종류 분류와 로그처리는 @ExceptionHandler 또는 @ControllerAdvice 처리할 수 있게 변경 할 수 있을 것 같았습니다.</p>
<h2 id="예외처리-resolvers">예외처리 Resolvers</h2>
<p>@ExceptionHandler를 아무리 지지고 볶아보아도 잘 작동이 되지 않았습니다. 로그에는 자꾸 NexaceoExecptioinMappingReoslver 클라스와 관련된 에러가 계속 발생되었습니다. 다행이도 github에 해당 소스가 올라와 있어서 확인해보니 AbstractExctpionHandlerResolver를 상속하는 예외처리 클라스였습니다. 예외처리에 대해서 더 깊게 알아야 되겠다는 생각에 더 조사해 보았습니다.
예외처리는 스프링에서 크게 세 개의 Resolver로 관리된다고 합니다.</p>
<ol>
<li>ExceptionHandlerExceptionReolver: @ExcetionHandler를 구현해주는 클래스 입니다.</li>
<li>ResponseStatusExcetionResolver: @ResponseStatus를 사용하여 더 구체적인 예외 내용을 전달한다. </li>
<li>DefaultHandlerExcetionResolver: 위의 예외처리가 모두 실패 하였을 때 마지막으로 처리한다. 상황의 맞는 응답코드를 반환한다.
이제 이유가 보이기 시작했습니다. NexacroExceptionMappingRsolver도 결국 예외처리를 하기 위해 확보된 클래스 였는데 내부 소스를 보니  modelandview로 넥사크로와 통신하고 있었습니다. 그런데 @ExceptionHandler에서 modelandview를 반환하지 않고, 다시 예외로 던져서 처리하려고 하다보니 ExceptionResolver 간에는 형제 관계이기 때문에 서로를 몰라 예외처리가 해결이 안된 것이었습니다.</li>
</ol>
<p>@HanlderException 에 NexacroMapingHandlerResolver의 소를 참고하여 별도의 예외처리르 하는 코드를 만들거나 NexacroMappingExceptionResolver와 소스가 동일한 CustomNeacroExceptionResolver 만들어  ServiceExecutor 내부의 Exceptionlogging() 메서드 로직을 가져와 넣어주면 되었습니다. 두번째 방식을 사용하는 것이 @HandlerException과 NexacroMappingHandlerResolver를 동시에 관리하는 것보다 더 간단해보인 관계로 두번째 방식을 시도해보기로 했습니다. 구현에 성공하여 어느 정도 작동되는 결과물을 만들어 냈습니다. 그러나 이번에는 트랜젝션이 문제였습니다. ExceptionLogging() 내부에 있던 트랜젝션 코드가 오류를 뱉어냈습니다.</p>
<p>스프링이라면 본디 선언적 트랜젝션이 강점일 텐데 왜 이렇게 구현되었을까 싶었는데 선언적 트랜젝션 방식과 try-catch문을 함께 사용하다보니 try-catch문으로 처리된 예외가 선언적 트랜젝션 설정 부분에서 감지하지 못하는 문제가 발생하였고, 이를 해결하기 위해 로그처리 메서드 내부에 에외처리 메서드가 함께 들어가 된 것으로 보여졌습니다. </p>
<p>개인적으로 판단하였을 때는 try-catch문을 제거하고, 선언적 트랜젝션으로 예외처리를 하도록 모두 변경하고, exceptionLogging()에서 처리하는 트랜젝션도 제거하는 것이 맞다고 판단되었으나 프로시저에서 실행되는 단계적 배치에서 예외가 날 경우 전체 롤백을 하기 위해서는 해당 트랜젝션이 필요하다고 하여 코드를 변경하는 것을 설득하지는 못하였습니다. 비록 누더기처럼 보였지만 작동이 되기로 했고요...</p>
<h2 id="getdsrowtomap">getDsRowToMap()</h2>
<h3 id="datasetgetrowtomap">DataSet.getRowToMap()</h3>
<p>본래는 DataSet의 하위 메서드로 구현하여 DataSet을 자연스럽게 가공하는 형태의 메서드로 구현할 생각을 했습니다. DataSet의 경우 넥사크로에서 제공되어지는데 상속 받아서 구현할 수 없도록 final로 막혀저 있어 방법을 찾을 수 없었습니다.</p>
<h3 id="별도의-클래스로-분리">별도의 클래스로 분리</h3>
<p>별도의 클래스를 생성하여 ConvertDataSetRowToMap(DataSet, Int)  메서를 구현했습니다. 이떄,
Int 해당 Row의 값입니다.</p>
<h3 id="제공되는-getroewtomap">제공되는 getRoewToMap()</h3>
<p>해결책을 찾았다. 왜 인지는 모르겠으나 getRowToMap(int) 메서드가 DataSet 하위에 이미 구성되어있었습니다. 아무래도 당연히 없으니 ServiceExecutor에서 구현해 놨을 거라고 생각한 것이 문제였던 것 같습니다. 또한 공식 메뉴얼에도 없는 메서드 였습니다. 아무래도 구버전 nexacro에서는 없던 기능이 포함됐는지도 모르겠습니다. 간단 명료한 방법입니다. 해당 객체가 스스로 컨버팅을 하는 형태의 메서드이다 보니 의미도 더 분명해 보입니다. Dataset.getRowToMap(row) 이미 있는 메서드를 왜이리 돌고돌아 왔는지 모르겠습니다. 역시 꺼진불도 다시봐야 될 것 같습니다. !</p>
<h1 id="되돌아보며">되돌아보며..</h1>
<p>나름 열심히 개인 프로젝트 처럼 코드와 구조를 개선해보는 좋은 경험이었습니다. 그러나 반영되지 못한 한계가 분명했습니다. 무엇보다도 완성되더라도 테스트가 되지 않은 해당 코드를 누가 써주겠나라는 사실이 더는 진행할 의지를 더욱 꺾어 놓았습니다. 대신 하나 확실히 배운게 있다면 테스트의 중요성이라는 생각이 듭니다. 나와 나의 동료들 더 나아가 클라이언트를 설득하기 위해서는 안정성이 무엇보다도 프로젝트에 있어서 중요한 것 같습니다 그리고 이를 실현 할 수 있는 사실사 유일한 방법은 단위 테스트와 목 테스트 뿐이다는 확신이 들었습니다.</p>
]]></description>
        </item>
    </channel>
</rss>