<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>source</title>
    <link>https://skysoo1111.tistory.com/</link>
    <description>      Hi, I&amp;rsquo;m @skysoo1111
      백엔드 개발자로 메인 언어는 Java 입니다.
      현재 관심사는 스크립트 기반 언어들이고 특히 스크립트 풀스택 개발에 흥미가 많습니다.
      skysoo1111@gmail.com
      @Tving - Backend Programmer

</description>
    <language>ko</language>
    <pubDate>Wed, 6 May 2026 11:25:01 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>skysoo1111</managingEditor>
    <image>
      <title>source</title>
      <url>https://tistory1.daumcdn.net/tistory/2213069/attach/8c69d2fb0c464ffe85b3a4919a9facb4</url>
      <link>https://skysoo1111.tistory.com</link>
    </image>
    <item>
      <title># 마이크로 서비스 통신 설계</title>
      <link>https://skysoo1111.tistory.com/134</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;마이크로서비스에서 각 서비스들을 호출하기 위한 여러가지 통신 방법이과 고려해야 될 사항들이 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;RPC 선택시 고려 사항&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API에 대한 RPC 메커니즘을 선택하기 전에 서비스와 클라이언트 간의 상호 작용 스타일을 고려하는 것이 좋다.&lt;/p&gt;
&lt;ol 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;br /&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/028e80b7-c0ab-425f-8bbd-9ba3775493bb/image.png&quot; alt=&quot;&quot; /&gt;&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;One-way notifications: 클라이언트가 서비스에 요청을 보내지만 응답을 기대하지 않는 방식&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;메시지 형식&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 텍스트 형식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;JSON or XML은 가장 많이 사용되는 텍스트 기반 형식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사람이 읽을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메시지가 장황하다.&lt;/li&gt;
&lt;li&gt;텍스트 구문 분석과 관련된 오버헤드가 존재한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 바이너리 형식&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Thrift, Protocol Buffers(Protobuf) 및 Avro는 가장 많이 사용되는 바이너리 형식이다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;메타 데이터가 최소한이므로 페이로드가 작다.&lt;/li&gt;
&lt;li&gt;텍스트 기반 구문 분석보다 비교적 빠르다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 단점&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;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;동기 호출 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;호출 패턴으로는 REST / GraphQL / gRPC 와 같은 동기식 통신 메커니즘 또는 AMQP / STOMRMP 등 비동기식 메시지 기반 통신 메커니즘이 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. REST&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP URI를 통해 자원을 명시하고 HTTP 메서드를 통해 해당 자원에 대한 CRUD 작업을 처리하는 것&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REST API 정의&lt;br /&gt;가장 인기있는 &lt;b&gt;REST 인터페이스 정의 언어는 OpenAPI&lt;/b&gt; 입니다.&lt;/li&gt;
&lt;li&gt;단일 요청으로 여러 리소스를 가져와야 하는 과제&lt;br /&gt;이러한 확장성 부족으로 인해 발생할 수 있는 언더패치나 오버패치를 해결하기 위해 &lt;b&gt;페이스북의 GraphQL이나 넷플릭스의 Falcor 같은 대체 API 기술&lt;/b&gt;이 점점 인기를 얻고 있다.&lt;/li&gt;
&lt;li&gt;HTTP 동사에 대한 매핑 작업의 과제&lt;br /&gt;REST API 디자인 문제는 수행하려는 작업을 HTTP 메서드에 매핑하면서 발생한다. PUT을 사용해서 주문을 취소, 수정 등의 업데이트 작업을 해야한다면 다음과 같이 해결할 수 있을 것이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;주문 취소: /orders/{orderId}/cancel&lt;/li&gt;
&lt;li&gt;주문 수정: /orders/{orderId}/revise&lt;br /&gt;이렇게 하위 리소스를 정의하여 처리할 수 있겠지만 이는 RESTful하지 않다. 이런 문제의 결과로 &lt;b&gt;gRPC와 같은 대안이 인기&lt;/b&gt;를 얻고 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. gRPC&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;gRPC는 구글이 개발한 고성능의 오픈소스 RPC 프레임워크로 MSA 구조안에서 여러 언어로 작성된 서비스들 간의 통신을 지원한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;HTTP/2를 사용하며 메시지 형식은 바이너리 타입의 protobuf를 사용하기 때문에 상대적으로 적은 페이로드를 사용하고 효율적이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;.proto 파일 내부에 서비스를 정의한다.&lt;/li&gt;
&lt;li&gt;protocol buffer compiler를 이용하여 서버와 클라이언트 코드를 작성한다.&lt;/li&gt;
&lt;li&gt;서버 인터페이스를 구현하고 gRPC 서버를 생성한다.&lt;/li&gt;
&lt;li&gt;클라이언트 어플리케이션을 만들고 RPC 호출을 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;풍부한 업데이트 작업이 있는 API를 비교적 간단히 디자인할 수 있다.&lt;/li&gt;
&lt;li&gt;메시지 형식이 바이너리라 작고 효율적이다.&lt;/li&gt;
&lt;li&gt;양방향 스트리밍을 통해 RPC와 메시징을 모두 사용할 수 있다.&lt;/li&gt;
&lt;li&gt;다양한 언어로 작성된 클라이언트들과 통신이 가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;REST/JSON 기반 API보다 더 많은 작업을 수행해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. GraphQL&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GraphQL은 클라이언트에 데이터를 표시하는 방법을 쿼리로 정의함으로써 단일 요청을 사용하여 여러 리소스를 가져오는 문제를 해결한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;GraphQL 서버는 클라이언트에 스키마(요청할 수 있는 데이터 모델)를 제공한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 장점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트는 서버에서 필요한 것을 정확하게 지정하여 언더패칭, 오버패칭을 줄이고 유연한 확장성을 가질 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt; 단점&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 제공 캐싱 지원이 없음&lt;/li&gt;
&lt;li&gt;REST보다 복잡하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;비동기 호출 패턴&lt;/h2&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. One-way notifications&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스는 채널을 구독하고 메시지를 처리한다. 응답이 다시 전송되지 않는다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/475e552d-debb-4308-b841-432dfaa0ae22/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. Publish / Subscribe&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;클라이언트는 여러 구독자가 읽을 수 있는 채널에 메시지를 게시하고 필요에 따라 여러 서비스가 구독하여 메시지를 처리한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/28940a56-d704-455b-8c8a-b7f347a4d801/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. Publish / Async Response&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Pub/Sub 방식과 Request/Response 방식을 결합하여 더 높은 수준의 상호 작용을 형성한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/d6836c00-e563-473c-8668-68a7203cf083/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learncsdesign.medium.com/microservices-communication-design-patterns-906ea111c0d0&quot;&gt;참고사이트&lt;/a&gt;&lt;/p&gt;</description>
      <category>Microservice/Design</category>
      <category>API</category>
      <category>graphql</category>
      <category>grpc</category>
      <category>Micro Service</category>
      <category>msa</category>
      <category>REST</category>
      <category>통신설계</category>
      <author>skysoo1111</author>
      <guid isPermaLink="true">https://skysoo1111.tistory.com/134</guid>
      <comments>https://skysoo1111.tistory.com/134#entry134comment</comments>
      <pubDate>Thu, 19 May 2022 17:41:32 +0900</pubDate>
    </item>
    <item>
      <title># 마이크로 서비스 데이터 설계</title>
      <link>https://skysoo1111.tistory.com/133</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;서비스당 데이터베이스&lt;/h2&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/b02d23bf-6c3c-4ca3-88b5-eef02c182998/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서비스 간 느슨한 결합으로 의존성을 낮추고 서비스 특성에 맞는 데이터베이스를 자유롭게 선택 가능하다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;문제점 및 해결방법&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 데이터베이스를 통해 조인해야 하는 쿼리&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이벤트 소싱&lt;/li&gt;
&lt;li&gt;API 구성&lt;/li&gt;
&lt;li&gt;CQRS(명령/조회 책임 분리)&lt;/li&gt;
&lt;/ul&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; start=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여러 데이터베이스에 걸친 트랜잭션&lt;/li&gt;
&lt;/ol&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사가현 패턴&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1-1. 이벤트 소싱 (Event Sourcing)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 소싱이란, 이벤트를 위주로 비즈니스 로직을 구현하고, 애그리거트를 event store에 저장하는 것을 의미한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;✅ &lt;i&gt;애그리거트: DDD에 기반한 설계에서 하나의 도메인에서 필요한 객체들을 하나의 군집으로 묶은 것&lt;/i&gt;&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/d409d2b1-52f7-4f32-a738-7b5456185392/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;데이터를 가진 서비스가 직접 데이터를 조작하게 하지 않고, 서비스는 이벤트를 발생시키고 해당 이벤트는 event store에 저장만 한다.&lt;br /&gt;그리고 이벤트 컨슈머가 해당 이벤트를 처리하도록 한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/a84e756c-3f0d-4f41-a3eb-2aa038d1ec11/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 소싱은 클라우드 환경에서의 메시지 중심의 분산 시스템에 적합하다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이벤트 소싱은 쓰기에 적합하지만 읽기에 매우 비효율적인 구조를 가진다. 따라서 CQRS(Command Query Responsibility Segregation)를 적용하면 읽기 성능을 높일 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이벤트 소싱 + CQRS&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Command가 발생하면 Command API에 요청 전달&lt;/li&gt;
&lt;li&gt;Command를 처리하는 서비스는 event store에 API 처리 결과를 저장&lt;/li&gt;
&lt;li&gt;이벤트가 처리된 후 이벤트 브로커로 결과 전송&lt;/li&gt;
&lt;li&gt;이벤트는 queries API로 전달되면 view store에 저장된다.&lt;/li&gt;
&lt;li&gt;이후 client로부터 query가 요청되면 view store에 동기화된 데이터를 조회하게 된다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이벤트 소싱 단점&lt;/h3&gt;
&lt;ol 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;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1-2. API 구성&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;API 컴포지션 패턴은 데이터를 소유하고 있는 각 서비스를 호출한 다음 결과를 조합하여 쿼리 작업을 구현하는 것을 의미한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/9b7e0c07-c093-41aa-bd0d-2f46fca0c7b9/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;높은 학습 곡선 없이 MSA에서 데이터를 쿼리하는 가장 편리한 방법&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;경우에 따라 대규모 데이터의 비효율적인 조인이 발생할 수 있다.&lt;/p&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;1-3. CQRS 패턴&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;CQRS의 주요 목표는 문제를 분리하는 것이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성, 수정 및 삭제 작업은 명령 모듈에 의해 구현된다.&lt;/li&gt;
&lt;li&gt;조회 작업은 쿼리 모듈에 의해 구현된다.&lt;/li&gt;
&lt;li&gt;명령 모듈에서 생성된 이벤트는 쿼리 모듈이 전달받고 항상 동기화된 상태를 유지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/4954454f-a1e7-4711-bdd7-edba7e01f89b/image.png&quot; alt=&quot;&quot; /&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/51e694f6-2e2d-4ce7-8569-67d38e909102/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;이점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MSA에서 쿼리를 효율적으로 구현할 수 있음&lt;/li&gt;
&lt;li&gt;이벤트 소싱 기반 응용 프로그램에서 쿼리를 가능하게 함&lt;/li&gt;
&lt;li&gt;서비스의 명령 및 쿼리 측면을 별도의 코드와 스키마로 분리함&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;더 복잡한 아키텍처&lt;br /&gt;: 개발자가 쿼리쪽 서비스를 따로 또 작성해야 하고 Application에서 다양한 유형의 DB를 사용할 수 있으므로 개발자와 DevOps 모두 복잡성이 증가&lt;/li&gt;
&lt;li&gt;복제 지연 발생&lt;br /&gt;이벤트가 명력측에서 게시되는 시점과 쿼리측에서 처리되는 시점 사이에 업데이트 지연이 발생&lt;/li&gt;
&lt;/ol&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;2-1. 사가 패턴(Saga Pattern)&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;사가 패턴을 사용하면 분산 트랜잭션을 사용하지 않고도 MSA에서 데이터 일관성을 유지할 수 있다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;트랜잭션의 관리 주체가 DBMS가 아닌 서비스이다. 따라서 사가 패턴은 보상 트랜잭션 처리도 해줘야 한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;보상 트랜잭션: 사가의 n번째 트랜잭션이 실패했을 때, 이전 n-1 번째 트랜잭션부터 수행된 모든 로컬 트랜잭션을 역순으로 롤백하는 것&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Choreography 방식&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/8be988ce-33aa-4d30-9cdc-5b09157a3aff/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;각 서비스 간 이벤트를 주고 받는 Event Pub/Sub 방식이다. 메시지 큐를 통해 비동기 방식으로 전달하는 것도 가능하다.&lt;/p&gt;
&lt;ol 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;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순성&lt;br /&gt;: 비즈니스 개체가 생성, 수정, 삭제되면 서비스는 이벤트를 게시만 하면 된다.&lt;/li&gt;
&lt;li&gt;느슨한 결합&lt;br /&gt;: 이벤트를 통해 처리되므로 각 서비스가 누군지 알 필요가 없다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 서비스는 자신에게 영향을 미치는 모든 이벤트를 항상 구독하고 있어야 한다.&lt;/li&gt;
&lt;li&gt;개별 트랜잭션들이 공통된 공유ID를 정의해야하고, 이벤트 추적, 디버깅 어려움, 새로운 스텝 추가시 복잡도 증가&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;Orchestration 방식&lt;/h3&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/123d6cd5-81f7-4c90-8986-777fca163de9/image.png&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;br /&gt;Orchestrator를 중심으로 한 Invoke/Reply 방식이다.&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션에 참가하는 각 서비스들의 로컬 트랜잭션은 Orchestrator에 의해 호출되고 상태값이 설정된다.&lt;/li&gt;
&lt;li&gt;참가하는 모든 트랜잭션이 처기가 된다면 메인 서비스의 상태가 변경된다.&lt;/li&gt;
&lt;li&gt;참가하는 서비스 중 하나라도 트랜잭션 실패하면 Orchestrator는 보상 트랜잭션을 수행한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;장점&lt;/h3&gt;
&lt;ol 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;br /&gt;: 서비스는 Orchestrator가 호출하는 API를 구현하므로 이벤트에 대해 알 필요가 없다.&lt;/li&gt;
&lt;li&gt;관심사의 분리를 통해 비즈니스 논리를 단순화 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;단점&lt;/h3&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Orchestrator에서 너무 많은 비즈니스 로직을 중앙 집중화할 위험이 존재한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;a href=&quot;https://learncsdesign.medium.com/microservices-data-design-patterns-7efcca86aab9&quot;&gt;참고사이트&lt;/a&gt;&lt;/p&gt;</description>
      <category>Microservice/Design</category>
      <category>CQRS</category>
      <category>microservice</category>
      <category>msa</category>
      <category>데이터설계</category>
      <category>애그리커트</category>
      <category>이벤트소싱</category>
      <author>skysoo1111</author>
      <guid isPermaLink="true">https://skysoo1111.tistory.com/133</guid>
      <comments>https://skysoo1111.tistory.com/133#entry133comment</comments>
      <pubDate>Thu, 19 May 2022 17:39:52 +0900</pubDate>
    </item>
    <item>
      <title># 마이크로 서비스 설계 원칙</title>
      <link>https://skysoo1111.tistory.com/132</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;1. 비즈니스 도메인을 중심으로 모델링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;도메인 기반 설계를 통해 더욱 안정된 서비스를 구축하고 다양한 사용자 인터페이스에 대해 보다 쉽게 각 서비스들을 재결합 할 수 있다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;2. 자동화 문화&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;많은 수의 마이크로서비스의 복잡성을 관리하려면 자동화가 필수적이다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;CI/CD&lt;/li&gt;
&lt;li&gt;빌드 파이프라인 단계에서의 SIT(System Integration Testing) &amp;amp; UAT(User Acceptance Testing) 테스트&lt;/li&gt;
&lt;li&gt;프로비저닝, OS 구성, 서비스 이미지 생성 등&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;3. 구현 세부 정보 숨기기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스가 독립적으로 변경되고 진화할 수 있도록 각 서비스의 구현 세부 정보를 숨겨야 한다. 필요한 정보는 DB에 직접 접근하는 것이 아닌 각 서비스 API를 통해서 얻어야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;4. 모든 것을 분권화하기&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;각 서비스는 자체 결정을 내릴 수 있어야 하며 그 안에서 완전한 자율성을 가질 수 있어야 한다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Service Ochestration&lt;br /&gt;: 중앙 집중식 접근 방식&lt;br /&gt;
&lt;div&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/c71aac00-6718-4459-9ae2-b6f6711594ee/image.png&quot; alt=&quot;&quot; /&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;Service Choreography&lt;br /&gt;: 느슨하게 결합된 접근 방식&lt;br /&gt;
&lt;div&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/ec15abab-8a94-4ea3-bc0c-d4fe899d1588/image.png&quot; alt=&quot;&quot; /&gt;&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;5. 독립적으로 배포&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;서비스 구성 요소를 변경하는 경우 다른 구성 요소를 변경하지 않고 프로덕션으로 릴리스할 수 있어야 한다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;6. 소비자 우선&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;당신의 서비스가 호출되기 위해 존재한다는 생각을 가지고 있어야 한다. 따라서 소비자 관점에서 디자인을 하고 API Docs 도구를 사용하여 API를 문서화하는 것은 필수적이다.&lt;/p&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;7. 오류 격리&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;기본적으로 마이크로서비스는 신뢰할 수 없다는 것을 전제로 설계해야 한다. 계단식 오류를 방지하기 위해 각 오류를 격리함으로써 시스템 복원력을 높이는 것이 중요하다.&lt;/p&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Timeout&lt;br /&gt;: 모든 프로세스간 호출에 대한 시간 제한과 기본 초과 시간을 설정한다.&lt;/li&gt;
&lt;li&gt;Bulkhead&lt;br /&gt;: 원격 호출을 서로 격리하고 원격 서비스 호출을 자체 스레드 풀로 분리한다.&lt;/li&gt;
&lt;li&gt;Circuit breaker&lt;br /&gt;: 느리게 실행되고 성능이 저하된 시스템 호출을 종료해 자원 고갈을 방지한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일부 서비스 호출의 실패가 전체 서비스 자원을 고갈시켜서는 안된다.&lt;br /&gt;✅ &lt;b&gt;Hystrix: 대표적인 오류 격리 서비스&lt;/b&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지정한 오류율을 초과한 경우 발생&lt;/li&gt;
&lt;li&gt;회로의 상태를 CLOSE -&amp;gt; OPEN 변경&lt;/li&gt;
&lt;li&gt;회로가 열린 상태에서 모든 요청에 대해 fallback method(대체함수) 실행&lt;/li&gt;
&lt;li&gt;지정 시간이 지난 후 HALF OPEN(하나의 요청만 원래 method로 실행) 수행&lt;/li&gt;
&lt;li&gt;HALP OPEN이 성공하면 회로 상태 CLOSE, 실패하면 다시 OPEN&lt;/li&gt;
&lt;/ol&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;8. 높은 수준의 모니터링&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;시스템에서 무슨일이 일어나고 있는지 알기 위해서 충분한 입력과 로그가 생성되어야 한다. 또한 보다 높은 수준의 모니터링을 위해서 상태 체크, 중앙 집중식 로그 및 통계 집계, 상관 관계 ID를 사용한 분산 추적 등이 가능해야 한다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;[참고사이트](&lt;span style=&quot;color: #eeffff;&quot;&gt;&lt;a href=&quot;https://learncsdesign.medium.com/microservices-design-principles-b62499b583bc&quot;&gt;https://learncsdesign.medium.com/microservices-design-principles-b62499b583bc&lt;/a&gt;&lt;/span&gt;)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>Microservice/Design</category>
      <category>Circuit Breaker</category>
      <category>hystrix</category>
      <category>msa</category>
      <category>설계원칙</category>
      <author>skysoo1111</author>
      <guid isPermaLink="true">https://skysoo1111.tistory.com/132</guid>
      <comments>https://skysoo1111.tistory.com/132#entry132comment</comments>
      <pubDate>Thu, 19 May 2022 17:33:19 +0900</pubDate>
    </item>
    <item>
      <title>EFFECTIVE JAVA3 - 3장. 모든 객체의 공통 메서드</title>
      <link>https://skysoo1111.tistory.com/131</link>
      <description>&lt;h2 data-ke-size=&quot;size26&quot;&gt;아이템 10. equals는 일반 규약을 지켜 재정의하라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;일반적으로 equals를 재정의하지 않는 것이 가장 좋다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  equals를 재정의 하지 않아도 될 때&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 인스턴스가 본질적으로 고유할 때 - Thread&lt;/li&gt;
&lt;li&gt;싱글톤 인스턴스&lt;/li&gt;
&lt;li&gt;Enum&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  equals를 재정의 할 때 지켜야 할 규약 (null이 아닌 참조 값)&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;b&gt;반사성&lt;/b&gt; : x.equals(x)는 true이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;대칭성&lt;/b&gt; : x.equals(y)는 true이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;추이성&lt;/b&gt; : x.equals(y)가 true이고, y.equals(z)가 true이면, x.equals(z)는 true이다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;일관성&lt;/b&gt; : x.equals(y)를 반복해서 호출하면 항상 같은 값을 반환한다.&lt;/li&gt;
&lt;li&gt;&lt;b&gt;null-아님&lt;/b&gt; : x.equals(null)은 false이다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;구체 클래스를 확장해 새로운 값을 추가하면서 equasl 규약을 만족시킬 방법은 존재하지 않는다.&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  instanceof는 null 검사를 명시적으로 하지 않아도 된다.&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;❌ Bad - 명시적 null 검사 - 필요 없다.
@Override
public boolean equals(Object o) {
    if (o == null) 
        return false;
    ...
}

✅ Good - 무시적 null 검사
@Override
public boolean equals(Object o) {
    if (!(o instanceof MyType)) 
        return false;
    MyType mt = (MyType) o;
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  equals 재정의 방법&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;== 연산자를 사용해 입력이 자기 자신의 참조인지 확인&lt;/li&gt;
&lt;li&gt;instanceof 연산자로 입력이 올바른 타입인지 확인&lt;/li&gt;
&lt;li&gt;입력 객체와 자기 자신의 대응되는 핵심 필드들이 모두 일치하는지 확인&lt;/li&gt;
&lt;/ol&gt;
&lt;pre class=&quot;kotlin&quot;&gt;&lt;code&gt;@Override
public boolean equals(Object o) {
    if (o == this)
        return true;
    if (!(o instanceof Point)) 
        return false;
    Point p = (Point) o;
    return p.x == x &amp;amp;&amp;amp; p.y == y;
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  타입별 비교 방법&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;기본 타입 필드(float, double 제외)는 == 비교&lt;/li&gt;
&lt;li&gt;참조 타입 필드는 equals 비교&lt;/li&gt;
&lt;li&gt;float, double은 정적 메서드인 Float.compare, Double.compare로 비교한다. (Float.equals나 Double.equals는 성능상 좋지 않으므로 지양하자)&lt;/li&gt;
&lt;li&gt;null도 정상 값으로 취급하는 참조 타입 필드의 경우 Objects.equals(Object, Object)로 비교&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;equals를 작성하고 테스트하는 일은 지루하다. AutoValue 프레임워크를 사용하자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;꼭 필요한 경우가 아니라면 equals를 재정의하지 말자. 많은 경우 Object의 equals가 원하는 비교를 수행해준다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아이템 11. equals를 재정의하려거든 hashCode도 재정의하라&lt;/h2&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;equals를 재정의할 때는 hashCode도 반드시 재정의 해야 한다.&lt;br /&gt;그렇지 않으면 일반 규약을 어겨 HashMap 같은 컬렉션의 원소로 사용할 때 문제를 일으킬 수 있다.&lt;br /&gt;서로 다른 인스턴스라면 되도록 해시코드는 다르게 구현해야 한다. 역시 AutoValue 프레임워크를 사용하면 자동으로 만들어준다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아이템 12. toString을 항상 재정의하라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  toString 작성법&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;toString은 그 객체가 가진 주요 정보 모두를 반환하는게 좋다.&lt;/li&gt;
&lt;li&gt;toString의 반환값의 포맷을 문서화할지 정하자.(포맷을 명시하든 안하든 의도는 명확히 밝혀야 한다.)&lt;/li&gt;
&lt;li&gt;toString이 반환한 값에 포함된 정보를 얻을 수 있는 접근자를 제공해야 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;모든 구체 클래스에서 Object의 toString을 재정의하자.&lt;br /&gt;toString은 해당 객체에 관한 명확하고 유용한 정보를 읽기 좋은 형태로 반환해야 한다.&lt;br /&gt;toString을 잘 구현한 클래스는 디버깅하기도 쉽다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아이템 13. clone 재정의는 주의해서 진행하라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;clone은 원본 객체에 아무런 해를 끼치지 않는 동시에 복제된 객체의 불변식을 보장해야 한다. =&amp;gt; 배열의 clone을 재귀적으로 호출하는 방법&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;13-2. 가변 상태를 참조하는 클래스용 clone 메서드&lt;/p&gt;
&lt;pre class=&quot;aspectj&quot;&gt;&lt;code&gt;@Override
public Stack clone() {
    try {
        Stack result = (Stack) super.clone();
        result.elements = elements.clone();
        return result;
    } catch (CloneNotSupportedException e) {
        throw new AssertionError();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;복잡한 clone 구현을 해야 할까? Cloneable을 이미 구현한 클래스를 확장하는 것이 아니라면 복사 생성자와 복사 팩토리 방식으로 더 나은 객체 복사 방식을 제공할 수 있다. (단, 배열만은 clone 메서드 방식이 가장 깔끔하다.)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;nimrod&quot;&gt;&lt;code&gt;//복사 생성자
public Yum(Yum yum) {...};

//복사 팩토리
public static Yum newInstance(Yum yum) {...};&lt;/code&gt;&lt;/pre&gt;
&lt;h2 data-ke-size=&quot;size26&quot;&gt;아이템 14. Comparable을 구현할지 고려하라&lt;/h2&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;물리적 동치성 : 스택의 값 비교(참조형: 주소값, 기본형: 값)&lt;br /&gt;논리적 동치성 : 실제 가지는 값 비교(참조형: Heap의 값, 기본형: 값)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Comparable을 구현했다는 것은 그 클래스의 인스턴스들에는 자연적인 순서가 있음을 뜻한다.&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;자바 플랫폼의 모든 값 클래스와 열거 타입이 Comparable을 구현했다. 알파벳, 숫자, 연대 같이 숫서가 명확한 값 클래스를 작성한다면 반드시 Comparable을 구현하자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  정렬된 컬렉션들은 동치성을 비교할 때 equals 대신 compareTo를 사용한다. (BigDecimal처럼 compareTo와 equals가 일관되지 않는 경우도 있기에 주의하자.)&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  compareTo 작성 요령&lt;/p&gt;
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;compareTo 입력 인수의 타입을 확인하거나 형변환할 필요 없다. (어차피 컴파일 타임에 걸린다.)&lt;/li&gt;
&lt;li&gt;compareTo는 각 필드가 동치인지를 비교하는게 아니라 그 순서를 비교한다.&lt;/li&gt;
&lt;li&gt;정수,실수의 비교시 박싱된 기본 타입의 정적 메서드 compare를 이용하자(&amp;lt;,&amp;gt;는 java7 이전 정수 비교시)&lt;/li&gt;
&lt;li&gt;java8 부터는 Comparator 인터페이스와 원하는 비교자 생성 메서드를 사용하여 compareTo 메서드를 간결하게 구현할 수 있다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;  Comparator의 수많은 보조 생성 메서드들&lt;/p&gt;
&lt;ol 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;/ol&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;필드의 값을 비교할 때 &amp;lt;와 &amp;gt; 연산자는 쓰지말아야 한다. 박싱된 기본 타입 클래스의 정적 compare 메서드나 Comparator 인터페이스가 제공하는 비교자 생성 메서드를 사용하자.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>기술서적</category>
      <category>EFFECTIVE JAVA3</category>
      <category>아이템</category>
      <category>아이템10</category>
      <category>아이템11</category>
      <category>아이템12</category>
      <category>아이템13</category>
      <category>아이템14</category>
      <author>skysoo1111</author>
      <guid isPermaLink="true">https://skysoo1111.tistory.com/131</guid>
      <comments>https://skysoo1111.tistory.com/131#entry131comment</comments>
      <pubDate>Fri, 15 Apr 2022 22:28:41 +0900</pubDate>
    </item>
    <item>
      <title>EFFECTIVE JAVA3 - 2장. 객체 생성과 파괴</title>
      <link>https://skysoo1111.tistory.com/130</link>
      <description>&lt;p&gt;EFFECTIVE JAVA3를 읽고 내용을 정리해보려고 한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://velog.velcdn.com/images/skysoo/post/beddb5b5-ee0f-44db-9530-113bf33c6549/image.png&quot; alt=&quot;&quot;&gt;&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;이번 장은 &lt;strong&gt;&lt;em&gt;객체의 생성과 파괴&lt;/em&gt;&lt;/strong&gt;를 다룬다. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;객체를 만들어야 할 때와 만들지 말아야 할 때를 구분하는 법&lt;/li&gt;
&lt;li&gt;제때 파괴됨을 보장하고 파괴 전에 수행해야 할 정리 작업을 관리하는 요령&lt;/li&gt;
&lt;/ol&gt;
&lt;h2&gt;아이템 1. 생성자 대신 정적 팩토리 메서드를 고려하라&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;여기서 얘기하는 정적 팩터리 메서드는 &lt;em&gt;&lt;strong&gt;디자인 패턴의 팩토리 메서드와 다르다.&lt;/strong&gt;&lt;/em&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h4&gt;장점  &lt;/h4&gt;
&lt;h4&gt;1. 이름을 가질 수 있다.&lt;/h4&gt;
&lt;pre&gt;&lt;code&gt;❌ Bad
BigInteger(int, int, Random)

✅ Good
BigInteger.probablePrime(int, int, Random)

&amp;quot;값이 소수인 BigInteger를 반환한다&amp;quot; 라는 보다 명확한 의미 전달이 가능하다.&lt;/code&gt;&lt;/pre&gt;&lt;h4&gt;2. 호출될 때마다 인스턴스를 새로 생성하지 않아도 된다.&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;이 덕분에 불변 클래스는 인스턴스를 캐싱하여 재활용하는 식으로 불필요한 객체 생성을 피할 수 있다. (객체 생성의 비용이 큰 경우 성능을 상당히  올릴 수 있다.)&lt;br&gt;  불변 클래스: 멤버 변수를 공개하지 않는다. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;✅ Good
public final class Boolean implements Serializable, Comparable&amp;lt;Boolean&amp;gt; {

  public static final Boolean TRUE = new Boolean(true);
  public static final Boolean FALSE = new Boolean(false);

  public static Boolean valueOf(boolean b) {
      return b ? TRUE : FALSE;
  }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;3. 반환 타입의 하위 타입 객체를 반활 할 수 있는 능력이 있다.&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// java 8 이전에는 인터페이스에 정적 메서드를 선언할 수 없었다.
// 인터페이스와 동반 클래스의 예
Collection&amp;lt;String&amp;gt; empty = Collections.emptyList();

public class Collections {
    public static final List EMPTY_LIST = new EmptyList&amp;lt;&amp;gt;();

    public static final &amp;lt;T&amp;gt; List&amp;lt;T&amp;gt; emptyList() {
            return (List&amp;lt;T&amp;gt;) EMPTY_LIST;
    }
}

// java 8 이후에는 인터페이스에 정적 메서드를 선언할 수 있다.
Stream&amp;lt;String&amp;gt; chosunKings = Stream.of(&amp;quot;태조&amp;quot;, &amp;quot;정종&amp;quot;};

public interface Stream&amp;lt;T&amp;gt; extends BaseStream&amp;lt;T, Stream&amp;lt;T&amp;gt;&amp;gt; {
    public static&amp;lt;T&amp;gt; Stream&amp;lt;T&amp;gt; of(T t) {
            return StreamSupport.stream(new Streams.StreamBuilderImpl&amp;lt;&amp;gt;(t), false);
    }
}


// java 9 이후에는 private 정적 메서드까지 허용한다.&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;4. 입력 매개변수에 따라 매번 다른 클래스의 객체를 반환할 수 있다.&lt;/h4&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;✅ Good
public static &amp;lt;E extends Enum&amp;lt;E&amp;gt;&amp;gt; EnumSet&amp;lt;E&amp;gt; noneOf(Class&amp;lt;E&amp;gt; elementType) {
        Enum&amp;lt;?&amp;gt;[] universe = getUniverse(elementType);
        if (universe == null)
            throw new ClassCastException(elementType + &amp;quot; not an enum&amp;quot;);

        if (universe.length &amp;lt;= 64)
            return new RegularEnumSet&amp;lt;&amp;gt;(elementType, universe);
        else
            return new JumboEnumSet&amp;lt;&amp;gt;(elementType, universe);
}&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;5. 정적 팩토리 메서드를 작성하는 시점에는 반환할 객체의 클래스가 존재하지 않아도 된다.&lt;/h4&gt;
&lt;blockquote&gt;
&lt;p&gt;이런 유연함은 서비스 제공자 프레임워크(3개의 핵심 컴포넌트)를 만드는 근간이 된다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;서비스 인터페이스 : 구현체의 동작을 정의&lt;/li&gt;
&lt;li&gt;제공자 등록 API : 제공자가 구현체를 등록할 때 사용&lt;/li&gt;
&lt;li&gt;서비스 접근 API : 클라이언트가 서비스의 인스턴스를 얻을 때 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;
✅ JDBC 서비스 제공자 프레임워크
1. 서비스 인터페이스 - Connection

2. 제공자 등록 API - DriverManager.registerDriver

3. 서비스 접근 API - Drivermanager.getConnection
private static Connection getConnection(
        String url, java.util.Properties info, Class&amp;lt;?&amp;gt; caller) 

4. 서비스 제공자 인터페이스 - Driver&lt;/code&gt;&lt;/pre&gt;
&lt;h4&gt;단점  &lt;/h4&gt;
&lt;h4&gt;1. 상속을 하려면 public이나 protected 생성자가 필요아므로 정적 팩토리 메서드만 제공하면 하위 클래스를 만들 수 없다.&lt;/h4&gt;
&lt;h4&gt;2. 정적 팩토리 메서드는 프로그래머가 찾기 어렵다.&lt;/h4&gt;
&lt;p&gt;API 문서화를 잘하고 메서드 이름도 알려진 규약을 따라 짓도록 하자&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;- from
Date d = Date.from(instance);
- of
Set&amp;lt;Rank&amp;gt; faceCards = EnumSet.of(JACK, QUEEN, KING);
- valueOf
BigInteger prime = BigInteger.valueOf(Integer,MAX_VALUE);
- instance or getInstance
StackWalker luke = StackWalker.getInstance(options);
- create or new Instance
Object newArray = Array.newInstance(classObject, arrayLean);
- getType
FileStore fs = Files.getFileStore(path);
- newType
BufferedReader br = Files.newBufferdReader(path);
- type
List&amp;lt;Complaint&amp;gt; litany = Collections.List(legacyLitany);&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;각자의 장단점이 있으나 정적 팩토리를 사용하는게 유리한 경우가 더 많으므로 무작정 public 생성자를 제공하는 습관이 있다면 고치자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이템 2. 생성자에 매개변수가 많다면 빌더를 고려하라&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;정적 팩토리와 생성자에는 선택적 매개 변수가 많을 때 적절히 대응하기 어렵다는 제약이 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;2-1. 점층적 생성자 패턴 - 확장하기 어렵다!&lt;br&gt;이는 각 매개변수가 무엇인지, 몇 개인지, 순서까지 고려해야 한다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;NutritionFacts cocaCola = new NutritionFacts(240, 8,100,0,35,27);&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;2-2. 자바빈즈 패턴 - 일관성이 깨지고, 불변으로 만들 수 없다.&lt;br&gt;위 패턴보다 인스턴스를 만들기 쉽고, 가독성이 좋아졌지만 객체 하나를 만들기 위해 setter 메서드 여러개를 호출해야 하고, 일관성을 유지하기 어렵다. &lt;/p&gt;
&lt;p&gt;2-3. 빌더 패턴 - 점층적 생성자 패턴과 자바빈즈 패턴의 장점만 취했다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;NutritionFacts cocaCola = new NutritionFacts.Builder(240,8)
    .calories(100).sodium(35).carbohydrate(27).build();

1. 메서드 체이닝이 가능해 가독성이 좋다. 
2. 필수 매개변수와 선택적 매개변수의 명시적 구분이 가능하고 사용이 편리하다.&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;가변 객체에도 불변식은 존재할 수 있으며, 불변은 불변식의 극단적인 예라고 볼 수 있다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;2-4. 계층적으로 설계된 클래스와 잘 어울리는 빌더 패턴&lt;br&gt;재귀적 타입 한정을 이용하는 제네릭 타입을 사용하고 추상 메서드인 self를 더해 하위 클래스에서 형변화 하지 않고도 메서드 체이닝을 지원할 수 있다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public abstract class Pizza{
    public enum Topping { HAM, MUSHROOM, ONION, PEPPER, SAUSAGE }
    final Set&amp;lt;Topping&amp;gt; toppings;

    // Pizza.Builder 클래스는 재귀적 타입 한정을 이용하는 제네릭 타입이다. 
    // 여기에 추상 메서드인 self를 추가하여 하위 클래스에서는 형변환하지 않고도 메서드 연쇄를 지원할 수 있다.
    abstract static class Builder&amp;lt;T extends Builder&amp;lt;T&amp;gt;&amp;gt;{
        EnumSet&amp;lt;Topping&amp;gt; toppings = EnumSet.noneOf(Topping.class);
        public T addTopping(Topping topping){
            toppings.add(Objects.requireNonNull(topping));
            return self();
        }

        // 각 하위 클래스의 빌더가 정의한 build 메서드는 해당하는 구체 하위 클래스를 반환하도록 선언한다.
        abstract Pizza build();

        protected abstract T self();
    }

    Pizza(Builder&amp;lt;?&amp;gt; builder){
        toppings = builder.toppings.clone();
    }
}

public class NyPizza extends Pizza{
    public enum Size {SMALL, MEDIUM, LARGE }
    private final Size size;

    // super의 builder도 상속받아
    public static class Builder extends Pizza.Builder&amp;lt;Builder&amp;gt;{
        private final Size size;

        public Builder(Size size){
            this.size = Objects.requireNonNull(size);
        }

        @Override
        public NyPizza build() {
            return new NyPizza(this);
        }

        @Override
        protected Builder self() {
            return this;
        }
    }

    private NyPizza(Builder builder) {
        super(builder);
        size = builder.size;
    }
}

NyPizza nyPizza = new NyPizza.Builder(NyPizza.Size.SMALL)
                             .addTopping(Pizza.Topping.SAUSAGE)
                             .addTopping(Pizza.Topping.ONION).build();&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;아이템 3. private 생성자나 열거 타입으로 싱글턴임을 보증하라&lt;/h2&gt;
&lt;blockquote&gt;
&lt;p&gt;싱글턴이란 인스턴스를 오직 하나만 생성할 수 있는 클래스를 말한다.&lt;br&gt;클래스를 싱글턴으로 만들면 이를 사용하는 클라이언트 테스트가 어려워진다. (mock 구현으로 대체할 수 없기 때문)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;3-1. public static final 필드 방식의 싱글턴&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// private 생성자는 public static final 필드를 초기화 할 때 한번만 호출된다.
public class Elvis { 
    public static final Elvis INSTANCE = new Elvis();     
    private Elvis() {...}

    public void leaveTheBuilding() {...}
}

  장점
해당 클래스가 싱글턴임이 API에 명백히 드러난다.&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;3-2. 정적 팩토리 방식의 싱글턴&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 항상 같은 객체의 참조를 반환 하므로 오직 하나의 인스턴스만 가진다. 
public class Elvis { 
  private static final Elvis INSTANCE = new Elvis(); 
  private Elvis() {...} 
  public static Elvis getInstance() { 
      return INSTANCE; 
  } 

  public void leaveTheBuilding() {...}
}


  장점
1. API를 바꾸지 않고도 싱글턴이 아니게 변경할 수 있다는 점이다.
2. 원한다면 정적 팩토리를 제네릭 싱글턴 팩토리로 만들 수 있다.(아이템 30)
3. 정적 팩토리의 메서드 참조를 공급자로 사용할 수 있다는 점이다.(아이템 43,44)&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;위에서 언급한 두 방식의 장점인 이유가 뭔가?&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;권한이 있는 클라이언트는 리플렉션 API인 AccessibleObject.setAccessible을 사용해 private 생성자를 호출할 수 있다. (3-1, 3-2 모두 해당 됨)&lt;br&gt;이를 막으려면 생성자를 수정하여 두번째 객체가 생성될 때 예외를 던지자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;위 두 방식으로 만든 싱글턴을 직렬화하려면 단순히 Serializable을 구현하는 것으로 부족하다.&lt;br&gt;모든 인스턴스 필드를 transient라고 선언하고 readResolve() 메서드를 제공해야 한다. (아이템 89)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;3-3. 열거 타입 방식의 싱글턴 - 바람직한 방법 &lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;public enum Elvis {
    INSTANCE;

    public void leaveTheBuilding() {...}
}

  장점
1. public 필드 방식과 비슷하지만 더욱 간결하다.
2. 추가 노력 없이 직렬화가 가능하다.
3. 리플렉션 공격에서도 완벽히 막아준다.&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;대부분의 상황에서는 원소가 하나뿐인 열거 타입이 싱글턴을 만드는 가장 좋은 방법이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이템 4. 인스턴스화를 막으려거든 private 생성자를 사용하라&lt;/h2&gt;
&lt;ol&gt;
&lt;li&gt;java.util.Arrays 처럼 기본 타입 값이나 배열 관련 메서드들을 모아놓을 때&lt;/li&gt;
&lt;li&gt;java.util.Collections 처럼 특정 인터페이스를 구현하는 객체를 생성해주는 정적 메서드(혹은 팩토리)를 모아놓을 때&lt;/li&gt;
&lt;li&gt;final 클래스와 관련한 메서드들을 모아 놓을 때&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;정적 멤버만 담은 클래스는 좋지는 않지만 위와 같은 경우 나름의 쓰임새가 있다. &lt;strong&gt;&lt;em&gt;하지만 생성자를 명시하지 않는 경우 컴파일러에 의해 자동으로 public 생성자가 만들어진다.&lt;/em&gt;&lt;/strong&gt;&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 정적 멤버 클래스에서 public 생성자가 만들어지는 것을 막기위한 명시적 생성자
public class UtilityClass {
    private UtilityClass() {
        throw new AssertionError();
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;h2&gt;아이템 5. 자원을 직접 명시하지 말고 의존 객체 주입을 사용하라&lt;/h2&gt;
&lt;p&gt;많은 클래스가 하나 이상의 자원에 의존한다. &lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;사용하는 자원에 따라 동작이 달라지는 클래스에는 정적 유틸리티 클래스나 싱글턴 방식이 적합하지 않다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;5-3. 의존 객체 주입은 유연성과 테스트 용이성을 높여준다.&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// 인스턴스를 생성할 때 필요한 자원을 주입하는 방식이다.
public class SpellChecker {
  private final Lexicon dictionary;
  private SpellChecker(Lexicon dictionary) {
    this.dictionary= Objects.requireNonNull(dictionary);
  }
  public boolean isValid(String word) { ... }
}

  이 패턴의 쓸만한 변형이 팩토리 메서드 패턴이다.
생성자에 자원 팩토리를 넘겨주는 방식으로 여기서 팩토리란, 호출할 때마다 특정 타입의 인스턴스를 반복해서 만들어주는 객체를 말한다.&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;자바8의 Supplier 인터페이스가 팩토리를 표현한 완벽한 예이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;// Supplier&amp;lt;T&amp;gt;를 입력 받는 메서드는 한정적 와일드 카드를 사용해 팩토리의 입력 매개 변수를 제한해야 한다.

Mosaic create(Supplier&amp;lt;? extends Tile&amp;gt; tileFactory) {...}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;클래스가 내부적으로 하나 이상의 자원에 의존하고, 그 자원이 클래스 동작에 영향을 준다면 필요한 자원(또는 자원을 만드는 팩토리)을 생성자(또는 정적 팩토리 or 빌더)에 넘겨주자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;정적 팩토리 메서드와 팩토리 메서드의 차이는?&lt;br&gt;정적 팩토리 메서드 : 객체 생성의 역할을 하는 클래스 메서드&lt;br&gt;팩토리 메서드 : 객체의 생성을 서브 클래스에서 결정&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이템 6. 불필요한 객체 생성을 피하라&lt;/h2&gt;
&lt;p&gt;앞에서 언급한 정적 팩토리 메서드의 사용 역시 불필요한 객체 생성을 막는 방법이 된다. (Boolean.valueOF(String)이 좋은 예)&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;❌ Bad
String s = new String(&amp;quot;bikini&amp;quot;);

✅ Good
String s = &amp;quot;bikini&amp;quot;;

&amp;gt; 불변 객체: 재할당은 가능하지만, 한번 할당하면 내부 데이터를 변경할 수 없는 객체&lt;/code&gt;&lt;/pre&gt;&lt;p&gt;6-2. 값비싼 객체를 재사용해 성능을 개선한다.&lt;br&gt;인스턴스를 클래스 초기화(정적 초기화) 과정에서 직접 생성해 캐싱하고 재사용해라&lt;/p&gt;
&lt;blockquote&gt;
&lt;p&gt;지연 초기화(아이텝 83)는 코드를 복잡하게 만들고 성능은 크게 개선되지 않을 때가 많기 때문에 권하지는 않는다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;불필요한 객체를 만드는 예&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;/* 
 * keySet()은 호출할 때마다 같은 Set 뷰를 반환한다.
 * 따라서 keySet이 뷰 객체를 여러개 만들 필요도 이득도 없다.
*/
@Test
    public void 불필요한_객체_생성(){
        Map&amp;lt;Integer, String&amp;gt; temps = new HashMap&amp;lt;&amp;gt;();
        temps.put(1, &amp;quot;1&amp;quot;);
        temps.put(2, &amp;quot;2&amp;quot;);
        temps.put(3, &amp;quot;3&amp;quot;);

        Set&amp;lt;Integer&amp;gt; tempsKeySet1 = temps.keySet();
        Set&amp;lt;Integer&amp;gt; tempsKeySet2 = temps.keySet();
        assertThat(tempsKeySet1).contains(1,2,3);

        tempsKeySet2.remove(3);
        assertThat(tempsKeySet2).doesNotContain(3);

}

/* 
 * 오토박싱은 기본 타입과 그에 대응하는 박싱된 기본 타입의 구분을 흐려주지만, 완전히 없애주는 것은 아니다.
 * 박싱된 기본 타입보다는 기본 타입을 사용하고 의도치 않은 오토박싱이 숨어들지 않도록 주의하자.
*/ &lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;이번 아이템은 &lt;em&gt;&lt;strong&gt;객체 생성은 비싸니 피해야 한다가 아니라,&lt;/strong&gt;&lt;/em&gt; &lt;strong&gt;&lt;em&gt;생성 비용이 비싼 객채는 재사용하자&lt;/em&gt;&lt;/strong&gt; 이므로 단순히 객체 생성을 피하고자 객체 풀을 만들어 쓰지는 말자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;&lt;em&gt;반드시 새로 만들어서 사용해야 할 객체를 재사용 했을 때의 피해가, 필요 없는 객체를 반복 생성했을 대의 피해보다 훨씬 크다.&lt;/em&gt;&lt;/strong&gt; (아이템 50 &amp;quot;새로운 객체를 만들어야 한다면 기존 객체를 재사용하지 마라&amp;quot;)&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이템 7. 다 쓴 객체 참조를 해제하라&lt;/h2&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;❌ Bad
public Object pop() {
    if(size == 0) 
        throw new EmptyStackException();
    return elements[--size];
}

✅ Good
public Object pop() {
    if(size == 0) 
        throw new EmptyStackException();
    Object result = elements[--size];
    elements[size] = null; // 다 쓴 객체 참조 해제
    return result;
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;참조 객체 하나를 살려두면 그 객체가 참조하는 모든 객체를 회수하지 못한다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;p&gt;  비활성 영역&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;자기 메모리를 직접 관리하는 클래스에서의 객체 참조&lt;/li&gt;
&lt;li&gt;캐시&lt;/li&gt;
&lt;li&gt;리스너 혹은 콜백&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;  비활성 영역을 가비지 컬렉터에서 처리하는 방법&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;참조 객체의 null 처리&lt;/li&gt;
&lt;li&gt;WeakhashMap 사용&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;단, &lt;strong&gt;&lt;em&gt;객체 참조를 null 처리하는 일은 예외적인 경우&lt;/em&gt;&lt;/strong&gt;여야 한다. 즉, 자기 메모리를 직접 관리하는 클래스인 경우에는 &lt;em&gt;&lt;strong&gt;가비지 컬렉터가 알 수 없는 (비활성 영역의 데이터) 참조 객체들을 null 처리&lt;/strong&gt;&lt;/em&gt; 해주면 된다. &lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이템 8. finalizer와 cleaner 사용을 피하라&lt;/h2&gt;
&lt;p&gt;자바는 두 가지 객체 소멸자를 제공한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;finalizer&lt;/li&gt;
&lt;li&gt;cleaner&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;  절대 사용하면 안될 때&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;제때 실행되어야 하는 작업&lt;/li&gt;
&lt;li&gt;상태를 영구적으로 수정하는 작업에서는 절대 의존하면 안된다.&lt;/li&gt;
&lt;li&gt;System.gc나 System.runFinalization 메서드에 현혹되지 말자. 실행 가능성은 높여주나 역시나 보장해주지는 않는다.&lt;/li&gt;
&lt;/ol&gt;
&lt;blockquote&gt;
&lt;p&gt;하지만 finalizer, cleaner는 즉시 수행된다는 보장이 없어 예측할 수 없고 일반적으로 불필요하다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;ol&gt;
&lt;li&gt;예외 무시&lt;/li&gt;
&lt;li&gt;심각한 성능 문제&lt;/li&gt;
&lt;li&gt;보안 문제&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;8-1. cleaner를 안전망으로 활용하는 AutoCloseable 클래스&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;❌ Bad
public class Adult {
    public static void main(String[] args)  {
        new Room(99);
        System.out.pringln(&amp;quot;안녕~&amp;quot;);
    }
}

✅ Good - try-with-resources
public class Adult {
    public static void main(String[] args) {
        try (Room myRoom = new Room(7)) {
            System.out.pringln(&amp;quot;안녕~&amp;quot;);
        }
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;finalizer나 cleaner 대신 AutoCloseable을 구현해주고 try-with-resources 를 사용하라&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h2&gt;아이템 9. try-finally보다는 try-with-resources를 사용하라&lt;/h2&gt;
&lt;p&gt;try-finally를 사용하는 경우 아래와 같은 단점들이 존재한다.&lt;/p&gt;
&lt;ol&gt;
&lt;li&gt;자원이 둘 이상이면 코드가 지저분해진다.&lt;/li&gt;
&lt;li&gt;자원의 사용처 / close 모두 예외를 던질 때 두번째 예외가 첫번째 예외를 집어 삼키므로 디버깅을 어렵게 한다.&lt;/li&gt;
&lt;/ol&gt;
&lt;p&gt;9-4. 복수의 자원을 처리하는 try-with-resources 짧고 매혹적이다!&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-java&quot;&gt;static void copy(String src, String dst) throws IOException { 
    try (InputStream in = new FileInputStream(src); 
    OutputStream out = new FileOutputStream(dst)) { 
        byte[] buf = new Byte[BUFFER_SIZE]; 
        int n; 
        while((n = in.read(buf)) &amp;gt;= 0) 
            out.write(buf, 0, n); 
    } 
}&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote&gt;
&lt;p&gt;꼭 회수해야 하는 자원을 다룰 때는 try-finally 말고, try-with-resources를 사용하자. 예외는 없다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>기술서적</category>
      <category>EFFECTIVE JAVA3</category>
      <category>아이템1</category>
      <category>아이템2</category>
      <category>아이템3</category>
      <category>아이템4</category>
      <category>아이템5</category>
      <category>아이템6</category>
      <category>아이템7</category>
      <category>아이템8</category>
      <category>아이템9</category>
      <author>skysoo1111</author>
      <guid isPermaLink="true">https://skysoo1111.tistory.com/130</guid>
      <comments>https://skysoo1111.tistory.com/130#entry130comment</comments>
      <pubDate>Fri, 15 Apr 2022 22:27:45 +0900</pubDate>
    </item>
    <item>
      <title>Clean Code - #3장. 함수</title>
      <link>https://skysoo1111.tistory.com/129</link>
      <description>&lt;h3 data-ke-size=&quot;size23&quot;&gt;작게 만들어라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;얼마나 짧아야 좋을까? 작을 수록 좋다.&lt;/i&gt;&lt;/p&gt;
&lt;pre class=&quot;mercury&quot;&gt;&lt;code&gt;중첩 구조가 생길만큼 함수가 커지면 좋지 않다.
if/else, while문 등에 들어가는 블록은 한 줄이어야 한다.&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;다시 말해 블록 안이 두 줄 이상이 된다면 함수로 추출하고 함수 이름을 적절히 짓자.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한가지만 해라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;함수는 한가지 일만 해야하며, 함수 내 추상화 수준은 하나로 맞추면 된다.&lt;/i&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;하지만 추상화 수준이 하나인 함수를 구현하기란 쉽지 않다. 핵심은 짧으면서도 한가지 일만 하는 함수이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;switch 문&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;다형성을 이용하여 각 switch 문을 저차원 클래스에 숨기고 절대로 반복하지 않는 방법이 있다.&lt;/i&gt;&lt;/p&gt;
&lt;pre class=&quot;java&quot;&gt;&lt;code&gt;아래 Bad 코드에는 직원 타입 별로 Pay를 계산하는 switch 문이 존재한다. 
여기서 많은 문제가 있지만 가장 큰 문제는 아래 switch 함수와 동일한 구조의 함수가 무한정 존재할 수 있다. 유형별로 지급일이 다르다면? 등

❌ Bad
public Money calculate(Employee e) throws InvalidEmployeeType {
    switch (e.type) {
        case COMMISSIONED:
            return calculateCommissionedPay(e);
        case HOURLY:
            return calculateHourlyPay(e);
        case SALARIED:
            return calculateSalariedPay(e);
        default:
            throw new InvalidEmployeeType(e.type);
    }
}

✅ Good
public abstract class Employee {
    public abstract boolean isPayday();
    public abstract Money calculatePay();
    public abstract void deliverPay();
}
------
public interface EmployeeFactory {
    public Employee makeEmployee(EmployeeRecord er) throws InvalidEmployeeType;
}
------
public class EmployeeFactoryImpl implements EmployeeFactory {
    public Employee makeEmployee(EmployeeRecord er) throws InvalidEmployeeType {
        switch (er.type) {
            case COMMISSIONED:
                return new CommissionedEmployee(er);
            case HOURLY:
                return new HourlyEmployee(er);
            case SALARIED:
                return new SalariedEmployee(er);
            default:
                throw new InvalidEmployeeType(er.type);
    }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이렇게 switch 문은 다형적 객체를 생성하는 코드에 한해서 한번만 참아준다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;서술적인 이름을 사용하라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;2장에서도 강조하는 내용이지만 이름을 정하는 것은 아무리 강조해도 지나치지 않다.&lt;/i&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;이름이 길어도 괜찮다. 길고 서술적인 이름이 길고 서술적인 주석보다 좋다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;함수 인수&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;이상적인 함수의 인수 개수는 무항(0개)이다. 이항(2개)까지는 괜찮고 삼항은 가급적 피하고 다항(4개 이상)은 특별한 이유가 필요하다.&lt;/i&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;인수가 3개 이상이 넘어가면 테스트 코드를 짜기에도 상당히 부담스럽다. 인수에 따른 갖가지 조합을 구성해서 테스트 해야 하기 때문이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;❗️일반적으로 우리는 인수를 입력 인수로 해석한다. 출력 인수를 사용할 일은 거의 없다. 출력 인수가 필요한 경우 사용하는 것이 this 변수이다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;❗️많이 쓰는 단항 형식
1. 인수에 질문을 던지는 경우
2. 인수를 뭔가로 변환해서 결과를 반환하는 경우
3. 입력은 있지만 출력은 없는 이벤트 함수인 경우

위의 경우를 제외하고서 사용하는 단항 함수는 가급적 피하라.
void includeSetupPageInfo(StringBuffer pageText) 와 같이 변환 함수에서 출력 인수를 사용하면 혼란을 일으킨다.

StringBuffer transform(StringBuffer in) 과 같이 입력 인수를 변환하는 함수라면 변환 결과는 반환 값으로 돌려주자.

❗️플래그 인수는 추하다
함수가 한꺼번에 여러가지를 처리한다고 대놓고 공표하는 것이니까
true일 때는 이것, false일 때는 저것을 한다는 말이니까

❗️이항 함수
이항 함수가 무조건 나쁜 것은 아니다. 하지만 그만큼 위험이 따른 다는 것을 인지해야 한다.
예를 들어서 writeField(name)은 writeField(outputStream, name)보다 이해하기 쉽다. 
전자에 비해 후자는 첫번째 인수를 한번 생각해야 하고 무시해도 되는 인자라는 것을 인지한다. 하지만 언제나 무시한 코드에 오류가 숨어들 수 있다.

❗️인수 객체
인수가 2-3개 필요하다면 독자적인 클래스 변수로 선언하는 것을 고려해보자. 

❗️동사와 키워드
좋은 함수 이름은 함수와 인수가 동사/명사 쌍을 이루는 것이다.
writeField(name) 는 이름이라는 필드를 쓴다는 것을 바로 인지할 수 있다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;명령과 조회를 분리해라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;함수는 뭔가를 수행하거나 답하거나 둘 중 하나만 해야 한다.&lt;/i&gt;&lt;/p&gt;
&lt;pre class=&quot;lasso&quot;&gt;&lt;code&gt;/* 함수 set은 attribute인 속성을 찾아 값을 value로 설정한 후 성공 여부를 반환하는 조회화 명령이 섞인 함수이다.
 * 따라서 아래 Bad 코드와 같이 보기에도 해석하기에도 괴상한 코드가 나온다.
*/
public boolean set(String attribute, String value);

❌ Bad
if(set(&quot;username&quot;, &quot;unclebob&quot;))...

✅ Good
if(attributeExists(&quot;username&quot;)) {
    setAttribute(&quot;username&quot;, &quot;unclebob&quot;);
    ...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;오류 코드보다 예외를 사용하라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;오류 코드를 반환하는 형식은 여러 단계로 중첩되는 코드를 야기하고 오류 코드를 곧바로 처리해야 하는 문제에 부딪힌다.&lt;/i&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;반면 예외를 사용하면 오류 처리 코드가 원래 코드에서 분리되므로 코드가 깔끔해진다.&lt;/p&gt;
&lt;/blockquote&gt;
&lt;pre class=&quot;reasonml&quot;&gt;&lt;code&gt;❌ Bad
if(deletePage(page) == E_OK) {
    if(registry.deleteReference(page.name) == E_OK) {
        if(configKeys.delete(page.name.makeKey()) == E_OK) {
            ...
        }
    }
}

✅ Good
try {
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.delete(page.name.makeKey();
} catch (Exception e) {
    logger.log(e.getMessages());
}

✅✅ Better
/*
 * 더 좋은 것은 정상 동작과 오류 처리 동작을 분리하는 것이다.
 * 상위 함수인 delete()에서 모든 오류를 처리하고 정상 동작은 하위 함수인 deletePageAndAllReferences()에서 처리하므로 코드를 이해하고 수정하기 쉬워진다.
*/
public void delete(Page page) {
    try {
        deletePageAndAllReferences(page);
    } catch (Exception e) {
        logger.log(e.getMessages());
    }
}

private void deletePageAndAllReferences(Page page) throws Exception {
    deletePage(page);
    registry.deleteReference(page.name);
    configKeys.delete(page.name.makeKey();
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;반복하지 마라&lt;/h3&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;i&gt;중복은 소프트웨어에서 악의 근원이다. 중복은 코드가 길어지고 수정 포인트를 늘리고 그만큼 오류 발생 가능성도 늘어난다.&lt;/i&gt;&lt;/p&gt;
&lt;blockquote data-ke-style=&quot;style1&quot;&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;함수도 글쓰기에 일환이다. 처음 생각을 그대로 기록하고 읽기 좋게 다듬는 과정을 거치는 것이다.&lt;br /&gt;처음에는 길고 복잡하지만 코드를 분리하고 이름을 바꾸고 중복을 제거하고 클래스를 쪼개기도 하는 일련의 과정을 거쳐서 깨끗한 코드를 만들어 나가는 것이다.&lt;/p&gt;
&lt;/blockquote&gt;</description>
      <category>기술서적</category>
      <author>skysoo1111</author>
      <guid isPermaLink="true">https://skysoo1111.tistory.com/129</guid>
      <comments>https://skysoo1111.tistory.com/129#entry129comment</comments>
      <pubDate>Fri, 8 Apr 2022 15:39:52 +0900</pubDate>
    </item>
    <item>
      <title>Clean Code - #2장. 의미 있는 이름</title>
      <link>https://skysoo1111.tistory.com/128</link>
      <description>&lt;p data-ke-size=&quot;size16&quot;&gt;clean code 라는 책을 읽고서 일부 내용을 정리하려고 한다.&lt;/p&gt;
&lt;p&gt;&lt;img src=&quot;https://imagedelivery.net/v7-TZByhOiJbNM9RaUdzSA/0b8f20a2-a11a-4341-91cf-04541eda7400/public&quot; alt=&quot;&quot; /&gt;&lt;/p&gt;
&lt;h1&gt;2장. 의미 있는 이름&lt;/h1&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의도를 분명히 밝혀라&lt;/h3&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;그릇된 정보를 피하라&lt;/h3&gt;
&lt;pre class=&quot;dart&quot;&gt;&lt;code&gt;실제 컨테이너가 List일지라도 컨테이너 유형을 이름에 넣지 않는 편이 좋다.

❌ Bad
List&amp;lt;String&amp;gt; accountList = new ArrayList();

✅ Good
List&amp;lt;String&amp;gt; accounts = new ArrayList();

변수명을 소문자 l이나 대문자 O를 사용하는 것은 가독성면에서 최악이다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;의미 있게 구분하라&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;Product라는 클래스가 있다고 가정해보자.
여기에 ProductInfo나 ProductData와 같은 클래스를 추가한다면 개념 구분 없이 이름만 달리한 경우다. 
-Info, -Data는 아무런 정보도 주지 못한다. Product1, Product2 와 마찬가지인 셈이다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;검색하기 쉬운 이름을 사용하라&lt;/h3&gt;
&lt;pre class=&quot;angelscript&quot;&gt;&lt;code&gt;❌ Bad
for (int j=0; j&amp;lt;34; j++) {
    s += (t[j]*4/5);
}

✅ Good
int realDaysPerIdealDay = 4;
int WORK_DAYS_PER_WEEK = 5;
int sum = 0;
for(int j=0; j&amp;lt;NUMBER_OF_TASKS; j++) {
    int realTaskDays = taskEstimate[j] * realDaysPerIdealDay;
    int realTaskWeeks = (realTaskDays / WORK_DAYS_PER_WEEK);
    sum += realTaskWeeks;
}

이름의 의미 있게 지으면 함수가 길어진다. 하지만 WORK_DAYS_PER_WEEK를 찾기가 얼마나 쉬운지 생각해보자.
그냥 5를 사용한다면 숫자5를 모두 찾고 그 의미를 분석해야 한다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;자신의 기억력을 자랑하지 마라&lt;/h3&gt;
&lt;pre class=&quot;erlang&quot;&gt;&lt;code&gt;자신만이 아는 이름으로 변수명을 짓는다면 그것은 바람직하지 못하다.
최악은 a와 b를 이미 사용했으므로 c를 사용한다는 논리이다.

단, 루프 범위가 아주 작은 경우에 한해서 반복 횟수를 세는 i,j,k는 괜찮다.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;클래스 이름 &amp;amp; 메서드 이름&lt;/h3&gt;
&lt;pre class=&quot;armasm&quot;&gt;&lt;code&gt;클래스 이름은 명사나 명사구를 사용하자.
❌ Bad
Manager, Data, Info
✅ Good
Customer, Account

메서드 이름은 동사나 동사구를 사용하자.
✅ Good
postPayment, deletePage

접근자, 변경자, 조건자는 javabean 표준에 따라 get,set,is 를 붙이자.&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;정적 팩토리 메서드를 사용하라&lt;/h3&gt;
&lt;pre class=&quot;fortran&quot;&gt;&lt;code&gt;❌ Bad
Complex fulcrumPoint = new Complex(23.0);

✅ Good
Complex fulcrumPoint = Complex.FromRealNumber(23.0);&lt;/code&gt;&lt;/pre&gt;
&lt;h3 data-ke-size=&quot;size23&quot;&gt;한 개념에 한 단어를 사용하라&lt;/h3&gt;
&lt;pre class=&quot;pgsql&quot;&gt;&lt;code&gt;추상적인 개념 하나에 단어 하나를 선택해 이를 고수한다.(일관성 있는 어휘 사용)
똑같은 메서드를 클래스마다 fetch, retrieve, get으로 제각각 부르면 혼란스럽다.&lt;/code&gt;&lt;/pre&gt;
&lt;hr data-ke-style=&quot;style1&quot; /&gt;</description>
      <category>기술서적</category>
      <author>skysoo1111</author>
      <guid isPermaLink="true">https://skysoo1111.tistory.com/128</guid>
      <comments>https://skysoo1111.tistory.com/128#entry128comment</comments>
      <pubDate>Thu, 7 Apr 2022 12:56:39 +0900</pubDate>
    </item>
    <item>
      <title># VMware 15 player 에 'Install vmware tools' 설치 안되는 이슈 해결</title>
      <link>https://skysoo1111.tistory.com/127</link>
      <description>&lt;p&gt;vmware15 player에서 window10을 설치하고서 vm의 여러 기능(drag and drop / Host-guest간 clipboard 공유 / 전체 화면 등)을 추가 사용하기 위해서 Install vmware tools를 활성화 해줘야한다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;근데 이게 별 짓을 다해도 vm의 Install vmware tools 키가 활성화 되지 않는 것이다.&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;아래와 같이 버튼은 있는데 클릭할 수가 없었다.&lt;/h4&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/cUCfIa/btqT02jEqBY/55rilrAqMFiUPUBoxbAxV1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cUCfIa/btqT02jEqBY/55rilrAqMFiUPUBoxbAxV1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cUCfIa/btqT02jEqBY/55rilrAqMFiUPUBoxbAxV1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcUCfIa%2FbtqT02jEqBY%2F55rilrAqMFiUPUBoxbAxV1%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;/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;문제는 드라이브에 있었다. Install vmware tools 을 설치하려면 처음에 드라이브 형태로 설치가 되는데 vm 기본 설정에서 드라이브를 추가할 수가 없었기 때문이다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;따라서 vm의 설정에서 사용하지 않는 플로피 디스크를 삭제하고 CD/DVD 드라이브를 추가해줬더니 해당 키가 활성화 되었다.&lt;/p&gt;
&lt;p&gt;이런 형태가 되어야 한다.&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/bkqNME/btqT8Jb7h28/agssp3DgfQoyph648NoUv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bkqNME/btqT8Jb7h28/agssp3DgfQoyph648NoUv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bkqNME/btqT8Jb7h28/agssp3DgfQoyph648NoUv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbkqNME%2FbtqT8Jb7h28%2Fagssp3DgfQoyph648NoUv0%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;/figure&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; data-ke-mobilestyle=&quot;widthContent&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dzSaOM/btqT8HSSQyk/8Kv17Xc621uO82mzex4Zv0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dzSaOM/btqT8HSSQyk/8Kv17Xc621uO82mzex4Zv0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dzSaOM/btqT8HSSQyk/8Kv17Xc621uO82mzex4Zv0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdzSaOM%2FbtqT8HSSQyk%2F8Kv17Xc621uO82mzex4Zv0%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;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;그 뒤 D드라이브 활성화 하겠냐고 묻고 착착 진행하면 된다!&lt;/p&gt;</description>
      <category>OS/Error</category>
      <category>install vmware tools 설치 안됨</category>
      <category>install vmware tools 에러</category>
      <category>VMWare</category>
      <category>vmware 에러</category>
      <author>skysoo1111</author>
      <guid isPermaLink="true">https://skysoo1111.tistory.com/127</guid>
      <comments>https://skysoo1111.tistory.com/127#entry127comment</comments>
      <pubDate>Tue, 19 Jan 2021 16:51:16 +0900</pubDate>
    </item>
    <item>
      <title># VM CentOS7에서 yum error [could not resolve host] 발생</title>
      <link>https://skysoo1111.tistory.com/126</link>
      <description>&lt;p&gt;vm에 centos 구성 후 yum 실행시 해당 에러로 설치가 안될 때가 있다.&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;확인사항 1. ping으로 외부 통신 테스트&lt;/h4&gt;
&lt;pre id=&quot;code_1610950353979&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ping 8.8.8.8&lt;/code&gt;&lt;/pre&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/bEljPY/btqTYQ34Bhw/LGES2882WIzg6MK0K3nKCK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bEljPY/btqTYQ34Bhw/LGES2882WIzg6MK0K3nKCK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bEljPY/btqTYQ34Bhw/LGES2882WIzg6MK0K3nKCK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbEljPY%2FbtqTYQ34Bhw%2FLGES2882WIzg6MK0K3nKCK%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;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;확인사항 2. NetworkManager resolve.conf 확인&lt;/h4&gt;
&lt;pre id=&quot;code_1610950432303&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ vi /etc/resolv.conf
nameserver 192.168.10.1 # 추가 &amp;lt;각자 상황에 맞는 IP&amp;gt;

$ systemctl restart NetworkManager # NetworkManager 재실행

$ systemctl status NetworkManager # NetworkManager 기동 확인&lt;/code&gt;&lt;/pre&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/MVOI9/btqTJSQb1FR/wT3TthJlj3XqUdvVMrfQeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/MVOI9/btqTJSQb1FR/wT3TthJlj3XqUdvVMrfQeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/MVOI9/btqTJSQb1FR/wT3TthJlj3XqUdvVMrfQeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FMVOI9%2FbtqTJSQb1FR%2FwT3TthJlj3XqUdvVMrfQeK%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;/figure&gt;
&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;
&lt;p&gt;다시 yum install 수행하면 정상 수행 될 것이다.&lt;/p&gt;</description>
      <category>OS/Error</category>
      <category>centos yum error</category>
      <category>centos7</category>
      <category>could not resolve host</category>
      <category>yum install error</category>
      <author>skysoo1111</author>
      <guid isPermaLink="true">https://skysoo1111.tistory.com/126</guid>
      <comments>https://skysoo1111.tistory.com/126#entry126comment</comments>
      <pubDate>Mon, 18 Jan 2021 15:18:12 +0900</pubDate>
    </item>
    <item>
      <title># VirtualBox에서 CentOS7 고정 IP 설정</title>
      <link>https://skysoo1111.tistory.com/125</link>
      <description>&lt;h4 data-ke-size=&quot;size20&quot;&gt;기본적으로 Clean OS를 설치했다면 아래 작업은 해주자.&lt;/h4&gt;
&lt;pre id=&quot;code_1611645311991&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;yum update
yum install epel-release -y&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;&amp;nbsp;&lt;/h4&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step 1. VirtualBox 네트워크 설정하기&lt;/h4&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/b2Uhv3/btqS6YKcJ4N/E1JtbZAH4W7XK9B4WYVD30/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/b2Uhv3/btqS6YKcJ4N/E1JtbZAH4W7XK9B4WYVD30/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/b2Uhv3/btqS6YKcJ4N/E1JtbZAH4W7XK9B4WYVD30/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fb2Uhv3%2FbtqS6YKcJ4N%2FE1JtbZAH4W7XK9B4WYVD30%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;/figure&gt;
&lt;/p&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step 2. 서버의 네트워크 설정 변경&lt;/h4&gt;
&lt;pre id=&quot;code_1610426849248&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt; $ vi /etc/sysconfig/network-scripts/ifcfg-enp0s3&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1610427299765&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;TYPE=&quot;Ethernet&quot;
PROXY_METHOD=&quot;none&quot;
BROWSER_ONLY=&quot;no&quot;
BOOTPROTO=&quot;dhcp&quot;
DEFROUTE=&quot;yes&quot;
IPV4_FAILURE_FATAL=&quot;no&quot;
IPV6INIT=&quot;yes&quot;
IPV6_AUTOCONF=&quot;yes&quot;
IPV6_DEFROUTE=&quot;yes&quot;
IPV6_FAILURE_FATAL=&quot;no&quot;
IPV6_ADDR_GEN_MODE=&quot;stable-privacy&quot;
NAME=&quot;enp0s3&quot;
UUID=&quot;91af51db-7cf0-4069-9433-77d356b31bca&quot;
DEVICE=&quot;enp0s3&quot;
ONBOOT=&quot;yes&quot; # 변경사항
GATEWAY=192.168.10.1 # 변경사항
IPADDR=192.168.10.11 # 변경사항&lt;/code&gt;&lt;/pre&gt;
&lt;pre id=&quot;code_1610427322727&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ systemctl restart network&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step 3. DNS 추가&lt;/h4&gt;
&lt;pre id=&quot;code_1610427474616&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ vi /etc/resolv.conf
nameserver 8.8.8.8&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step 4. IP 및 ping 확인&lt;/h4&gt;
&lt;pre id=&quot;code_1610427371158&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;$ ip addr show

$ ping 8.8.8.8&lt;/code&gt;&lt;/pre&gt;
&lt;h4 data-ke-size=&quot;size20&quot;&gt;Step 5. 외부 접속 확인&lt;/h4&gt;
&lt;p&gt;putty나 mobaXterm 활용&lt;/p&gt;
&lt;p&gt;&amp;nbsp;&lt;/p&gt;</description>
      <category>OS</category>
      <category>centos7</category>
      <category>ip 고정 할당</category>
      <category>VirtualBox</category>
      <category>virtualbox ping</category>
      <author>skysoo1111</author>
      <guid isPermaLink="true">https://skysoo1111.tistory.com/125</guid>
      <comments>https://skysoo1111.tistory.com/125#entry125comment</comments>
      <pubDate>Tue, 12 Jan 2021 13:59:26 +0900</pubDate>
    </item>
  </channel>
</rss>