<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>See One</title>
    <link>https://see-one.tistory.com/</link>
    <description>&amp;quot;왜?&amp;quot; 라는 물음에 &amp;quot;그냥&amp;quot; 보다는 이유와 근거를 말할 수 있는 개발자가 되기 위한 노력의 과정을 기록하고있습니다.</description>
    <language>ko</language>
    <pubDate>Tue, 14 Apr 2026 16:02:58 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>Dev-SeeOne</managingEditor>
    <image>
      <title>See One</title>
      <url>https://tistory1.daumcdn.net/tistory/4411317/attach/c371bba430574782bda16b57330f66c1</url>
      <link>https://see-one.tistory.com</link>
    </image>
    <item>
      <title>Service와 ServiceImpl 구조에 대해서</title>
      <link>https://see-one.tistory.com/1</link>
      <description>&lt;p&gt;스프링에 대해 공부하면서 그리고 프로젝트를 진행해보고 여러 예제 코드들을 접해보면서 느낀 것은 관습적으로 브릿지 패턴을 이용한 추상화를 사용하고 있다는 점입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;관습적인 추상화&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;계층화된 아키텍처인 &lt;i&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;MVC&lt;/span&gt;&lt;/b&gt;&lt;/i&gt; 패턴을 적용한 대부분의 프로젝트에서는 그 중에서도 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;i&gt;&lt;b&gt;Service&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 계층에서는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;i&gt;&lt;b&gt;MemberService&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 와 같이 서비스를 인터페이스로 생성하고 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;i&gt;&lt;b&gt;MemberServiceImpl&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 이라는 구현체를 생성해서 사용하는 방식으로 대부분의 설계가 이루어집니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;토비의 스프링이나 여러 객체지향과 스프링 관련 책들을 보면 이와 같은 패턴으로 설계를 해야하는 이유에 대해서 잘 설명하고 있습니다. 인터페이스와 구현체의 분리를 통해 특정 기술이나 외부환경에 독립적으로 보다 자유로운 확장이 가능해진다는 OCP 원칙에 입각한 분명한 장점이 존재합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;728&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cMqyK2/btqQXB3CR5C/svupFTkMF276ITX1anqu0k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cMqyK2/btqQXB3CR5C/svupFTkMF276ITX1anqu0k/img.png&quot; data-alt=&quot;https://ko.wikipedia.org/wiki/브릿지_패턴&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cMqyK2/btqQXB3CR5C/svupFTkMF276ITX1anqu0k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcMqyK2%2FbtqQXB3CR5C%2FsvupFTkMF276ITX1anqu0k%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;728&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://ko.wikipedia.org/wiki/브릿지_패턴&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 실제로 대부분의 프로젝트나 예제 코드를 보면 인터페이스와 구현체 클래스 사이의 관계가 1:1로 구성되어 실질적으로 인터페이스를 사용하는 것에 대한 이점을 전혀가져가지 못함에도 불구하고 관습적으로 이러한 추상화 패턴을 적용하고 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이러한 관습적인 추상화의 장점과 단점은 무엇일까요? 맹목적으로 이러한 추상화 방식을 프로젝트에 적용하는 것이 올바른 설계 방법일까요? 이러한 질문들에 초점을 맞추어 한번 고민을 해보았습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;관습적인 추상화의 장점과 단점&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;이러한 추상화 방식은 브릿지 패턴이 가지고 있는 장점과 단점과 같다고 생각해도 무방할 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;앞서 언급했듯이 인터페이스와 구현체 클래스를 분리함으로써 구현체를 독립적으로 확장할 수 있으며, 구현체 클래스를 변경하거나 확장해도 이를 사용하는 클라이언트의 코드에는 영향을 주지 않습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;추상화를 통한 구현방식은 객체지향의 특징 중 하나인 다형성과 객체지향의 다섯가지 원칙 중 하나인 OCP 원칙을 가장 잘 실현해주는 설계방식이라고 할 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dSrFMR/btqQXBWShgJ/zWvvk9JmVj03vXPq9D5lkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dSrFMR/btqQXBWShgJ/zWvvk9JmVj03vXPq9D5lkk/img.png&quot; data-alt=&quot;https://www.journaldev.com/1491/bridge-design-pattern-java&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dSrFMR/btqQXBWShgJ/zWvvk9JmVj03vXPq9D5lkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdSrFMR%2FbtqQXBWShgJ%2FzWvvk9JmVj03vXPq9D5lkk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.journaldev.com/1491/bridge-design-pattern-java&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;관습적인 추상화의 많은 장점에도 불구하고 이러한 설계 방식은 구조가 복잡해진다는 단점을 가지고 있습니다. 복잡화된 구조인 만큼 코드를 분석하고 확인하는 과정에서도 인터페이스를 거쳐서 다시 그 인터페이스의 구현체들을 확인하는 단계가 추가되면서 가지는 불편함도 분명 존재할 것 입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그리고 제가 생각했을 때의 가장 큰 단점은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;인터페이스와 구현체가 &lt;b&gt;1:1&lt;/b&gt; 관계로 이루어지는 설계에서는 이러한 장점들을 잘 살리지 못한다는 것입니다.&lt;/span&gt; 그럼에도 불구하고 반드시 관습적인 추상화를 적용해야 할까요?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;관습적인 추상화를 통한 설계 방식을 꼭 적용해야 할까?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;이와 관련해서는 크게 두 가지 의견으로 나누어지는 것 같습니다. 먼저 이러한 관습적인 추상화를 적용해야 한다는 입장에서의 의견은 토비의 스프링에 잘 나와있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;세상에 변하는 것과 변하지 않는 것이 있지만, 객체지향의 세계에서는 모든 것이 변한다. 여기서 변한다는 것은 오브젝트에 대한 설계와 이를 구현한 코드가 변한다는 의미이다. 소프트웨어 개발에서의 끝이란 개념은 없고 사용자의 비즈니스 프로세스와 그에 따른 요구사항은 끊임없이 바뀌고 발전한다. 그래서 개발자가 객체를 설계할 때 가장 염두에 두어야 할 사항은 미래를 어떻게 대비할 것인가이다.&lt;br /&gt;&lt;br /&gt;-토비의 스프링 3.1&amp;nbsp; 1장 중-&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;책에 잘 나와있는 것 처럼 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;객체는 변화하며 개발자는 끊임없이 이에 대비해야 합니다. 미래를 위한 설계를 통해 변화에 효과적으로 대처할 수 있다는 것입니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;그런 점에 있어서 우리가 설계한 인터페이스와 구현체 클래스가 당장에 &lt;b&gt;1:1 &lt;/b&gt;관계를 맺고있을지 모르지만 서비스가 커지고 변화함에 따라서 얼마든지 구현체 클래스는 확장될 가능성을 가지고 있습니다. 그렇기 때문에 인터페이스와 구현체를 분리한 설계를 통해 미래의 변화에 유연하게 대처할 수 있어야 한다는 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;반면에 반대의 입장을 가지고 있는 의견은 &lt;a href=&quot;https://multifrontgarden.tistory.com/97&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;LichKing님의 블로그&lt;/a&gt;의 글을 참고할 수 있었습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;서비스를 인터페이스로 만들었던건 관례로 굳어지게 되었는데 개발은 Transaction Script 형식으로 진행하다 보니까 관례는 관례대로 남고 애초에 그렇게 하자고 했던 이유는 사라져버리게 된 것이다. 그래서 내린 결론은 한 메서드에서 모든 역할을 다하는 이런 절차지향적인 코드에서는 사실 서비스를 인터페이스로 할 필요는 없다는 것이다. 물론 인터페이스를 만들지 말자 보다는 애초에 인터페이스를 만들었던 이유를 잘 살리는 것이 바람직 하다는 것이다.&lt;br /&gt;&lt;br /&gt;-LichKing 블로그 중-&lt;/blockquote&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사실 반대의 입장이라기 보다는 정확히 그 의미를 잘 알고서 사용해야한다는 의미에 가깝습니다.&lt;/p&gt;
&lt;p&gt;&quot;관습적으로 그냥 원래 이렇게 해왔으니까?&quot;, &quot;예제에 그냥 이렇게 나와있던데?&quot;, &quot;이렇게 하래요&quot; 와 같은 무책임한 말이 아니라 이러한 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;인터페이스와 구현체를 분리한 설계를 통한 이점들과 이유와 근거에 대해서 설계를 한 개발자 당사자는 명확히 이해&lt;/span&gt;하고 있어야 한다는 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;두 가지 글에서 알 수 있듯이 이러한 추상화 방법이 가지고 있는 장점들은 명확하게 존재합니다. 하지만 대부분이 관습적으로 이를 프로젝트에 적용하면서도 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;&quot;왜?&quot; 사용하는지에 대한 이유에 대해서 잘 인지하고 있지 못하고 있습니다.&lt;/span&gt; 그렇기 떄문에 이러한 추상화를 통한 설계방식을 적용한 이유와 장단점에 대해서 명확히 알고 사용하는 것이 중요할 것 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;추상화를 통한 설계 방식의 네이밍 컨벤션에 대해서&amp;nbsp;&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;그렇다면 또 한가지 고민해볼 수 있는 점은 바로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;Service&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 와 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;i&gt;ServiceImpl&lt;/i&gt;&lt;/b&gt;&lt;/span&gt; 의 네이밍 컨벤션에 관한 문제입니다. 보통의 경우 인터페이스에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;i&gt;Service&lt;/i&gt;&lt;/b&gt;&lt;/span&gt;를 그리고 이를 구현한 구현체 클래스에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;&lt;i&gt;ServiceImpl&lt;/i&gt;&lt;/b&gt;&lt;/span&gt; 이라는 접미사를 붙인 네이밍을 사용하거나 접두사에 인터페이스임을 명시하기 위해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;I&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 라는 접두어를 사용하고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;과연 이러한 네이밍 방식은 적절한 방법일까요? 우리가 흔히 말하는 읽기 좋은 코드 클린 코드에 부합하는 네이밍 일까요?&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Java에서 직접적으로 이러한 설계에 대해 네이밍을 적용하고 있는지 알 수 있는 대표적인 사례로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;Collection&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 프레임워크를 들 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;Collection&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 프레임워크 중에서 &lt;i&gt;&lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;List&lt;/span&gt;&lt;/b&gt;&lt;/i&gt; 의 구조에 대해서 간단하게 살펴보면, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;List&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 인터페이스와 이를 구현한 하위 구현체들에는 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;ArrayListImpl&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;, &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;LinkedListImpl&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 등이 아니라 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;ArrayList&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 와 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;LinkedList&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 라는 네이밍으로 표현하고 있는 것을 볼 수 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;해당 네이밍에서 Impl 을 접미사로 붙여서 사용하는 것은 불필요하고 무의미한 네이밍을 반복해서 사용하고 있을 뿐임을 잘 나타내 주고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사실 이러한 점에 있어서 단순히 클래스 이름을 고유하게 만들기 위해 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;i&gt;&lt;b&gt;Impl&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 을 접미사로 붙이는 것이 전부라면 인터페이스를 갖는 것에 대해 다시 한번 생각해볼 필요가 있는 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이러한 문제가 발생하는 원인은 인터페이스와 구현체 사이의 &lt;b&gt;1:1 &lt;/b&gt;관계에 있습니다. 즉, 인터페이스가 딱히 구현에 있어서 필요하지 않지만 설계 패턴을 만족시키기 위해서 위와 같은 네이밍을 사용하고 있기 때문에 발생하는 문제 입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;때문에 어떠한 방법이 옳다라고 딱 잘라서 정의내릴 수는 없지만, 이러한 점들을 고려해서 어떠한 방법으로 네이밍을 하는 것이 더 좋은 방법으로 코드를 작성하는 것인가에 대해서는 고민해볼 필요가 있을 것 같습니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;프로젝트에 추상화 제대로 적용해봅시다&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;앞선 내용들을 통해서 프로젝트에 이러한 추상화를 적용하기 전에 고려할 두 가지 숙제가 주어졌습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스와 구현 클래스가 &lt;b&gt;1:1 &lt;/b&gt;관계를 가지고 있는 프로젝트 구조에서 추상화를 적용해야하는가&lt;/li&gt;
&lt;li&gt;추상화를 적용한다면 네이밍을 어떻게 가져갈 것인가&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이러한 고민들에 있어서 어떻게 프로젝트에 적용하는 것이 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;스프링을 스프링 답게 사용하면서도 읽기 좋은 코드, 객체지향적 특징을 잘 살릴 수 있는 코드가 될까? &lt;/span&gt;라는 측면에서 답을 찾고자 노력했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;당근마켓이라는 서비스는 일반 사용자도 존재하지만 지역 게시판에 자신의 가게를 홍보할 수 있는 사장님이라는 권한을 가진 사용자도 존재합니다. 떄문에 사용자 서비스는 일반 사용자들에 대한 서비스와 사장님으로 등록된 사용자들에 대한 서비스 등으로 구현을 달리 할 수 있으며, 서비스가 확장됨에 따라서 사용자의 유형도 얼마든지 확장될 가능성이 존재한다고 판단했습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;사용자 서비스는 사용자의 유형에 따라서 얼마든지 확장될 가능성을 가지고 있기 때문에 지금의 프로젝트가 &lt;b&gt;1:1 &lt;/b&gt;관계를 가지고 있다고 하지만 얼마든지 &lt;b&gt;1:N &lt;/b&gt;관계를 갖는 서비스로 확장될 수 있습니다. 또한 이러한 확장에 있어서 사용자 유형이라는 고유의 특징을 구현체마다 가지고 있기 때문에 다음과 같이 구조를 변경할 수 있을 것 같습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;i&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;MemberService&lt;/span&gt;&lt;/i&gt;&lt;/b&gt; : 회원 서비스 인터페이스
&lt;ul style=&quot;list-style-type: disc;&quot;&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;i&gt;GeneralMemberService&lt;/i&gt;&lt;/span&gt;&lt;/b&gt; : 일반 회원 서비스 구현체 클래스&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;i&gt;CeoMemberService&lt;/i&gt;&lt;/span&gt;&lt;/b&gt; : 사장님 회원 서비스 구현체 클래스&lt;/li&gt;
&lt;li&gt;...&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;코드에 정답이 존재하는 것은 아닙니다. 물론 제가 적용한 방법보다 좀 더 의미적으로 명확하고 좋은 방법들이 존재할 것입니다. 하지만 이러한 구조로 설계를 변경하면서 기존에 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;i&gt;MemberService&lt;/i&gt;&lt;/span&gt;&lt;/b&gt; 와 &lt;b&gt;&lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;i&gt;MemberServiceImpl&lt;/i&gt;&lt;/span&gt;&lt;/b&gt; 로 설계했을 때 보다 누가봐도 충분히 이름만 가지고 동작과 역할이 유추가능한 클래스 이름을 가지고 있으며, 기존에 추상화를 사용함으로써 얻을 수 있는 장점을 그대로 유지할 수 있는 구조로 개선할 수 있게 되었습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github.com/f-lab-edu/daangn-market-used-trading&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1608593357002&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;f-lab-edu/daangn-market-used-trading&quot; data-og-description=&quot;중고 거래부터 동네 정보까지 당근마켓을 모티브로 만든 중고거래 플랫폼 API 서버. Contribute to f-lab-edu/daangn-market-used-trading development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; data-og-url=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/zW9og/hyIEH3hxwe/n2FQKQR1evklRJ7dtFenh0/img.png?width=278&amp;amp;height=278&amp;amp;face=0_0_278_278&quot;&gt;&lt;a href=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/zW9og/hyIEH3hxwe/n2FQKQR1evklRJ7dtFenh0/img.png?width=278&amp;amp;height=278&amp;amp;face=0_0_278_278');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;f-lab-edu/daangn-market-used-trading&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;중고 거래부터 동네 정보까지 당근마켓을 모티브로 만든 중고거래 플랫폼 API 서버. Contribute to f-lab-edu/daangn-market-used-trading development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;blockquote data-ke-size=&quot;size14&quot; data-ke-style=&quot;style2&quot;&gt;참조&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;http://www.yes24.com/Product/Goods/7516911&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;www.yes24.com/Product/Goods/7516911&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://multifrontgarden.tistory.com/97&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;multifrontgarden.tistory.com/97&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://javarevealed.wordpress.com/2013/10/09/bridge-design-pattern/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;javarevealed.wordpress.com/2013/10/09/bridge-design-pattern/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://stackoverflow.com/questions/2814805/java-interfaces-implementation-naming-convention&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;stackoverflow.com/questions/2814805/java-interfaces-implementation-naming-convention&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>프로젝트/당근마켓 API 서버 프로젝트</category>
      <category>Bridge Pattern</category>
      <category>Interface</category>
      <category>OOP</category>
      <category>service</category>
      <category>ServiceImpl</category>
      <category>Spring</category>
      <author>Dev-SeeOne</author>
      <guid isPermaLink="true">https://see-one.tistory.com/1</guid>
      <comments>https://see-one.tistory.com/1#entry1comment</comments>
      <pubDate>Wed, 17 Mar 2021 20:38:45 +0900</pubDate>
    </item>
    <item>
      <title>무심코 적용한 Validation 의심해볼 필요가 있습니다</title>
      <link>https://see-one.tistory.com/14</link>
      <description>&lt;p&gt;&amp;nbsp;유효성 검사로직은 클라이언트와 서버 애플리케이션 모두에서 필수적으로 진행되어야 하는 요소입니다. 스프링 부트에서 제공하는 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;spring-boot-starter-validation&lt;/span&gt;&lt;/b&gt; 모듈을 통해서 Java 표준에서 제공하는 어노테이션으로 쉽게 유효성 검사를 수행할 수 있습니다. 스프링 부트는 유효성 검사 표준의 구현체로 &lt;b&gt;&lt;span style=&quot;color: #ee2323;&quot;&gt;hibernate-validator&lt;/span&gt;&lt;/b&gt; 를 사용하고 있으며 2.4.2 모듈을 기준으로 6.1.7 버전의 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;hibernate-validatior&lt;/b&gt;&lt;/span&gt; 를 사용하고 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;프로젝트에 무의식적으로 적용한 Java 표준 유효성검사 어노테이션들은 표준이라는 이유로 검증 없이 사용되는 경우가 많습니다. 하지만 돌다리도 두들겨 보고 건너라는 말이 있듯이 유효성 검사가 올바르게 수행되고 있는지 개발자들은 항상 의심하고 이를 검증해볼 필요성이 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;표준 Validation 어노테이션을 적용한 회원 엔티티 클래스&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;회원을 저장할 간단한 클래스를 생성해봅시다. Member라는 클래스는 사용자의 이름과, 나이, 이메일 주소를 필드로 가지고 있으며 각각의 필드에 유효성 검사 어노테이션을 적용하였습니다.&lt;/p&gt;
&lt;pre id=&quot;code_1615317425301&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Member {

  @NotBlank(message = &quot;이름은 반드시 입력되어야 하며 null 또는 공백문자일 수 없습니다.&quot;)
  private String name;

	@Max(value = 50, message = &quot;50살 이하의 회원만 가입할 수 있습니다.&quot;)
  @Min(value = 20, message = &quot;20살 이상의 회원만 가입할 수 있습니다.&quot;)
  @NotNull(message = &quot;나이는 반드시 입력되어야 하며 null값일 수 없습니다.&quot;)
  private Integer age;

  @Email(message = &quot;유효하지 않은 이메일 주소입니다.&quot;)
  private String email;

  public Member(String name, Integer age, String email) {
      this.name = name;
      this.age = age;
      this.email = email;
  }

}&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&amp;nbsp;&lt;/h2&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;Member 클래스 필드 유효성 검사 테스트 작성&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;이제 적용한 어노테이션들이 정상적으로 동작하는지 간단한 테스트 코드를 작성해봅시다. 테스트 코드는 JUnit 5를 기준으로 작성하였으며 Validator 클래스의 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;validate&lt;/span&gt; 메서드를 수행한 결과를 통해 유효성 검사에 실패하였을 때 메세지가 출력되는지를 확인할 것입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615317482012&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;private Validator validator;

@BeforeEach
void setUp() {
	validation = Validation.buildDefaultValidatorFactory().getValidator();
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;먼저 모든 각각의 테스트에서 사용할 Validator 객체를 생성하는 공통 메서드를 작성하겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615317502306&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
@DisplayName(&quot;@사용자 나이에 대한 유효성 검사가 정상적으로 동작하는지 테스트&quot;)
void memberAgeValidationTest() {
    Member m1 = new Member(&quot;둘리&quot;, 100, &quot;doollee@gmail.com&quot;);
    Member m2 = new Member(&quot;둘리&quot;, 50, &quot;doollee@gmail.com&quot;);
    Member m3 = new Member(&quot;둘리&quot;, 20, &quot;doollee@gmail.com&quot;);
    Member m4 = new Member(&quot;둘리&quot;, 4, &quot;doollee@gmail.com&quot;);
    Member m5 = new Member(&quot;둘리&quot;, null, &quot;doollee@gmail.com&quot;);

    List&amp;lt;Member&amp;gt; members = List.of(m1, m2, m3, m4, m5);

    for (Member member : members) {
        Set&amp;lt;ConstraintViolation&amp;lt;Member&amp;gt;&amp;gt; violations = validator.validate(member);

        if(violations.isEmpty()) {
            System.out.println(&quot;member = &quot; + member.toString() + &quot;은/는 유효한 회원입니다.&quot;);
        } else {
            System.out.println(&quot;member = &quot; + member.toString() + &quot;은/는 유효하지 않은 회원입니다.&quot;);
            for (ConstraintViolation&amp;lt;Member&amp;gt; violation : violations) {
                System.out.println(violation.getMessage());
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;먼저 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;@Max&lt;/span&gt; 와 &lt;span data-token-index=&quot;3&quot; data-reactroot=&quot;&quot;&gt;@Min&lt;/span&gt; 어노테이션이 정상적으로 동작하는지 검증하는 테스트를 작성하였습니다. 해당 테스트의 실행 결과는 아래와 같으며 정상적으로 유효성 검사를 수행하고 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;member = Member(name=둘리, age=100, email=doollee@gmail.com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;50살 이하의 회원만 가입할 수 있습니다. &lt;br /&gt;member = Member(name=둘리, age=50, email=doollee@gmail.com)은/는 유효한 회원입니다. &lt;br /&gt;member = Member(name=둘리, age=20, email=doollee@gmail.com)은/는 유효한 회원입니다. &lt;br /&gt;member = Member(name=둘리, age=4, email=doollee@gmail.com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;20살 이상의 회원만 가입할 수 있습니다. member = Member(name=둘리, age=null, email=doollee@gmail.com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;나이는 반드시 입력되어야 하며 null값일 수 없습니다.&lt;/blockquote&gt;
&lt;p&gt;다음으로 사용자 이름 필드에 대한 유효성검사를 수행하도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615317582954&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
@DisplayName(&quot;사용자 이름에 대한 유효성 검사가 정상적으로 동작하는지 테스트&quot;)
void memberNameValidationTest() {
    Member m1 = new Member(&quot;둘리&quot;, 20, &quot;doollee@gmail.com&quot;);
    Member m2 = new Member(&quot;&quot;, 20, &quot;doollee@gmail.com&quot;);
    Member m3 = new Member(&quot; &quot;, 20, &quot;doollee@gmail.com&quot;);
    Member m4 = new Member(&quot;\t&quot;, 20, &quot;doollee@gmail.com&quot;);
    Member m5 = new Member(null, 20, &quot;doollee@gmail.com&quot;);

    List&amp;lt;Member&amp;gt; members = List.of(m1, m2, m3, m4, m5);

    for (Member member : members) {
        Set&amp;lt;ConstraintViolation&amp;lt;Member&amp;gt;&amp;gt; violations = validator.validate(member);

        if(violations.isEmpty()) {
            System.out.println(&quot;member = &quot; + member.toString() + &quot;은/는 유효한 회원입니다.&quot;);
        } else {
            System.out.println(&quot;member = &quot; + member.toString() + &quot;은/는 유효하지 않은 회원입니다.&quot;);
            for (ConstraintViolation&amp;lt;Member&amp;gt; violation : violations) {
                System.out.println(violation.getMessage());
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;회원 이름에 대한 유효성 검사 테스트 코드는 다음과 같으며 해당 테스트를 실행한 결과 정상적으로 유효성 검사를 수행하고 있음을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;member = Member(name=둘리, age=20, email=doollee@gmail.com)은/는 유효한 회원입니다. &lt;br /&gt;member = Member(name=, age=20, email=doollee@gmail.com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;이름은 반드시 입력되어야 하며 null 또는 공백문자일 수 없습니다. &lt;br /&gt;member = Member(name= , age=20, email=doollee@gmail.com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;이름은 반드시 입력되어야 하며 null 또는 공백문자일 수 없습니다. member = Member(name= , age=20, email=doollee@gmail.com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;이름은 반드시 입력되어야 하며 null 또는 공백문자일 수 없습니다. &lt;br /&gt;member = Member(name=null, age=20, email=doollee@gmail.com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;이름은 반드시 입력되어야 하며 null 또는 공백문자일 수 없습니다.&lt;/blockquote&gt;
&lt;p&gt;마지막으로 회원 이메일에 대한 유효성 검사 테스트를 작성하도록 하겠습니다. 모든 이메일 패턴에 대한 경우를 테스트할 수 없지만 이메일의 도메인 패턴과 @ 문자를 중심으로 테스트를 수행하도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615317637064&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Test
@DisplayName(&quot;사용자 이메일 주소에 대한 유효성 검사가 정상적으로 동작하는지 테스트&quot;)
void memberEmailValidationTest() {
    Member m1 = new Member(&quot;둘리&quot;, 20, &quot;doollee@gmail.com&quot;);
    Member m2 = new Member(&quot;둘리&quot;, 20, &quot;doollee.gmail.com&quot;);
    Member m3 = new Member(&quot;둘리&quot;, 20, &quot;doolleegmail.com&quot;);
    Member m4 = new Member(&quot;둘리&quot;, 20, &quot;doollee@gmail/com&quot;);
    Member m5 = new Member(&quot;둘리&quot;, 20, &quot;doollee@gmail//.com&quot;);
    Member m6 = new Member(&quot;둘리&quot;, 20, &quot;doollee@gmail//./com&quot;);
    Member m7 = new Member(&quot;둘리&quot;, 20, &quot;doollee@gmailcom&quot;);

    List&amp;lt;Member&amp;gt; members = List.of(m1, m2, m3, m4, m5, m6, m7);

    for (Member member : members) {
        Set&amp;lt;ConstraintViolation&amp;lt;Member&amp;gt;&amp;gt; violations = validator.validate(member);

        if(violations.isEmpty()) {
            System.out.println(&quot;member = &quot; + member.toString() + &quot;은/는 유효한 회원입니다.&quot;);
        } else {
            System.out.println(&quot;member = &quot; + member.toString() + &quot;은/는 유효하지 않은 회원입니다.&quot;);
            for (ConstraintViolation&amp;lt;Member&amp;gt; violation : violations) {
                System.out.println(violation.getMessage());
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;사용자 유효성 검사에 대한 테스트 코드는 다음과 같으며 테스트를 수행하기에 앞서서 결과를 예상해보았을 때 정상적으로 유효성 검사를 수행할 경우 1번을 제외한 나머지 경우에 대해서 유효성 검사를 실패해야 합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 예상과 다르게 테스트를 수행한 결과를 확인하면 아래와 같은 결과를 얻을 수 있습니다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;br /&gt;member = Member(name=둘리, age=20, email=doollee@gmail.com)은/는 유효한 회원입니다. &lt;br /&gt;member = Member(name=둘리, age=20, email=doollee.gmail.com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;유효하지 않은 이메일 주소입니다. &lt;br /&gt;member = Member(name=둘리, age=20, email=doolleegmail.com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;유효하지 않은 이메일 주소입니다. &lt;br /&gt;member = Member(name=둘리, age=20, email=doollee@gmail/com)은/는 유효한 회원입니다. &lt;br /&gt;member = Member(name=둘리, age=20, email=doollee@gmail//.com)은/는 유효한 회원입니다. &lt;br /&gt;member = Member(name=둘리, age=20, email=doollee@gmail//./com)은/는 유효한 회원입니다. &lt;br /&gt;member = Member(name=둘리, age=20, email=doollee@gmailcom)은/는 유효한 회원입니다.&lt;/blockquote&gt;
&lt;p&gt;@ 문자가 없는 경우에는 유효하지 않은 이메일로 정상 판단하지만 이메일 도메인 주소를 판단하는데 있어서 비정상적인 패턴이 등장해도 올바른 이메일로 취급하고 있음을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;표준 유효성검사 어노테이션만 믿고 회원 이메일에 대한 유효성을 검사하여 회원가입 서비스를 만들었다면 어떻게 될까요?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;만약 회원 이메일을 변경하거나 알람 서비스를 이메일로 보내는 기능을 수행하는 경우에는 당연하게도 기능이 정상적으로 동작하지 않을 것입니다. 단순히 시스템 상의 기능 오류는 해프닝으로 끝날 수 있습니다. 하지만 이러한 버그를 악용한 클라이언트가 있다면 어떻게 될까요? 이는 단순한 해프닝을 넘어서 회사의 금전적인 손실로까지 이어질 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;멘토로 계시던 선배 개발자 분께서 해주신 말 중에서 크게 와닿는 말이 있었는데 클라이언트는 개발자가 생각하는 것만큼 착하지 않다는 것입니다. 매번 클라이언트가 개발자가 의도한 입력을 서버로 요청한다는 보장이 없기 때문에 개발자는 서비스를 개발함에 있어서 항상 기능이 올바로 동작하는지 의심하고 이를 반드시 검증해야 합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;@Email를 이용한 유효성 검사는 어떻게 동작할까요?&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;왜 이러한 문제가 발생하는지 @Email 어노테이션을 통한 유효성검사가 어떠한 과정을거쳐서 수행되는지 디버깅을 통해 알아보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;중간 과정을 생략하고 핵심 로직만 살펴보면 내부적으로 EmailValidator 클래스의 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;isValid&lt;/span&gt; 메서드를 호출하는 것을 확인할 수 있습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;v1.png&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;572&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bz8V6f/btqZMmP2Hvw/rGySVqzxpeopafJmXftlyK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bz8V6f/btqZMmP2Hvw/rGySVqzxpeopafJmXftlyK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bz8V6f/btqZMmP2Hvw/rGySVqzxpeopafJmXftlyK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbz8V6f%2FbtqZMmP2Hvw%2FrGySVqzxpeopafJmXftlyK%2Fimg.png&quot; data-filename=&quot;v1.png&quot; data-origin-width=&quot;887&quot; data-origin-height=&quot;572&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;isValid&lt;/span&gt; 메서드에서는 다시 부모 클래스인 AbstractEmailValidator 클래스의 &lt;span data-token-index=&quot;2&quot; data-reactroot=&quot;&quot;&gt;isValid&lt;/span&gt; 를 호출하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;v2.png&quot; data-origin-width=&quot;1263&quot; data-origin-height=&quot;497&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/OMDPL/btqZIy4ZCSS/UdG3ekHZvTW2e9OJeALRU0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/OMDPL/btqZIy4ZCSS/UdG3ekHZvTW2e9OJeALRU0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/OMDPL/btqZIy4ZCSS/UdG3ekHZvTW2e9OJeALRU0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FOMDPL%2FbtqZIy4ZCSS%2FUdG3ekHZvTW2e9OJeALRU0%2Fimg.png&quot; data-filename=&quot;v2.png&quot; data-origin-width=&quot;1263&quot; data-origin-height=&quot;497&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;AbstractEmailValidator 클래스에는 각종 패턴에 대한 정규표현식들이 정의되어 있으며 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;isValid&lt;/span&gt; 메서드 내부에서는 먼저 입력받은 문자열이 @ 문자를 가지고 있는지 판단합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이후 @ 문자를 기준으로 앞의 부분문자열을 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;localPart&lt;/span&gt; 로 뒤의 부분문자열을 &lt;span data-token-index=&quot;3&quot; data-reactroot=&quot;&quot;&gt;domainPart&lt;/span&gt; 로 구분하여 각각을 &lt;span data-token-index=&quot;5&quot; data-reactroot=&quot;&quot;&gt;isValidEmailLocalPart&lt;/span&gt; 와 &lt;span data-token-index=&quot;7&quot; data-reactroot=&quot;&quot;&gt;DomainNameUtil.isValidEmailDomainAddress&lt;/span&gt; 메서드를 호출하여 유효성검사를 수행하고 있는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 중에서도 문제가 되는 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;DomainNameUtil.isValidEmailDomainAddress&lt;/span&gt; 부분을 살펴보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;v3.png&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;592&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/biFVLJ/btqZGlERGkX/Dj1zilkrNfoegJLYxbYEC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/biFVLJ/btqZGlERGkX/Dj1zilkrNfoegJLYxbYEC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/biFVLJ/btqZGlERGkX/Dj1zilkrNfoegJLYxbYEC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbiFVLJ%2FbtqZGlERGkX%2FDj1zilkrNfoegJLYxbYEC1%2Fimg.png&quot; data-filename=&quot;v3.png&quot; data-origin-width=&quot;911&quot; data-origin-height=&quot;592&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;DomainNameUtil 클래스의 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;isValidEmailDomainAddress&lt;/span&gt; 메서드를 살펴보면 &lt;span data-token-index=&quot;3&quot; data-reactroot=&quot;&quot;&gt;EMAIL_DOMAIN_PATTERN&lt;/span&gt; 이라는 도메인 관련 정규 표현식을 정의하고 있으며 이를 &lt;span data-token-index=&quot;5&quot; data-reactroot=&quot;&quot;&gt;isValidDomainAddress&lt;/span&gt; 메서드의 파라미터로 전달하고 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;v4.png&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;519&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dfeIpy/btqZEBIffTt/WYRokwQacvKdz4ICKk4f1K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dfeIpy/btqZEBIffTt/WYRokwQacvKdz4ICKk4f1K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dfeIpy/btqZEBIffTt/WYRokwQacvKdz4ICKk4f1K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdfeIpy%2FbtqZEBIffTt%2FWYRokwQacvKdz4ICKk4f1K%2Fimg.png&quot; data-filename=&quot;v4.png&quot; data-origin-width=&quot;805&quot; data-origin-height=&quot;519&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;메서드 자체에서는 해당 정규표현식의 패턴에 부합한지 여부를 확인하고 있기 때문에 별다른 로직이 없지만 이를 통해 확인할 수 있는 것은 해당 정규표현식이 도메인을 검증하기에 충분하지 않다는 사실입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;프로젝트에 올바른 이메일 유효성 검사 적용하기&lt;/span&gt;&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span data-token-index=&quot;0&quot; data-reactroot=&quot;&quot;&gt;만약 서비스에 사용자 이메일 관련 유효성검사를 진행해야 한다면 정규 표현식을 재정의함을 통해서 유효성 검사로 인한 문제를 예방할 수 있습니다. &lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;pre id=&quot;code_1615317870531&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public class Member {

	...

	@Email(message = &quot;유효하지 않은 이메일 형식입니다.&quot;,
        regexp = &quot;^[\\w!#$%&amp;amp;'*+/=?`{|}~^-]+(?:\\.[\\w!#$%&amp;amp;'*+/=?`{|}~^-]+)*@(?:[a-zA-Z0-9-]+\\.)+[a-zA-Z]{2,6}$&quot;)
	private String email;

}&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;먼저 AbstractEmailValidator 클래스의 정규표현식으로 유효성 검사를 수행하는것을 같지만 이후 EmailValidator 클래스의 &lt;span data-token-index=&quot;1&quot; data-reactroot=&quot;&quot;&gt;isValid&lt;/span&gt; 메서드에서 사용자정의 정규표현식이 존재할 경우 다시 한번 더 검사를 수행하도록 동작하고 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;v5.png&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;325&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cD6u4w/btqZMmWOhKK/3svk20pOmBTwkLx80J4wp1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cD6u4w/btqZMmWOhKK/3svk20pOmBTwkLx80J4wp1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cD6u4w/btqZMmWOhKK/3svk20pOmBTwkLx80J4wp1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcD6u4w%2FbtqZMmWOhKK%2F3svk20pOmBTwkLx80J4wp1%2Fimg.png&quot; data-filename=&quot;v5.png&quot; data-origin-width=&quot;799&quot; data-origin-height=&quot;325&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;이를 적용하여 다시 테스트를 수행해보면 다음과 같이 정상적으로 유효성 검사를 수행하는 것을 확인할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style3&quot;&gt;&lt;br /&gt;member = Member(name=둘리, age=20, email=doollee@gmail.com)은/는 유효한 회원입니다. &lt;br /&gt;member = Member(name=둘리, age=20, email=doollee.gmail.com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;유효하지 않은 이메일 형식입니다. &lt;br /&gt;member = Member(name=둘리, age=20, email=doolleegmail.com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;유효하지 않은 이메일 형식입니다. &lt;br /&gt;member = Member(name=둘리, age=20, email=doollee@gmail/com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;유효하지 않은 이메일 형식입니다. &lt;br /&gt;member = Member(name=둘리, age=20, email=doollee@gmail//.com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;유효하지 않은 이메일 형식입니다. &lt;br /&gt;member = Member(name=둘리, age=20, email=doollee@gmail//./com)은/는 유효하지 않은 회원입니다. &lt;br /&gt;유효하지 않은 이메일 형식입니다. &lt;br /&gt;member = Member(name=둘리, age=20, email=doollee@gmailcom)은/는 유효하지 않은 회원입니다. &lt;br /&gt;유효하지 않은 이메일 형식입니다.&lt;/blockquote&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&amp;nbsp;Java 표준이라고 생각하면 수많은 뛰어난 개발자들에 의해서 설계되고 철저한 검증 과정을 거쳐서 적용된 것이지만 이메일 유효성 검사에 대한 예외 케이스가 존재하는 것 처럼 사람이 만들어낸 결과물 중에 &quot;절대&quot;, &quot;완벽&quot;이라는 것은 존재하지 않습니다. 때문에 개발자는 항상 이를 적용하기에 앞서 의심하고 검증해보는 과정이 필요합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github.com/f-lab-edu/daangn-market-used-trading&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1615317972394&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;f-lab-edu/daangn-market-used-trading&quot; data-og-description=&quot;중고 거래부터 동네 정보까지 당근마켓을 모티브로 만든 중고거래 플랫폼 API 서버 토이 프로젝트 - f-lab-edu/daangn-market-used-trading&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; data-og-url=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hI2Qh/hyJvFSeUpB/Pc7qHTKpUukfmVSwD9RstK/img.png?width=278&amp;amp;height=278&amp;amp;face=0_0_278_278,https://scrap.kakaocdn.net/dn/IRWAU/hyJwZ9mava/qKYH9v5aC7qE4zGNE3Y7y0/img.png?width=1080&amp;amp;height=1080&amp;amp;face=0_0_1080_1080,https://scrap.kakaocdn.net/dn/i31JU/hyJvU9GohY/oTsgrwdeHCSJ6uh1NnEWKk/img.png?width=1666&amp;amp;height=467&amp;amp;face=0_0_1666_467&quot;&gt;&lt;a href=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hI2Qh/hyJvFSeUpB/Pc7qHTKpUukfmVSwD9RstK/img.png?width=278&amp;amp;height=278&amp;amp;face=0_0_278_278,https://scrap.kakaocdn.net/dn/IRWAU/hyJwZ9mava/qKYH9v5aC7qE4zGNE3Y7y0/img.png?width=1080&amp;amp;height=1080&amp;amp;face=0_0_1080_1080,https://scrap.kakaocdn.net/dn/i31JU/hyJvU9GohY/oTsgrwdeHCSJ6uh1NnEWKk/img.png?width=1666&amp;amp;height=467&amp;amp;face=0_0_1666_467');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;f-lab-edu/daangn-market-used-trading&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;중고 거래부터 동네 정보까지 당근마켓을 모티브로 만든 중고거래 플랫폼 API 서버 토이 프로젝트 - f-lab-edu/daangn-market-used-trading&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트/당근마켓 API 서버 프로젝트</category>
      <author>Dev-SeeOne</author>
      <guid isPermaLink="true">https://see-one.tistory.com/14</guid>
      <comments>https://see-one.tistory.com/14#entry14comment</comments>
      <pubDate>Wed, 10 Mar 2021 03:52:20 +0900</pubDate>
    </item>
    <item>
      <title>분산서버 환경에서 발생할 수 있는 Session 불일치 문제를 해결해봅시다 (1) - Sticky Session</title>
      <link>https://see-one.tistory.com/10</link>
      <description>&lt;p&gt;&amp;nbsp;앞선 포스트에서 서버의 확장 방법인&amp;nbsp;&lt;b&gt;Scale Up&amp;nbsp;&lt;/b&gt;과&amp;nbsp;&lt;b&gt;Scale Out&lt;/b&gt; 에 대해서 알아보았습니다. 그리고 당근마켓 서버 API를 개발하는 프로젝트에서 서비스의 특징과 가용성을 고려하여 &lt;b&gt;Scale Out&amp;nbsp;&lt;/b&gt;을 이용한 확장을 적용하기로 하였는데 이에 앞서서 먼저 해결해야할 문제가 존재합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;b&gt;&amp;nbsp;Scale Out&lt;/b&gt; 을 적용하기 위해서는 반드시 데이터 불일치라는 문제를 해결해야한다고 언급했습니다. 일반적으로 HTTP 방식의 통신과 웹 애플리케이션 서버는 무상태성이라는 특징과 서버에 클라이언트의 정보를 저장하고있지 않기 때문에 데이터 불일치 문제로부터 자유롭게&lt;b&gt; Scale Out&amp;nbsp;&lt;/b&gt;방식을 적용한 확장이 가능합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;하지만 세션을 이용한다면 이야기가 달라집니다. 현재 프로젝트에서는 사용자의 로그인 정보를 처리하기 위해서 세션을 이용한 로그인 방식을 사용하고 있습니다. 즉, 서버에 클라이언트 데이터를 저장하고 있기 때문에 데이터 불일치 문제로부터 자유로울 수 없다는 것인데요 그럼 어떻게 서버 확장에 따른 데이터 불일치 문제를 해결할 수 있을지 한번 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;b&gt;Sticky Session 방식은 어떻게 세션 불일치 문제를 해결할 수 있을까요&lt;/b&gt;&lt;/h2&gt;
&lt;p&gt;먼저 Sticky Session 이라는 방식이 존재합니다. 단어의 뜻에서도 유추할 수 있듯이 Sticky Session 방식은 고정된 세션이라는 의미 그대로 클라이언트의 요청을 특정 서버에서 처리하도록 로드밸런싱 하여서 클라이언트 요청과 서버를 하나로 묶어서 고정시키는 방식을 사용합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;sticky session.png&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;406&quot; width=&quot;636&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/tZ9Ml/btqZGl5V7IB/GPvlj2uE4iiaewbGfEIDSk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/tZ9Ml/btqZGl5V7IB/GPvlj2uE4iiaewbGfEIDSk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/tZ9Ml/btqZGl5V7IB/GPvlj2uE4iiaewbGfEIDSk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FtZ9Ml%2FbtqZGl5V7IB%2FGPvlj2uE4iiaewbGfEIDSk%2Fimg.png&quot; data-filename=&quot;sticky session.png&quot; data-origin-width=&quot;731&quot; data-origin-height=&quot;406&quot; width=&quot;636&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그림에서처럼 각각의 사용자가 보낸 로그인 요청에 의해 사용자의 로그인 정보가 각각의 서버의 세션 저장소에 저장된다고 가정해봅시다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;처음 클라이언트 요청은 어느 서버로도 보내질 수 있지만 로그인 이후 특정 서버에 사용자의 로그인 정보가 저장된 다음에는 로드밸런서에 의해 특정 클라이언트의 요청은 특정 서버로 연결되어 고정된 서버의 세션만을 사용하게 됩니다. 이 과정에서 클라이언트는 자신의 정보를 쿠키에 담아서 보내고 로드벨런서는 클라이언트 요청 정보에 포함된 쿠키를 통해 지정된 서버로 요청을 전달합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Sticky Session은 세션이 유지되는 동안 클라이언트는 동일한 서버만을 사용하기 때문에 데이터 불일치 문제로부터 자유로울 수 있습니다. 하지만 Sticky Session 방식은 여러가지 단점과 한계점이 존재합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;특정 서버에 트래픽이 집중되는 문제&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;만약 현재 사용하는 로드밸런서가 사용자의 IP 정보를 기준으로 로드밸런싱을 적용하는 L4 로드밸런서라고 가정해봅시다. 그리고 대학교나 어떤 기관에서는 하나의 공공 와이파이를 사용하여 해당 서버로 요청을 보내 업무를 처리한다고 가정해봅시다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-filename=&quot;l4.png&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;406&quot; width=&quot;757&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ALSOv/btqZDO17C7g/JMDsPcBWfOgwv1vk3Mo4E0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ALSOv/btqZDO17C7g/JMDsPcBWfOgwv1vk3Mo4E0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ALSOv/btqZDO17C7g/JMDsPcBWfOgwv1vk3Mo4E0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FALSOv%2FbtqZDO17C7g%2FJMDsPcBWfOgwv1vk3Mo4E0%2Fimg.png&quot; data-filename=&quot;l4.png&quot; data-origin-width=&quot;872&quot; data-origin-height=&quot;406&quot; width=&quot;757&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;기관 내에서 사용하는 컴포넌트들의 IP는 사설 IP로써 동일한 와이파이를 사용하더라도 각기 다른 IP를 가지고 있지만 인터넷 상에서는 해당 기관의 클라이언트들이 하나의 &lt;b&gt;공인IP&lt;/b&gt;로써 보일 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;때문에 클라이언트의 요청은 L4 로드밸런서에 의해서 동일한 서버로 로드밸런싱이 이루어지게 될 텐데 이는 결국 하나의 서버에 트래픽이 집중되는 것을 의미합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;앞서서 특정 서버로 부하가 집중되는 문제를 해결하기 위해서 Scale Out을 적용했지만 결국 다시 특정 서버로 부하가 집중되는 문제가 되풀이되면서 Scale Out이 가지고 있는 장점을 활용하지 못하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;뿐만 아니라 서버에 장애가 발생하면 로드 밸런서는 클라이언트 요청을 다른 서버로 연결할 것이고 클라이언트는 다시 로그인만 하면 될 것입니다. 하지만 문제는 이러한 상황이 계속 반복될 것이라는 것에 있습니다. 결국 어떻게 보면 클라이언트 입장에서는 서버들이 각각 단일 고장점으로써 작용하고 있다고 봐도 무방할 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&lt;b&gt;&amp;nbsp;서버의 이용률과 효율성 문제&amp;nbsp;&lt;/b&gt;&lt;/h4&gt;
&lt;p&gt;위와 같이 장애가 발생한 서버가 복구되었다고 가정해봅시다. 복구된 서버는 이전 클라이언트들의 로그인 정보가 여전히 세션저장소에&amp;nbsp; 저장되어있고 클라이언트 요청을 처리할 준비를 끝마친 상태입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;하지만 해당 서버는 더이상 이전 클라이언트 요청을 처리할 수 없습니다. 이미 클라이언트는 로드밸런서에 의해 다른 서버로 요청이 연결되었기 때문에 클라이언트의 요청은 더이상 이전 서버로 연결되지 않습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;결국 이러한 문제는 서버의 이용률과 효율성이라는 측면에서 문제를 가지고 있습니다. 서버가 복구되었지만 이전 클라이언트의 요청을 더 이상 처리할 수 없으며 이제는 더 이상 필요없는 이전 클라이언트의 로그인 정보를 여전히 세션 저장소에 저장하고 있기 때문에 불필요한 메모리를 낭비하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;이러한 단점들로 인해서 Sticky Session은 데이터 불일치 문제를 해결해줄지는 몰라도 Scale Out 방식을 통해 얻을 수 있는 가용성과 부하분산이라는 단점을 제대로 활용하지 못하는 아이러니한 상황이 발생하게 됩니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다면 이러한 다중 서버 환경에서는 데이터 불일치 문제를 해결하기 위해서 이러한 단점을 감수해야만 하는 것일까요? 그렇지 않습니다.&amp;nbsp; Sticky Session 뿐만 아니라 다중 서버환경에서 데이터 정합성을 해결할 수 있는 방법은 Session Clustering, Session Storage를 외부로 분리하는 등의 다양한 방법이 존재합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다음 포스트에서는 Session Clustering을 이용하여 데이터 불일치 문제를 해결하는 방법에 대해 알아보겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;a href=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github.com/f-lab-edu/daangn-market-used-trading&lt;/a&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1615315498593&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;f-lab-edu/daangn-market-used-trading&quot; data-og-description=&quot;중고 거래부터 동네 정보까지 당근마켓을 모티브로 만든 중고거래 플랫폼 API 서버 토이 프로젝트 - f-lab-edu/daangn-market-used-trading&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; data-og-url=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/hI2Qh/hyJvFSeUpB/Pc7qHTKpUukfmVSwD9RstK/img.png?width=278&amp;amp;height=278&amp;amp;face=0_0_278_278,https://scrap.kakaocdn.net/dn/IRWAU/hyJwZ9mava/qKYH9v5aC7qE4zGNE3Y7y0/img.png?width=1080&amp;amp;height=1080&amp;amp;face=0_0_1080_1080,https://scrap.kakaocdn.net/dn/i31JU/hyJvU9GohY/oTsgrwdeHCSJ6uh1NnEWKk/img.png?width=1666&amp;amp;height=467&amp;amp;face=0_0_1666_467&quot;&gt;&lt;a href=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/hI2Qh/hyJvFSeUpB/Pc7qHTKpUukfmVSwD9RstK/img.png?width=278&amp;amp;height=278&amp;amp;face=0_0_278_278,https://scrap.kakaocdn.net/dn/IRWAU/hyJwZ9mava/qKYH9v5aC7qE4zGNE3Y7y0/img.png?width=1080&amp;amp;height=1080&amp;amp;face=0_0_1080_1080,https://scrap.kakaocdn.net/dn/i31JU/hyJvU9GohY/oTsgrwdeHCSJ6uh1NnEWKk/img.png?width=1666&amp;amp;height=467&amp;amp;face=0_0_1666_467');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;f-lab-edu/daangn-market-used-trading&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;중고 거래부터 동네 정보까지 당근마켓을 모티브로 만든 중고거래 플랫폼 API 서버 토이 프로젝트 - f-lab-edu/daangn-market-used-trading&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트/당근마켓 API 서버 프로젝트</category>
      <author>Dev-SeeOne</author>
      <guid isPermaLink="true">https://see-one.tistory.com/10</guid>
      <comments>https://see-one.tistory.com/10#entry10comment</comments>
      <pubDate>Wed, 10 Mar 2021 03:45:39 +0900</pubDate>
    </item>
    <item>
      <title>사용자가 증가하면 서버를 어떻게 확장해야할까?</title>
      <link>https://see-one.tistory.com/4</link>
      <description>&lt;p&gt;현재 진행중인 프로젝트는 중고거래 플랫폼인 &lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;당근마켓&lt;/span&gt;&lt;/b&gt;의 API 서버를 구현해보는 토이 프로젝트입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #333333;&quot;&gt;&lt;b&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;당근마켓&lt;/span&gt;&lt;/b&gt;은 2020.9월을 기준으로 &lt;span style=&quot;color: #ee2323;&quot;&gt;&lt;b&gt;월간 이용자 수 1000만&lt;/b&gt;&lt;/span&gt;에 이르는 대규모 서비스입니다. 이와 같은 서비스 플랫폼을 개발하면서 반드시 고민해야하는 문제는 &quot;수많은 사용자가 서비스를 이용한다면 과연 서버가 이를 감당할수 있을까?&quot; 라는 점입니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;565&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bfipiX/btqQZwhL3xQ/qIJcHfCVwokeux2BgOLWR0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bfipiX/btqQZwhL3xQ/qIJcHfCVwokeux2BgOLWR0/img.jpg&quot; data-alt=&quot;https://platum.kr/archives/148249&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bfipiX/btqQZwhL3xQ/qIJcHfCVwokeux2BgOLWR0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbfipiX%2FbtqQZwhL3xQ%2FqIJcHfCVwokeux2BgOLWR0%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;565&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://platum.kr/archives/148249&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;아마도 지금과 같이 하나의 서버로 구성된 서비스는 밀려오는 수많은 클라이언트들을 단 몇초도 감당하지 못할 것입니다. 만약 실제로 운영되는 서비스였다면 이는 곧 회사의 금전적인 손실로 연결될 것입니다.&lt;/p&gt;
&lt;p&gt;그렇다면 이러한 대규모 트래픽을 처리해야하는 문제를 서버는 어떻게 해결할 수 있을까요?&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Scale Up 과 Scale Out 이라는 것이 존재합니다&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;수많은 사용자의 트래픽을 서버가 견딜수 있도록 하려면 어떻게 해야할까요? 간단하게 생각해보면 다음의 두 가지 경우를 고려해볼 수 있을 것 같습니다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더 좋은 성능을 가진 서버로 업그레이드한다.&lt;/li&gt;
&lt;li&gt;서버를 여러대 설치하여 다수의 서버가 이를 처리하도록 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;두 가지 방법 모두 정답이 될 수 있습니다. 앞서 언급한 서버의 성능을 업그레이드 하는 방법은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;i&gt;&lt;b&gt;Scale Up&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 이라고 하고 서버의 개수를 늘리는 방법을 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;i&gt;&lt;b&gt;Scale Out&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 이라고 합니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그럼 Scale Up과 Scale Out에 대해서 좀 더 자세히 알아보도록 하겠습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Scale Up&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Scale Up은 단어 뜻 그대로 수직적인 확장 방법을 의미합니다. 서버에 CPU나 메모리 등을 추가하여 서버 자체의 성능을 증강시켜 처리능력을 향상시키는 방법입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/kynFs/btqQ8MwzF5d/Dw0KZNnpZQZBqXv3H9rIsk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/kynFs/btqQ8MwzF5d/Dw0KZNnpZQZBqXv3H9rIsk/img.png&quot; data-alt=&quot;Scale Up&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/kynFs/btqQ8MwzF5d/Dw0KZNnpZQZBqXv3H9rIsk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FkynFs%2FbtqQ8MwzF5d%2FDw0KZNnpZQZBqXv3H9rIsk%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Scale Up&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Scale Up을 사용한 성능 향상 방법에는 어떠한 장점이 있을까요?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순히 서버의 장비를 추가하거나 교체하면 되기 때문에 구축 및 설계가 간단합니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;별도의 컨트롤러나 네트워크 인프라 비용 등이 발생하지 않습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;별도의 서버를 추가하지 않기 때문에 여러대의 서버를 관리하면서 발생할 수 있는 데이터 정합성 문제에서 자유롭습니다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇다면 Scale Up만으로 무한대로 서버의 성능을 향상시킬 수 있을까요?&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그렇지 않습니다. &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Scale Up의 성능향상에는 한계가 존재합니다.&lt;/span&gt;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 서버에는 추가할 수 있는 부품의 개수가 제한되어 있습니다.&lt;/li&gt;
&lt;li&gt;한정된 자원을 초과하여 성능을 향상시키기 위해서는 서버 자체를 변경해야 합니다.&lt;/li&gt;
&lt;li&gt;매번 장비가 업그레이드 될 때마다 서버의 장비를 교체하는 것은 비용이라는 문제가 존재하기 때문에 지속적인 확장이 불가능합니다.&lt;/li&gt;
&lt;li&gt;하나의 서버에서 모든 트래픽을 감당해야 하기 때문에 장애극복 기능이 현저히 떨어지게 됩니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;여기서 가장 중요한 문제점은 바로 마지막 항목입니다. 아무리 서버의 성능을 향상시키더라도 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;하나의 서버에 트래픽이 집중되는 문제는 여전히 남아있습니다.&lt;/span&gt; 또한 서버를 하나만 사용한다는 것은 곧 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;서버가 다운되면 시스템 전체의 장애로 이어진다는 것입니다.&lt;/span&gt; 때문에 해당 서버가 복구되기 전까지 정상적으로 서비스를 운영할 수 없는 상황이 발생할 것입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Scale Out&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Scale Out은 Scale Up과는 반대로 수평적인 확장을 의미합니다. 서버 자체의 성능을 향상시키는 방법이 아닌 서버의 대수를 늘려서 성능을 향상시키는 방법입니다.&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bGcOIQ/btqQ8N3hE6e/TSGeRU7VKzXFzU0kiDieh0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bGcOIQ/btqQ8N3hE6e/TSGeRU7VKzXFzU0kiDieh0/img.png&quot; data-alt=&quot;Scale Out&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bGcOIQ/btqQ8N3hE6e/TSGeRU7VKzXFzU0kiDieh0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbGcOIQ%2FbtqQ8N3hE6e%2FTSGeRU7VKzXFzU0kiDieh0%2Fimg.png&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;Scale Out&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;Scale Out을 사용한 성능향상 방법에는 어떠한 장점이 있을까요?&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;필요에 따라 서버의 개수를 추가하거나 감소시킬 수 있기 때문에 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;확장이 유연합니다&lt;/span&gt;.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;여러대의 서버를 이용한 분산처리가 가능하기 때문에 특정 서버에 트래픽이 집중되어 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;부하가 발생하는 것을 분산할 수 있습니다&lt;/span&gt;.&lt;/li&gt;
&lt;li&gt;하나의 서버에 장애가 발생하더라도 다른 서버에서 서비스 제공이 가능하기 때문에 시스템의 가용성을 높일 수 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;Scale Up과 마찬가지로 Scale Out 또한 여러가지 한계점을 가지고 있습니다.&amp;nbsp;&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;서버를 추가하는 복잡성 때문에 Scale Up 보다 유연한 확장이 가능하지만 무한정으로 확장이 가능하지는 않습니다.&lt;/li&gt;
&lt;li&gt;여러대의 서버를 관리하기 위한 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;설계와 구현이 복잡해지고 이에 따른 관리 비용 또한 증가&lt;/span&gt;하게 됩니다.&lt;/li&gt;
&lt;li&gt;서버의 개수가 늘어난 만큼 문제 발생의 잠재적인 원인 또한 증가할 것입니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;서버 각각의 성능이나 안정성 면에서는 Scale Up 방식보다 뒤쳐질 수 밖에 없습니다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #f6e199;&quot;&gt;데이터 불일치 문제가 발생&lt;/span&gt;할 수 있습니다.&amp;nbsp;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;Scale Out에서 고민해야하는 가장 큰 문제점은 바로 데이터 불일치 문제입니다. 만약 로그인된 사용자의 정보와 같은 특정 정보가 특정 서버에만 저장되어있다면 어떠한 문제가 발생할까요?&lt;/p&gt;
&lt;p&gt;만약 로드 밸런서에 의해 사용자의 요청이 다른 서버로 매칭된다면 해당 서버에는 사용자의 데이터가 존재하지 않기 때문에 사용자는 다시 로그인을 시도해야할 것입니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;로그인과 같은 문제는 클라이언트 입장에서 다시 로그인을 하면 되기 때문에 간단한 헤프닝으로 끝날 수 있지만 금융 데이터와 같은 민감한 데이터에 이와 같은 문제가 발생한다면 간단한 헤프닝으로 끝날 수 있을까요?&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;때문에&amp;nbsp; &lt;span style=&quot;background-color: #f6e199;&quot;&gt;Scale Out을 이용한 확장 방식에서는 반드시 데이터 정합성에 대한 문제를 고민해야합니다&lt;/span&gt;.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;Scale Up과 Scale Out 중에서 어떠한 확장 방식을 사용해야 할까요?&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;Scale Up과 Scale Out 중 어느 한 가지 방법이 무조건 뛰어나다고 할 수는 없습니다. 서비스할 애플리케이션의 성격이나 스토리지의 용도 등에 따라서 각각의 장단점이 극명하게 나누어지기 때문에 이를 고려한 선택이 필요합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;538&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/wvq1R/btqUmJjEYYj/yf0ORKboLnYKhR7DcB6Txk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/wvq1R/btqUmJjEYYj/yf0ORKboLnYKhR7DcB6Txk/img.jpg&quot; data-alt=&quot;https://www.factioninc.com/blog/hybrid-multi-cloud/scale-up-vs-scale-out-scale-out-nas-meets-multi-cloud/&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/wvq1R/btqUmJjEYYj/yf0ORKboLnYKhR7DcB6Txk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fwvq1R%2FbtqUmJjEYYj%2Fyf0ORKboLnYKhR7DcB6Txk%2Fimg.jpg&quot; data-origin-width=&quot;0&quot; data-origin-height=&quot;0&quot; width=&quot;538&quot; height=&quot;NaN&quot; data-ke-mobilestyle=&quot;widthContent&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot;/&gt;&lt;/span&gt;&lt;figcaption&gt;https://www.factioninc.com/blog/hybrid-multi-cloud/scale-up-vs-scale-out-scale-out-nas-meets-multi-cloud/&lt;/figcaption&gt;
&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;데이터의 정합성을 요구하는 데이터베이스 서버나 금융거래와 같이 빠르고 정확한 처리가 필요하고 잦은 갱신이 발생하는 OLTP (Online Transaction Processing) 환경에서는 고성능의 Scale Up 방식이 적합합니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;반면에 메일 서버, 게시판 서버, 데이터 읽기 전용 애플리케이션, 웹 서버 등 높은 병렬성을 실현하기 쉬우며 데이터의 정합성 유지가 쉬운 환경에서는 Scale Out 방식을 이용한 확장 방식을 통해서 높은 효율을 기대할 수 있습니다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;hr contenteditable=&quot;false&quot; data-ke-type=&quot;horizontalRule&quot; data-ke-style=&quot;style2&quot; /&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;b&gt;당근마켓 API 서버를 확장해봅시다&lt;/b&gt;&lt;/span&gt;&lt;/h2&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;Scale Up과&amp;nbsp; Scale Out 방식에 대해 알아보면서 &lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;당근마켓&lt;/b&gt;&lt;/span&gt;이라는 서비스를 운영할 때에는 어떠한 방법으로 서버를 확장하는 것이 더 적합하고 효율적일지 고민해보았습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;span style=&quot;color: #ef6f53;&quot;&gt;&lt;b&gt;당근마켓&lt;/b&gt;&lt;/span&gt;이라는 서비스의 특성상 평상시에도 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;많은 유저 트래픽&lt;/span&gt;이 발생하며, 수 많은 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;거래 정보를 조회하는 요청을 동시에 그리고 병렬적으로 처리&lt;/span&gt;해야합니다.&amp;nbsp;&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;수 많은 사용자가 이용하는 만큼 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;애플리케이션 자체의 신뢰성 또한 중요하기 때문에 특정 서버에 장애가 발생하더라도 정상적으로 서비스를 제공할 수 있어야 합니다&lt;/span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;이러한 점들을 고려했을 때 트래픽을 분산시켜줄 수 있고, 시스템의 신뢰성이 높은 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;i&gt;&lt;b&gt;Scale Out&lt;/b&gt;&lt;/i&gt;&lt;/span&gt; 방식이 적합할 것이라는 결론을 내릴 수 있었습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;하지만 당근마켓 서비스는 사용자의 로그인 정보와 위치정보 등 특정 데이터를 반드시 필요로하는 서비스이기 때문에 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;i&gt;&lt;b&gt;Scale Out&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;을 적용함으로써 발생할 수 있는 &lt;span style=&quot;background-color: #f6e199;&quot;&gt;데이터 불일치 문제에 대해서는 반드시 해결이 필요로합니다&lt;/span&gt;.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;다음 포스트에서는 &lt;span style=&quot;color: #ef5369;&quot;&gt;&lt;i&gt;&lt;b&gt;Scale Out&lt;/b&gt;&lt;/i&gt;&lt;/span&gt;에서의 데이터 정합성 문제를 해결하기 위해 어떠한 방법들을 고려할 수 있는지 알아보도록 하겠습니다.&lt;/span&gt;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&lt;span style=&quot;color: #000000;&quot;&gt;&lt;a href=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;github.com/f-lab-edu/daangn-market-used-trading&lt;/a&gt;&lt;br /&gt;&lt;/span&gt;&lt;/p&gt;
&lt;figure id=&quot;og_1611292870247&quot; contenteditable=&quot;false&quot; data-ke-type=&quot;opengraph&quot; data-og-type=&quot;object&quot; data-og-title=&quot;f-lab-edu/daangn-market-used-trading&quot; data-og-description=&quot;중고 거래부터 동네 정보까지 당근마켓을 모티브로 만든 중고거래 플랫폼 API 서버. Contribute to f-lab-edu/daangn-market-used-trading development by creating an account on GitHub.&quot; data-og-host=&quot;github.com&quot; data-og-source-url=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; data-og-url=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; data-og-image=&quot;https://scrap.kakaocdn.net/dn/bFH6ed/hyI1ob1VbR/sLAC1hkFZHxzXX0qSrJej0/img.png?width=278&amp;amp;height=278&amp;amp;face=0_0_278_278&quot;&gt;&lt;a href=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot; data-source-url=&quot;https://github.com/f-lab-edu/daangn-market-used-trading&quot;&gt;
&lt;div class=&quot;og-image&quot; style=&quot;background-image: url('https://scrap.kakaocdn.net/dn/bFH6ed/hyI1ob1VbR/sLAC1hkFZHxzXX0qSrJej0/img.png?width=278&amp;amp;height=278&amp;amp;face=0_0_278_278');&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;div class=&quot;og-text&quot;&gt;
&lt;p class=&quot;og-title&quot;&gt;f-lab-edu/daangn-market-used-trading&lt;/p&gt;
&lt;p class=&quot;og-desc&quot;&gt;중고 거래부터 동네 정보까지 당근마켓을 모티브로 만든 중고거래 플랫폼 API 서버. Contribute to f-lab-edu/daangn-market-used-trading development by creating an account on GitHub.&lt;/p&gt;
&lt;p class=&quot;og-host&quot;&gt;github.com&lt;/p&gt;
&lt;/div&gt;
&lt;/a&gt;&lt;/figure&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style2&quot;&gt;참조&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;a href=&quot;https://tech.gluesys.com/blog/2020/02/17/storage_3_intro.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;tech.gluesys.com/blog/2020/02/17/storage_3_intro.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.youtube.com/watch?v=6wPr2jgdDxM&amp;amp;ab_channel=%EC%9A%B0%EC%95%84%ED%95%9CTech&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;www.youtube.com/watch?v=6wPr2jgdDxM&amp;amp;ab_channel=%EC%9A%B0%EC%95%84%ED%95%9CTech&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.slideshare.net/charsyam2/massive-service-basic?fbclid=IwAR1k06teUf2LSOUAlfLIaqR4pTGoGFblQ5Kte5qG2raVtKCaST1FJmxhUBQ&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;www.slideshare.net/charsyam2/massive-service-basic?fbclid=IwAR1k06teUf2LSOUAlfLIaqR4pTGoGFblQ5Kte5qG2raVtKCaST1FJmxhUBQ&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>프로젝트/당근마켓 API 서버 프로젝트</category>
      <author>Dev-SeeOne</author>
      <guid isPermaLink="true">https://see-one.tistory.com/4</guid>
      <comments>https://see-one.tistory.com/4#entry4comment</comments>
      <pubDate>Wed, 23 Dec 2020 04:27:59 +0900</pubDate>
    </item>
  </channel>
</rss>