<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0">
  <channel>
    <title>빨간색코딩</title>
    <link>https://sjh836.tistory.com/</link>
    <description>사람중심의 코딩,
보기쉬운 소스,
남을 위해 오늘도 삽질합니다</description>
    <language>ko</language>
    <pubDate>Sat, 14 Mar 2026 23:30:50 +0900</pubDate>
    <generator>TISTORY</generator>
    <ttl>100</ttl>
    <managingEditor>빨간색소년</managingEditor>
    <image>
      <title>빨간색코딩</title>
      <url>https://t1.daumcdn.net/cfile/tistory/215DB34E585E3EC136</url>
      <link>https://sjh836.tistory.com</link>
    </image>
    <item>
      <title>[데이터 중심 애플리케이션 설계] 2장. 데이터 모델과 질의 언어</title>
      <link>https://sjh836.tistory.com/196</link>
      <description>&lt;ul&gt;
&lt;li&gt;참고문서&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://github.com/ept/ddia-references&quot;&gt;https://github.com/ept/ddia-references&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;데이터 중심 애플리케이션 설계(마틴 클레프만, 위키북스)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;데애설.jpg&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cOsgNc/btrAXBqyF9q/gn6PKUwysUHycupGiKffEK/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cOsgNc/btrAXBqyF9q/gn6PKUwysUHycupGiKffEK/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cOsgNc/btrAXBqyF9q/gn6PKUwysUHycupGiKffEK/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcOsgNc%2FbtrAXBqyF9q%2Fgn6PKUwysUHycupGiKffEK%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;276&quot; height=&quot;353&quot; data-filename=&quot;데애설.jpg&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p&gt;초심자의 눈으로 이해한 내용을 정리해보았다. 책에 있는 내용을 기반으로 썼지만, 책에 없는 내용도 조금씩 적어보았다. 책은 꼭 사서 보시길 바랍니다..&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;데이터 모델은 우리가 어떤 문제를 어떻게 해결해야하는지, 생각에도 영향을 미친다.&lt;ul&gt;
&lt;li&gt;데이터 모델은 그 위에서 소프트웨어가 할 수 있는 일과 할 수 없는 일에 영향을 준다.&lt;/li&gt;
&lt;li&gt;ex) 어떤 연산은 빠르고, 어떤 연산은 느리다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 모델의 큰 범주&lt;ul&gt;
&lt;li&gt;관계형 모델&lt;/li&gt;
&lt;li&gt;문서 모델&lt;/li&gt;
&lt;li&gt;그래프 모델&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;1. 관계형 모델과 문서 모델&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;관계형 모델&lt;ul&gt;
&lt;li&gt;데이터는 관계(relation)으로 구성&lt;/li&gt;
&lt;li&gt;각 관계는 순서없는 튜플(tuple)의 모음&lt;/li&gt;
&lt;li&gt;SQL 은 정규화된 구조로 데이터를 저장하고 질의할 필요가 있을 때, 대부분 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;NoSQL 의 탄생 : Not Only SQL&lt;ul&gt;
&lt;li&gt;대규모 데이터셋이나 높은 쓰기처리량을 달성&lt;/li&gt;
&lt;li&gt;무료, 오픈소스 스프트웨어 선호&lt;/li&gt;
&lt;li&gt;관계형 모델에서 지원하지 않는 특수 질의&lt;/li&gt;
&lt;li&gt;관계형의 스키마 제한에 대한 불만, 동적이고 표현력 풍부한 데이터 모델을 선호&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1-1. 데이터 모델들이 관계를 표현하는 방법&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;객체 관계형 불일치&lt;ul&gt;
&lt;li&gt;OOP의 객체를 SQL 데이터 모델로 바꾸려면 거추장스러운 전환 계층이 필요 : 임피던스(impedance) 불일치&lt;/li&gt;
&lt;li&gt;ActiveRecord 나 Hibernate 같은 ORM 으로 boilerplate code 를 줄이지만, 여전히 두 모델의 차이가 완벽히 숨겨지진 않는다.&lt;/li&gt;
&lt;li&gt;ex) 1:N 관계를 관계형 모델로 표현하려면,&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cj60BL/btrAVr22YPL/K0uKfpYg7B1EKeJWnKQ0Ok/img.png&quot; alt=&quot;그림2-1&quot;&gt;&lt;/li&gt;
&lt;li&gt;1안) multi table 스키마로 정규화하고 join&lt;/li&gt;
&lt;li&gt;2안) 구조화된 데이터 타입 사용(xml, json)하여 저장. 최신 표준 RDBMS 는 이런 타입도 질의, 색인 가능&lt;/li&gt;
&lt;li&gt;3안) 자체적으로 부호화해서 저장하여 응용단에서 활용. 질의, 색인 불가능&lt;/li&gt;
&lt;li&gt;문서 지향(document oriented) DB를 사용하면 JSON 데이터 모델을 지원하는데, 위와 같은 문제를 쉽게 해결&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;1:N 관계는 의미상 트리 구조와 같다.&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/Ty4i9/btrATdEsPGH/2lOvG5cG5eoLkcrTRXtJsK/img.png&quot; alt=&quot;그림2-2&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JSON 데이터모델 표현은 multi table 스키마보다 더 나은 지역성(locality)을 갖는다.&lt;ul&gt;
&lt;li&gt;NoSQL 들이 분산환경을 지원하는 관점에서, 물리적인 지역성(seek)보다는 스키마의 집약성을 이야기하는 듯&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;N대1 관계&lt;ul&gt;
&lt;li&gt;ex) N명이 하나의 학교를 가르킴&lt;/li&gt;
&lt;li&gt;지역명이나 학교명을 평문으로 저장하게되면, 나중에 이름이 바뀌었을 때 갱신하기 어려워진다.&lt;/li&gt;
&lt;li&gt;unique id 로 저장하게 된다면, id 자체는 아무런 의미가 없기때문에 변경할 필요가 없어진다.&lt;/li&gt;
&lt;li&gt;id가 의미를 갖게되면, 미래에 언젠가 id를 변경해야할 수도 있다.&lt;/li&gt;
&lt;li&gt;이러한 중복된 데이터(N대 1)를 정규화하는 것은 관계형 모델에선 쉽지만, 문서 지향에선 어렵다.&lt;/li&gt;
&lt;li&gt;DBMS가 join을 지원해주지 않으면, 어플리케이션에서 다중 질의를 해서 join을 흉내내야 한다.&lt;/li&gt;
&lt;li&gt;지금은 join이 필요없게 만들더라도, 비즈니스가 확장되가며, 데이터는 점차 상호 연결되는 경향이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;N대M 관계&lt;ul&gt;
&lt;li&gt;ex) 프로필에 추천해준 사람들의 프로필이 뜬다. 이때, 추천인이 사진을 바꾸면, 이 사람이 추천해준 모든 사람 내 프로필의 추천인 사진이 바뀌어야한다. 즉, 엔티티를 참조해야한다.&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/oC2H8/btrAWASXP70/kKBO4IjYhrYokM0d8eo6E0/img.png&quot; alt=&quot;그림2-4&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;N대1과 N대M처럼 다대다 관계에서 데이터를 중복할지, 아니면 분리하고 수동으로 참조해야할 지에 대한 논쟁은, NoSQL 이전부터 있었다.&lt;ul&gt;
&lt;li&gt;1970년대 계층모델(JSON 데이터 모델과 유사)에서 이 문제를 해결하기 위한 관계형 모델의 join vs 네트워크 모델&lt;/li&gt;
&lt;li&gt;계층 모델 : 트리 구조에서 모든 레코드는 하나의 부모만 있다. 1대N 에선 쉽게 동작&lt;ul&gt;
&lt;li&gt;오늘 날의 json 모델과 비슷하다.&lt;/li&gt;
&lt;li&gt;N대M 관계를 표현하려면, 데이터를 중복하거나 다른 레코드 참조를 수동으로 해결해야만 했다. (어려움)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;네트워크 모델 : 레코드는 다중 부모가 있을 수 있다. 따라서, N대1, N대M 문제를 해결&lt;ul&gt;
&lt;li&gt;레코드 간 연결은 외래 키보다는 포인터와 유사&lt;/li&gt;
&lt;li&gt;레코드에 접근하는 방법은 root 에서 연속된 연결 경로를 따라가는 것&lt;/li&gt;
&lt;li&gt;N대M 에서는 다양한 경로가 있을 수 있어서, 개발자가 경로 선택, 탐색을 해줘야했다. (복잡함)&lt;/li&gt;
&lt;li&gt;외래 데이터를 삽입하는 것 자체가 join이 이미 수행된 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;관계형 모델 : 관계는 단순히 튜플의 컬렉션일 뿐&lt;ul&gt;
&lt;li&gt;외래 관계에서도 새로운 레코드를 쉽게 생성 가능 (외래 키 제약을 줄수는 있긴 함)&lt;/li&gt;
&lt;li&gt;개발자는 접근 경로를 신경쓸 필요가 없다. 질의 최적화(query optimizer)로 자동으로 만든다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;문서 지향 DB&lt;ul&gt;
&lt;li&gt;N대1, N대M 관계를 표현할 때는 RDB와 다르지 않다. 외래키와 같이 문서를 참조한다.&lt;/li&gt;
&lt;li&gt;네트워크 모델은 이것을 복잡하게 지원해서 망했지만, 오늘 날의 문서지향 DB들은 이 부분을 수용했다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;1-2. 문서 모델&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;문서 모델에 적합한 어플리케이션은?&lt;ul&gt;
&lt;li&gt;문서와 비슷한 구조(1대N, 트리 구조로 한번에 전체 트리를 적재)라면 문서 모델&lt;/li&gt;
&lt;li&gt;문서 모델의 제약&lt;ul&gt;
&lt;li&gt;embed 된 field 를 직접 참조할 수 없음 : &lt;code&gt;root.education.list[1]&lt;/code&gt; 와 같이 접근 경로로 해야함&lt;/li&gt;
&lt;li&gt;N대M 관계를 사용해야하면 매력이 떨어짐&lt;ul&gt;
&lt;li&gt;비정규화로 join 을 줄일 수 있으나, 일관성을 맞추기 위한 추가작업이 소요&lt;ul&gt;
&lt;li&gt;cf) mongodb 4.0부터 updateMany 로 multi document transaction 을 지원하면서 좀 더 쉬워지긴 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;어플리케이션단 join으로 풀 수도 있으나, DBMS에서 지원해주는 것보다 느림&lt;ul&gt;
&lt;li&gt;cf) mongodb 3.2부터 aggregate 에서 $lookup 으로 join을 지원하나, 샤딩된 컬렉션에선 사용할 수 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;상호 연결이 많은 데이터일 수록 문서 모델은 곤란, 관계형 모델은 무난, 그래프 모델은 매우 자연스럽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;문서 모델의 스키마 유연성(schemaless)&lt;ul&gt;
&lt;li&gt;사실 스키마가 완전히 없다고 볼 수는 없다.&lt;/li&gt;
&lt;li&gt;쓰기 스키마는 없지만, 읽기 스키마가 암묵적으로 존재하기 때문이다.&lt;ul&gt;
&lt;li&gt;쓰기 스키마 : 정적 타입 확인, RDB의 방식. 이게 없다면 임의의 키와 값을 자유롭게 추가&lt;/li&gt;
&lt;li&gt;읽기 스키마 : 동적 타입 확인, 문서DB의 방식. 이게 없다면 필드의 존재여부가 보장되지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;즉, 어플리케이션은 데이터를 읽는 코드가 있으므로 읽기 스키마를 어느정도 가정하고 사용한다.&lt;/li&gt;
&lt;li&gt;예를들어, fullname 을 저장하고 있다가, 성과 이름을 분리하는 작업이 있다고 가정하면,&lt;ul&gt;
&lt;li&gt;문서 모델 : firstName, lastName 을 그냥 추가해서 저장하면 된다. 대신, 작업날짜를 기점으로 과거 데이터는 fullname을 split 쳐서 읽어야한다.&lt;/li&gt;
&lt;li&gt;관계 모델 : &lt;code&gt;ALTER TABLE users ADD COLUMN firstName VARCHAR(20)&lt;/code&gt; 으로 쓰기스키마를 변경하고 마이그레이션을 해줘야한다. 중단시간이 발생할 수도 있다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;읽기 스키마 방식은 컬렉션 내 문서들이 모종의 이유로 동일한 구조가 아닐 때 유리하다.&lt;ul&gt;
&lt;li&gt;하위에 여러 유형의 Object들이 있을 때, 각 유형마다 table을 만드는 것은 실용적이지 않으므로..&lt;/li&gt;
&lt;li&gt;개발자가 정적으로 제어하는게 아니라, 언제나 변경 가능한 외부 시스템에 의해 데이터 구조가 결정될 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;저장소 지역성(storage locality)&lt;ul&gt;
&lt;li&gt;관련 데이터를 함께 그룹화하는 개념&lt;/li&gt;
&lt;li&gt;문서는 json, xml 로 부호화된 문자열이나 BSON 같은 바이너리로 저장된다.&lt;/li&gt;
&lt;li&gt;문서 중 일부만을 접근경로로 꺼내서 사용하지 않고, 전체를 잘 사용한다면 지역성의 이점을 누렸다고 볼 수 있다. (낭비가 없었으므로)&lt;ul&gt;
&lt;li&gt;전체를 찾아야한다면 RDB는 multi table 을 join 해야하므로 이런 지역성 관점에선 좋지않다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;부호화된 문서의 크기를 바꾸지 않는 수정이라면 문서DB는 수정을 쉽게 수행한다.&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mongodb.com/blog/post/schema-design-for-time-series-data-in-mongodb&quot;&gt;https://www.mongodb.com/blog/post/schema-design-for-time-series-data-in-mongodb&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;문서의 구조, 크기가 증가하는 쓰기를 피하라고 하는 이유 : 색인같은걸 다시 쓴다는 듯?&lt;/li&gt;
&lt;li&gt;필드 수준의 업데이트를 권고&lt;/li&gt;
&lt;li&gt;이 성능 제한때문에 유용한 상황이 줄어들긴 함&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$inc&lt;/code&gt; 같은걸 지원하는 이유다. 원자성은 덤&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;문서 모델에만 있는 것은 아니다.&lt;ul&gt;
&lt;li&gt;오라클의 multi-table index cluster table&lt;/li&gt;
&lt;li&gt;column-family 개념(카산드라, HBase)의 지역성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;2. 데이터를 위한 질의 언어&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;명령형 언어는 특정 순서로 특정 연산을 수행하라고 일일이 모두 정의하고 지시한다.&lt;ul&gt;
&lt;li&gt;결과를 결정하기 위한 알고리즘을 모두 지정&lt;/li&gt;
&lt;li&gt;멀티 코어 병렬 처리가 어려움&lt;/li&gt;
&lt;li&gt;cf) 네트워크 모델류의 DB와 IMS 가 이런 형태를 사용함&lt;/li&gt;
&lt;li&gt;ex) 특정 html태그를 javascript로 DOM API 를 사용해서 스타일링하는 것. 더 좋은 api를 사용하려면 코드바꿔야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;SQL, 관계대수(relational algebra) 같은 선언형 언어는 결과를 충족해야하는 조건과 변환을 지정해주기만 하면 된다.&lt;ul&gt;
&lt;li&gt;내부적으로 어떤 순서로 어떤 연산을 수행할지는 쿼리 최적화기가 알아서 한다.&lt;/li&gt;
&lt;li&gt;멀티 코어 병렬 처리도 알아서 활용&lt;/li&gt;
&lt;li&gt;ex) 특정 html태그를 css(선언형)로 꾸미는것. 브라우저 벤더가 알아서 최적화함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;2-1. 맵리듀스(MapReduce) 질의&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;클러스터 환경에서 분산 실행을 위한 프로그래밍 모델&lt;ul&gt;
&lt;li&gt;많은 컴퓨터에서 대량의 데이터를 처리&lt;/li&gt;
&lt;li&gt;일부 NoSQL DBMS가 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;선언형 언어도 아니고 명령형 언어도 아닌 중간 지점에 있다. pure한 javasciprt 함수 2개를 정의&lt;pre&gt;&lt;code&gt;db.collection.mapReduce({
    function map() {
        var year = this.timestamp.getFullYear();
        var month = this.timestamp.getMonth() + 1;
        emit(year + &amp;quot;-&amp;quot; + month, this.number);
    },
    function reduce(key, values) { reutrn Array.sum(values); },
    {
        query: { type: &amp;quot;A&amp;quot; },
        out: &amp;quot;monthlyCount&amp;quot;
    }
})&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;이것은 저수준의 모델이다. js 함수를 어떻게 작성하냐에 따라 성능이 달라질 수도 있고 어렵다.&lt;/li&gt;
&lt;li&gt;mongodb 2.2부터 aggregation pipeline 을 통해 선언형 질의를 지원한다.&lt;pre&gt;&lt;code&gt;db.collection.aggregate([
    { $match: { type: &amp;quot;A&amp;quot; } },
    { $group: {
        _id: {
            year: { $year: &amp;quot;$timestamp&amp;quot; },
            month: { $month: &amp;quot;$timestamp&amp;quot; },
        },
        monthlyCount: { $sum: &amp;quot;$number&amp;quot; }
    }}
])&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;성능 비교 : &lt;a href=&quot;https://sysdig.com/blog/mongodb-showdown-aggregate-vs-map-reduce/&quot;&gt;https://sysdig.com/blog/mongodb-showdown-aggregate-vs-map-reduce/&lt;/a&gt;&lt;ul&gt;
&lt;li&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bHcJRB/btryPCeLp6M/Vl7NEku2xg2kpdG2Lp5pnk/img.png&quot; alt=&quot;벤치마크1&quot;&gt;&lt;/li&gt;
&lt;li&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZwYJg/btrARFAtnOJ/pt5MAhkUOB72OgECyA1uf0/img.png&quot; alt=&quot;벤치마크2&quot;&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;마이그레이션 가이드 : &lt;a href=&quot;https://www.mongodb.com/docs/manual/reference/map-reduce-to-aggregation-pipeline/&quot;&gt;https://www.mongodb.com/docs/manual/reference/map-reduce-to-aggregation-pipeline/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;3. 그래프형 데이터 모델&lt;/h2&gt;
&lt;ul&gt;
&lt;li&gt;N대M 관계가 일반적일 때 사용&lt;/li&gt;
&lt;li&gt;그래프 = 정점(=노드, 엔티티, vertex) + 간선(관계, 호, edge)&lt;/li&gt;
&lt;li&gt;단일 노드에서 사용 예제&lt;ul&gt;
&lt;li&gt;소셜 그래프 : vertex = 사람 , edge = 사람들이 서로 알고 있음&lt;/li&gt;
&lt;li&gt;웹 그래프 : vertex = 웹페이지 , edge = 다른 페이지로의 링크&lt;ul&gt;
&lt;li&gt;ex) pagerank : 웹페이지의 인기와 검색 결과에서 순위 결정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;교통 네트워크 : vertex = 교차로 , edge = 교차로 간 도로, 철도&lt;ul&gt;
&lt;li&gt;ex) 네비게이션 : 최단 경로 탐색&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;복합 노드에서 사용 예제&lt;ul&gt;
&lt;li&gt;페이스북 : vertex = 사람, 장소, 이벤트, 댓글 등 , edge = 친구여부, 체크인 위치, 이벤트 참석, 포스트 댓글 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그래프 모델&lt;ul&gt;
&lt;li&gt;속성 그래프 모델 : Neo4j, Titan, InfiniteGraph&lt;/li&gt;
&lt;li&gt;트리플 저장소 모델 : Datomic, AllegroGraph&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그래프 질의 언어&lt;ul&gt;
&lt;li&gt;선언형 질의 언어 : 사이퍼(Cypher), 스파클(SPARQL), Datalog&lt;/li&gt;
&lt;li&gt;명령형 질의 언어 : 그렘린(Gremlin)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;그래프 처리 프레임워크 : 프리글(Pregel)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3-1. 속성 그래프 모델&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;vertex의 요소&lt;ul&gt;
&lt;li&gt;고유한 식별자&lt;/li&gt;
&lt;li&gt;outgoing edge 집합 : 이 vertex 에서 나가는 간선들&lt;/li&gt;
&lt;li&gt;incoming edge 집합 : 이 vertex 로 들어오는 간선들&lt;/li&gt;
&lt;li&gt;속성(attribute) 컬렉션(key-value 쌍)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;edge의 요소&lt;ul&gt;
&lt;li&gt;고유한 식별자&lt;/li&gt;
&lt;li&gt;edge 가 시작하는 vertex : tail vertex = 꼬리 노드&lt;/li&gt;
&lt;li&gt;edge 가 끝나는 vertex : head vertex = 머리 노드&lt;/li&gt;
&lt;li&gt;두 vertex 간 관계 유형을 설명하는 레이블&lt;/li&gt;
&lt;li&gt;속성(attribute) 컬렉션(key-value 쌍)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3-1-1. 사이퍼(Cypher) 질의 언어&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;속성 그래프를 위한 선언현 질의 언어&lt;/li&gt;
&lt;li&gt;neo4j 용으로 만들어졌다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;CREATE (USA:Location {name:&amp;#39;USA&amp;#39;, type:&amp;#39;country}), (Idaho:Location {name:&amp;#39;Idaho&amp;#39;, type:&amp;#39;state&amp;#39;}))&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;(Idaho) - [:WITHIN] -&amp;gt; (USA)&lt;/code&gt; : 꼬리노드 Idaho 에서 머리노드 USA인 WITHIN 레이블로 구성된 edge&lt;/li&gt;
&lt;li&gt;&lt;code&gt;() -&amp;gt; [:WITHIN*0..] -&amp;gt; ()&lt;/code&gt; : 0회 이상 WITHIN edge을 따라 가라&lt;/li&gt;
&lt;li&gt;순방향으로 질의할 수도 있지만, 역방향으로 질의할 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3-1-2. SQL 에서 Graph 질의&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;p&gt;관계형 스키마로 표현&lt;/p&gt;
&lt;pre&gt;&lt;code&gt;CREATE TABLE vertices (
  vertex_id INTEGER PRIMARY KEY,
  properties JSON
);

CREATE TABLE edges (
  edge_id INTEGER PRIMARY KEY,
  tail_vertex INTEGER REFERENCES vertices (vertex_id),
  head_vertex INTEGER REFERENCES vertices (vertex_id),
  label TEXT,
  properties JSON
)

CREATE INDEX edges_tails ON edges (tail_vertex); # vertex 가 주어지면 outgoing edge 집합을 쉽게 찾음
CREATE INDEX edges_heads ON edges (head_vertex); # vertex 가 주어지면 incoming edge 집합을 쉽게 찾음&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;li&gt;&lt;p&gt;SQL 에서는 질의에 필요한 join 을 미리 알고 있어야하지만,&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;그래프 질의에서는 찾고자 하는 vertex 를 찾기 전에 가변적인 여러 edge 를 순회해야 한다. 따라서, 미리 join 수를 알 수가 없다.&lt;/li&gt;
&lt;li&gt;SQL 에서 굳이 어렵게라도 하려면, 재귀 공통 테이블식(recursive common table expression)을 통해 구현할 수 있다.&lt;pre&gt;&lt;code&gt;WITH RECURSIVE 가상테이블1명 AS (
SELECT - # 초기질의
UNION [ALL]
SELECT - # 재귀질의
[WHERE -] # 종료 조건
), ( 가상테이블2명 AS ... ), ( ... )&lt;/code&gt;&lt;/pre&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;p&gt;책에서는 RDBMS 에서 그래프 데이터 모델을 권장하고 있진 않지만, 스토리지엔진으로 InnoDB 대신 그래프 엔진을 선택하면 위에보단 나은 성능을 가져간다.&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;MariaDB : OQGRAPH(Open Query Graph)&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://mariadb.com/kb/en/oqgraph-storage-engine/&quot;&gt;https://mariadb.com/kb/en/oqgraph-storage-engine/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://mariadb.com/kb/en/oqgraph-examples/&quot;&gt;https://mariadb.com/kb/en/oqgraph-examples/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.slideshare.net/AntonyTCurtis/oqgraph-at-mysql-users-conference-2011&quot;&gt;https://www.slideshare.net/AntonyTCurtis/oqgraph-at-mysql-users-conference-2011&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;PostgreSQL : Apache AGE&lt;ul&gt;
&lt;li&gt;PostgreSQL의 OpenCypher의 그래프 데이터베이스 기능을 지원해주는 확장 라이브러리&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://age.apache.org/&quot;&gt;https://age.apache.org/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3-1-3. 문서DB 에서 Graph 질의&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;mongodb 3.4 부터 &lt;code&gt;$graphLookup&lt;/code&gt; 을 지원한다.&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/&quot;&gt;https://www.mongodb.com/docs/manual/reference/operator/aggregation/graphLookup/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;$lookup 과 마찬가지로 sharding 컬렉션에선 사용 불가능&lt;/li&gt;
&lt;li&gt;일반적인 graph 유즈케이스의 70% 정도를 커버&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3-2. 트리플 저장소 모델&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;트리플 저장소 모델은 같은 생각을 다른 용어로 사용해서 설명할 뿐, 속성 그래프 모델과 거의 동등하다.&lt;/li&gt;
&lt;li&gt;three-part(트리플) statements : 주어(subject) + 서술어(predicate) + 목적어(object)&lt;ul&gt;
&lt;li&gt;ex) 빨간색소년(=주어)은 사과(=목적어)를 좋아(=서술어)한다.&lt;/li&gt;
&lt;li&gt;주어 = vertex&lt;/li&gt;
&lt;li&gt;목적어&lt;ul&gt;
&lt;li&gt;원시 데이터값(정수, 문자열)이라면, 서술어와 목적어는 각각 vertex 속성의 key와 value 이다.&lt;/li&gt;
&lt;li&gt;그래프의 다른 vertex 이라면, 서술어는 edge 이고, 주어는 꼬리 노드, 목적어는 머리 노드이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;turtle 형식으로 트리플 작성 예제&lt;ul&gt;
&lt;li&gt;turtle 문법 : &lt;a href=&quot;https://www.w3.org/TR/turtle/&quot;&gt;https://www.w3.org/TR/turtle/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_:lucy a :Person.&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_:lucy :name &amp;quot;Lucy&amp;quot;.&lt;/code&gt; : vertex lucy의 name은 Lucy다.&lt;/li&gt;
&lt;li&gt;&lt;code&gt;_:lucy :bornIn _:idaho.&lt;/code&gt; : vertex lucy 는 vertex idaho 에서 태어났다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4&gt;3-2-1. 스파클(SPARQL) 질의 언어&lt;/h4&gt;
&lt;ul&gt;
&lt;li&gt;&lt;a href=&quot;https://www.w3.org/TR/sparql11-query/&quot;&gt;https://www.w3.org/TR/sparql11-query/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;SPARQL Protocol and RDF Query Language&lt;/li&gt;
&lt;li&gt;RDF 데이터 모델을 사용한 트리플 저장소 질의 언어&lt;ul&gt;
&lt;li&gt;RDF = Resource Description Framework&lt;/li&gt;
&lt;li&gt;시맨틱 웹에서 유래 : 서로 다른 웹사이트가 일관된 형식으로 데이터를 개시한다면, 만물을 데이터베이스처럼 쓸 수 있을 것이다.&lt;/li&gt;
&lt;li&gt;사람이 읽을 수 있는 형식&lt;/li&gt;
&lt;li&gt;인터넷 전체의 데이터 교환을 위해 설계&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;사이퍼 보다 먼저 만들어지고, 사이퍼가 스파클의 일부를 차용함&lt;/li&gt;
&lt;li&gt;스파클은 변수를 ?으로 시작&lt;/li&gt;
&lt;li&gt;ex1) 미국에서 유럽으로 이주한 사람 찾기&lt;ul&gt;
&lt;li&gt;사이퍼 : &lt;code&gt;(person) -[:BORN_IN] -&amp;gt; () -[:WITHIN*0..] -&amp;gt; (location)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;스파클 : &lt;code&gt;?person :bornIn / :within* ?location&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;ex2) usa 변수 선언&lt;ul&gt;
&lt;li&gt;사이퍼 : &lt;code&gt;(usa {name:&amp;#39;United States})&amp;#39;&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;스파클 : &lt;code&gt;?usa :name &amp;#39;United States&amp;#39;.&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3-3. Datalog&lt;/h3&gt;
&lt;ul&gt;
&lt;li&gt;1980년대 탄생, 가장 오래되었음&lt;/li&gt;
&lt;li&gt;&lt;code&gt;서술어(주어, 목적어)&lt;/code&gt;로 작성한다.&lt;ul&gt;
&lt;li&gt;&lt;code&gt;name(usa, &amp;#39;United States&amp;#39;)&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;within(idaho, usa)&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;변수는 대문자로 시작&lt;/li&gt;
&lt;li&gt;사이퍼나 스파클은 바로 SELECT로 질의하지만, Datalog 는 단계를 나눠 조금씩 질의하며 나아간다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;3-4. 그래프 데이터 모델과 네트워크 데이터 모델의 차이점&lt;/h3&gt;
&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th align=&quot;left&quot;&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;strong&gt;네트워크 모델&lt;/strong&gt;&lt;/th&gt;
&lt;th align=&quot;left&quot;&gt;&lt;strong&gt;그래프 모델&lt;/strong&gt;&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;스키마&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;중첩 가능한 레코드 타입을 지정&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;vertex는 edge 로 다른 vertex와 자유롭게 연결&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;특정 레코드 도달&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;접근 경로 중 하나씩 탐색&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;고유id로 직접 참조 or 색인을 통해 빠르게 탐색&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;정렬&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;tree구조라 삽입 시점에 정렬해둠&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;읽기 질의를 할때 유연하게 정렬&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td align=&quot;left&quot;&gt;질의어&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;명령형, 스키마 변경에 따라 질의어 변경필요&lt;/td&gt;
&lt;td align=&quot;left&quot;&gt;선언형&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;&lt;/table&gt;</description>
      <category>database</category>
      <category>cypher</category>
      <category>ddia</category>
      <category>graphdb</category>
      <category>gremlin</category>
      <category>mapreduce</category>
      <category>neo4j</category>
      <category>NoSQL</category>
      <category>RDB</category>
      <category>SPARQL</category>
      <category>SQL</category>
      <author>빨간색소년</author>
      <guid isPermaLink="true">https://sjh836.tistory.com/196</guid>
      <comments>https://sjh836.tistory.com/196#entry196comment</comments>
      <pubDate>Sun, 1 May 2022 18:20:43 +0900</pubDate>
    </item>
    <item>
      <title>Redis maxmemory (policy, samples, replica-ignore)</title>
      <link>https://sjh836.tistory.com/195</link>
      <description>&lt;p data-ke-size=&quot;size18&quot;&gt;redis 기본 개념 포스팅 : &lt;a href=&quot;https://sjh836.tistory.com/178&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://sjh836.tistory.com/178&lt;/a&gt;&lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;redis.png&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;206&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/RQk4Y/btrAgpj4tU8/uqbNKlwNzCmuKJgAQZNlC1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/RQk4Y/btrAgpj4tU8/uqbNKlwNzCmuKJgAQZNlC1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/RQk4Y/btrAgpj4tU8/uqbNKlwNzCmuKJgAQZNlC1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FRQk4Y%2FbtrAgpj4tU8%2FuqbNKlwNzCmuKJgAQZNlC1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;244&quot; height=&quot;206&quot; data-filename=&quot;redis.png&quot; data-origin-width=&quot;244&quot; data-origin-height=&quot;206&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;Redismaxmemory-maxmemory&quot; data-ke-size=&quot;size26&quot;&gt;maxmemory&lt;/h2&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://redis.io/docs/manual/config/&quot;&gt;https://redis.io/docs/manual/config/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;redis.conf 에서 초기설정 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;런타임에는&lt;span&gt;&amp;nbsp;&lt;/span&gt;CONFIG SET&lt;span&gt;&amp;nbsp;&lt;/span&gt;으로 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;32bit : 3GB&lt;/li&gt;
&lt;li&gt;64bit : 0 = 상한선없음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;RAM을 모두 사용하면, 디스크 swap 까지 사용&lt;/li&gt;
&lt;li&gt;swap 사용부터는 상당한 성능저하&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;redis cluster 에서도 동작은 한다.&lt;/li&gt;
&lt;li&gt;다만, 한 장비에 N개의 노드를 뛰울 경우, maxmemory 를 잘 생각해야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A 장비에 master 노드, slave 노드를 뛰울거면, &amp;divide;2 를 해야..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;RDB 에 스냅샷을 남기는 경우, BGSAVE 를 수행하는 스레드가 메모리를 사용할 수도 있으므로, 고려해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Redismaxmemory-maxmemory-policy&quot; data-ke-size=&quot;size26&quot;&gt;maxmemory-policy&lt;/h2&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://redis.io/docs/manual/eviction/&quot;&gt;https://redis.io/docs/manual/eviction/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;noeviction : 기존 데이터 삭제X. maxmemory 시, OOM 발생하며, 새 데이터 저장X&lt;/li&gt;
&lt;li&gt;allkeys-lru : LRU 로 삭제해서 공간확보 후 저장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LRU : 가장 오래 사용하지 않은 것&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;allkeys-lfu : LFU 로 삭제해서 공간확보 후 저장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;LFU : 가장 적게 사용한 것&lt;/li&gt;
&lt;li&gt;redis 4.0 부터 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;volatile-lru : ttl 이 설정되서 expire 대상 중에 LRU 로 삭제해서 공간확보 후 저장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;volatile 류는 대상이 없으면 noeviction 로 동작한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;volatile-lfu : ttl 이 설정되서 expire 대상 중에 LFU 로 삭제해서 공간확보 후 저장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;redis 4.0 부터 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;volatile-ttl : ttl 이 설정되서 expire 대상 중에 ttl 이 짧은 것부터 삭제해서 공간확보 후 저장&lt;/li&gt;
&lt;li&gt;allkeys-random : 랜덤삭제해서 공간확보 후 저장&lt;/li&gt;
&lt;li&gt;volatile-random : ttl 이 설정되서 expire 대상 중에 랜덤삭제해서 공간확보 후 저장&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Redismaxmemory-maxmemory-samples&quot; data-ke-size=&quot;size26&quot;&gt;maxmemory-samples&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;lru 의 정밀도라고 볼 수 있다.&lt;/li&gt;
&lt;li&gt;정밀할 수록 더 많은 비용이 들어가기 때문에, 적당한 값을 권장한다.&lt;/li&gt;
&lt;li&gt;5가 기본. 10은 정밀. 3은 빠르지만 상대적 비정밀&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;Redismaxmemory-replica-ignore-maxmemory&quot; data-ke-size=&quot;size26&quot;&gt;replica-ignore-maxmemory&lt;/h2&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://redis.io/docs/manual/replication/&quot;&gt;https://redis.io/docs/manual/replication/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;redis 5.0 부터, 복제 노드들은 maxmemory 설정을 기본적으로 무시한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;slave 라는걸 인지하고 무시하는건 아니고, write 가 master에서 처리되고 slave로 전달되니, 구조적으로 그렇다는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;master와 slave 노드가 서로 다른 maxmemory 값을 갖고 있으면 문제가 될 여지가 있다.&lt;/li&gt;
&lt;li&gt;따라서,&lt;span&gt;&amp;nbsp;&lt;/span&gt;replica-ignore-maxmemory yes&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 통해 예방이 가능하다.&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>database</category>
      <category>cache</category>
      <category>memory</category>
      <category>redis</category>
      <author>빨간색소년</author>
      <guid isPermaLink="true">https://sjh836.tistory.com/195</guid>
      <comments>https://sjh836.tistory.com/195#entry195comment</comments>
      <pubDate>Sun, 24 Apr 2022 03:08:43 +0900</pubDate>
    </item>
    <item>
      <title>[데이터 중심 애플리케이션 설계] 1장. 신뢰할 수 있고 확장 가능하며 유지보수하기 쉬운 어플리케이션</title>
      <link>https://sjh836.tistory.com/194</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;2&quot;&gt;참조문서
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;3&quot;&gt;&lt;a href=&quot;https://github.com/ept/ddia-references&quot;&gt;https://github.com/ept/ddia-references&lt;/a&gt;&lt;/li&gt;
&lt;li data-line=&quot;4&quot;&gt;데이터 중심 애플리케이션 설계(마틴 클레프만, 위키북스)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;데애설.jpg&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;1200&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bmb9xC/btrzDu0QkHK/aK2zd3qer1FH6BHI68YjNk/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bmb9xC/btrzDu0QkHK/aK2zd3qer1FH6BHI68YjNk/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bmb9xC/btrzDu0QkHK/aK2zd3qer1FH6BHI68YjNk/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbmb9xC%2FbtrzDu0QkHK%2FaK2zd3qer1FH6BHI68YjNk%2Fimg.jpg&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;262&quot; height=&quot;336&quot; data-filename=&quot;데애설.jpg&quot; data-origin-width=&quot;938&quot; data-origin-height=&quot;1200&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;&lt;span style=&quot;background-color: #ffffff; color: #666666;&quot;&gt;초심자의 눈으로 이해한 내용을 정리해보았다. 책에 있는 내용을 기반으로 썼지만, 책에 없는 내용도 조금씩 적어보았다. 책은 꼭 사서 보시길 바랍니다..&lt;/span&gt;&lt;/p&gt;
&lt;h2 id=&quot;1-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EC%8B%9C%EC%8A%A4%ED%85%9C&quot; data-line=&quot;6&quot; data-ke-size=&quot;size26&quot;&gt;1. 데이터 시스템&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;7&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;7&quot;&gt;어플리케이션은 계산 중심(compute-intensive)보다 데이터 중심(data-intensive)이 많다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;8&quot;&gt;계산에 쓰이는 CPU보다는 데이터 양, 데이터 복잡도 등이 더 문제다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;9&quot;&gt;데이터 중심 어플리케이션이 공통으로 필요로 할수 있는 기능들 : 데이터 시스템
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;10&quot;&gt;데이터베이스 : 영속성 스토리지&lt;/li&gt;
&lt;li data-line=&quot;11&quot;&gt;캐시 : 읽기 속도 향상, 값비싼 수행 결과 기억&lt;/li&gt;
&lt;li data-line=&quot;12&quot;&gt;검색 색인 : 사용자가 키워드로 데이터를 검색하거나 다양한 방법으로 필터링할 수 있게 제공&lt;/li&gt;
&lt;li data-line=&quot;13&quot;&gt;스트림 처리 : 비동기 처리를 위해 다른 프로세스로 메세지 보내기&lt;/li&gt;
&lt;li data-line=&quot;14&quot;&gt;일괄(batch) 처리 : 대량의 누적된 데이터를 주기 분석&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;15&quot;&gt;데이터 시스템의 범주들
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;16&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;16&quot;&gt;데이터베이스, 메세지큐, 캐시 등&lt;/li&gt;
&lt;li data-line=&quot;17&quot;&gt;최근에는 이 경계들이 허물어지고있다. ex) redis를 MQ로도 쓸 수 있음. kafka 도 지속성을 지원함&lt;/li&gt;
&lt;li data-line=&quot;18&quot;&gt;단일 시스템으로는 어플리케이션 니즈를 충족시킬 수 없음. 복합 데이터 시스템으로 흘러감&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;19&quot;&gt;개발자는 애플리케이션 개발자뿐 아니라 데이터 시스템 설계자이기도 하다.&lt;/li&gt;
&lt;li data-line=&quot;20&quot;&gt;개발자의 고민들
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;21&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;21&quot;&gt;데이터를 정확하고 완전하게 유지하려면 어떻게 해야할까?&lt;/li&gt;
&lt;li data-line=&quot;22&quot;&gt;시스템의 일부 성능이 저하되더라도 클라이언트에 일관되게 좋은 성능을 어떻게 제공할 수 있을까?&lt;/li&gt;
&lt;li data-line=&quot;23&quot;&gt;부하 증가를 다루기 위해 어떻게 규모를 확장할까?&lt;/li&gt;
&lt;li data-line=&quot;24&quot;&gt;서비스를 위해 좋은 API 는 어떤 건가?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EC%97%94%EC%A7%80%EB%8B%88%EC%96%B4%EB%A7%81%EC%9D%98-%EB%AA%A9%ED%91%9C&quot; data-line=&quot;26&quot; data-ke-size=&quot;size26&quot;&gt;2. 소프트웨어 엔지니어링의 목표&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;27&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;27&quot;&gt;신뢰성(Reliability) : sw결함, 휴먼에러, 재난같은 상황에서도 시스템은 올바르게 동작&lt;/li&gt;
&lt;li data-line=&quot;28&quot;&gt;확장성(Scalability) : 트래픽이 증가해도 대응가능&lt;/li&gt;
&lt;li data-line=&quot;29&quot;&gt;유지보수성(Maintainability) : 모든 개발,운영자가 생산적으로 작업할 수 있어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3-%EC%8B%A0%EB%A2%B0%EC%84%B1&quot; data-line=&quot;31&quot; data-ke-size=&quot;size26&quot;&gt;3. 신뢰성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;32&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;32&quot;&gt;결함(fault)을 예측하고 대처할 수 있는 시스템을 내결함성(fault-tolerant) 또는 탄력성(resilient) 있다고 함
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;33&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;33&quot;&gt;결함 : 사양에 벗어난 시스템의 구성 요소&lt;/li&gt;
&lt;li data-line=&quot;34&quot;&gt;장애 : 유저에게 서비스를 제공하지 못하고 시스템이 멈춤&lt;/li&gt;
&lt;li data-line=&quot;35&quot;&gt;결함으로 인해 장애가 발생하지 않도록 내결함성 구조로 설계해야함&lt;/li&gt;
&lt;li data-line=&quot;36&quot;&gt;결함을 0으로 줄일 수는 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;37&quot;&gt;카오스 몽키(Chaos Monkey) : 고의적으로 결함을 유도하고 내결함성 시스템을 지속적으로 훈련하고 테스트
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;38&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;38&quot;&gt;&lt;a href=&quot;https://github.com/netflix/chaosmonkey&quot;&gt;https://github.com/netflix/chaosmonkey&lt;/a&gt;&lt;/li&gt;
&lt;li data-line=&quot;39&quot;&gt;cf) 내결함성을 쉽게 테스트할 수 있는 spring 지원 구현체 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://codecentric.github.io/chaos-monkey-spring-boot&quot;&gt;https://codecentric.github.io/chaos-monkey-spring-boot&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-1-%ED%95%98%EB%93%9C%EC%9B%A8%EC%96%B4-%EA%B2%B0%ED%95%A8&quot; data-line=&quot;41&quot; data-ke-size=&quot;size23&quot;&gt;3-1. 하드웨어 결함&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;42&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;42&quot;&gt;시스템 장애의 대부분의 원인 : 디스크, 램 고장. 대규모 정전, 담당자가 장비를 잘못 다룸&lt;/li&gt;
&lt;li data-line=&quot;43&quot;&gt;대응 방안 : 하드웨어 구성에 중복(redundancy)을 추가
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;44&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;44&quot;&gt;디스크 : RAID&lt;/li&gt;
&lt;li data-line=&quot;45&quot;&gt;서버 : 이중 전원 디바이스, hot-swap CPU&lt;/li&gt;
&lt;li data-line=&quot;46&quot;&gt;IDC : 예비용 전원 발전기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;47&quot;&gt;애초부터 내결함성과 탄력성을 잘 고려해서 설계해라
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;48&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;48&quot;&gt;ex) aws 의 가상장비들은 언제나 죽는걸 가정하고 설게했다.&lt;/li&gt;
&lt;li data-line=&quot;49&quot;&gt;이렇게 고려된 시스템은 재부팅도 쉽다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-2-%EC%86%8C%ED%94%84%ED%8A%B8%EC%9B%A8%EC%96%B4-%EA%B2%B0%ED%95%A8&quot; data-line=&quot;51&quot; data-ke-size=&quot;size23&quot;&gt;3-2. 소프트웨어 결함&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;52&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;52&quot;&gt;systematic error 는 더 예상하기 어렵고 오류를 더욱 많이 유발한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;53&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;53&quot;&gt;ex) 리눅스 커널 윤초 버그로 레딧, 링크드인 등이 전부 장애남&lt;/li&gt;
&lt;li data-line=&quot;53&quot;&gt;&lt;a href=&quot;https://engineering.vcnc.co.kr/2016/12/struggling-with-the-leap-second/&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://engineering.vcnc.co.kr/2016/12/struggling-with-the-leap-second/&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;54&quot;&gt;특정 상황에 의해 발생하기 전까지 나타나지 않을수도 있다.&lt;/li&gt;
&lt;li data-line=&quot;55&quot;&gt;명확한 해결책은 없다. 빈틈없는 테스트, 프로세스 격리, 모니터링, 알람체계를 잘 갖춰라&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;3-3-%EC%9D%B8%EC%A0%81-%EC%98%A4%EB%A5%98human-error&quot; data-line=&quot;57&quot; data-ke-size=&quot;size23&quot;&gt;3-3. 인적 오류(human error)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;58&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;58&quot;&gt;대응방안
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;59&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;59&quot;&gt;잘 설계된 추상화 API, 인터페이스를 사용하라. 옳은 일은 쉽게하고 잘못된 일을 막을 수 있다.&lt;/li&gt;
&lt;li data-line=&quot;60&quot;&gt;실수가 잦게 발생하는 곳은 실제 데이터로 안전하게 실험해볼 수 있는 sandbox 를 제공하라 (ex. stage환경)&lt;/li&gt;
&lt;li data-line=&quot;61&quot;&gt;단위테스트, 통합테스트, 수동테스트를 철저히 해라. 특히 코너케이스에 유의&lt;/li&gt;
&lt;li data-line=&quot;62&quot;&gt;장애를 빠르게 복구 할 수 있게 도구를 만들어라&lt;/li&gt;
&lt;li data-line=&quot;63&quot;&gt;모니터링이 중요하다. 지표를 분석해라&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4-%ED%99%95%EC%9E%A5%EC%84%B1&quot; data-line=&quot;65&quot; data-ke-size=&quot;size26&quot;&gt;4. 확장성&lt;/h2&gt;
&lt;h3 id=&quot;4-1-%EB%B6%80%ED%95%98-%EB%A7%A4%EA%B0%9C%EB%B3%80%EC%88%98&quot; data-line=&quot;66&quot; data-ke-size=&quot;size23&quot;&gt;4-1. 부하 매개변수&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;67&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;67&quot;&gt;부하 성장 질문
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;68&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;68&quot;&gt;시스템이 특정 방식으로 커지면 어떤 선택지들이 있는가?&lt;/li&gt;
&lt;li data-line=&quot;69&quot;&gt;추가 부하를 다루기 위해 계산 자원을 어떻게 투입할까?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;70&quot;&gt;부하 매개변수 : 이걸 정의해야 부하 성장 질문을 논의할수 있다. 기준이 생기기 때문이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;71&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;71&quot;&gt;각 시스템 설계에 따라 달라진다.&lt;/li&gt;
&lt;li data-line=&quot;72&quot;&gt;ex) rps, qps, active user, cache hit ratio 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;73&quot;&gt;예시) 트위터 타임라인 아키텍처
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;74&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;74&quot;&gt;버전1 : 트윗을 쓰면 테이블에 전역적으로 insert. 각 유저는 타임라인 접근 시점에 join
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;75&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;75&quot;&gt;SELECT tweets.*, users.* FROM tweets JOIN users ON tweets.sender_id = users.id JOIN follows ON follows.followee_id = users.id WHERE follows.follower_id = current_user&lt;/li&gt;
&lt;li data-line=&quot;76&quot;&gt;쉽고 빠른 쓰기 + 쉽지만 느린 읽기&lt;/li&gt;
&lt;/ul&gt;
&lt;img src=&quot;https://blog.kakaocdn.net/dn/bpqlo2/btrzytgLrl4/eiIQweDm9oDSWWh1qgkFxK/img.png&quot; width=&quot;516&quot; height=&quot;188&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/bpqlo2/btrzytgLrl4/eiIQweDm9oDSWWh1qgkFxK/img.png&quot; data-filename=&quot;트위터_타임라인_관계형스키마.png&quot; data-origin-width=&quot;979&quot; data-origin-height=&quot;357&quot; /&gt;&lt;/li&gt;
&lt;li data-line=&quot;77&quot;&gt;버전2 : 각 유저별 트윗 우편함에 insert. 각 유저는 타임라인 접근 시, 자신의 트윗 우편함만 읽으면 됨
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;78&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;78&quot;&gt;어려운 쓰기 + 쉽고 빠른 읽기&lt;/li&gt;
&lt;li data-line=&quot;79&quot;&gt;한계 : 인플루언서가 수천만 팔로워를 갖고있다면 쓰기가 너무 비대해지고 어려워짐&lt;/li&gt;
&lt;/ul&gt;
&lt;img src=&quot;https://blog.kakaocdn.net/dn/c1U5FC/btrzDPwza0a/ub4fPTmOlvb9NTTBa9xpbk/img.png&quot; width=&quot;592&quot; height=&quot;206&quot; data-image-src=&quot;https://blog.kakaocdn.net/dn/c1U5FC/btrzDPwza0a/ub4fPTmOlvb9NTTBa9xpbk/img.png&quot; data-filename=&quot;트위터_팬아웃_데이터_파이프라인.png&quot; data-origin-width=&quot;973&quot; data-origin-height=&quot;339&quot; /&gt;&lt;/li&gt;
&lt;li data-line=&quot;80&quot;&gt;버전3 : 인플루언서는 2방식에서 제외. 1방식으로 전환&lt;/li&gt;
&lt;li data-line=&quot;81&quot;&gt;트위터의 부하 매개변수 : 팬아웃 -&amp;gt; 사용자당 팔로워의 분포, 빈도&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-2-%EC%84%B1%EB%8A%A5-%EA%B8%B0%EC%88%A0%ED%95%98%EA%B8%B0&quot; data-line=&quot;83&quot; data-ke-size=&quot;size23&quot;&gt;4-2. 성능 기술하기&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;84&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;84&quot;&gt;하둡같은 일괄 처리 시스템은 처리량에 관심이 있지만, 서비스들은 응답시간에 관심이 있다.&lt;/li&gt;
&lt;li data-line=&quot;85&quot;&gt;응답시간은 요청마다 매번 다르다. 따라서, 분포로 생각해야한다.&lt;/li&gt;
&lt;li data-line=&quot;86&quot;&gt;대부분의 요청은 빠르지만, 가끔 오래걸리는 특이점(outlier)이 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;87&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;87&quot;&gt;컨텍스트 스위칭, 네트워크 패킷손실 - TCP재전송, GC, page fault 등 다양한 이유가 있겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;88&quot;&gt;응답시간 분석에 백분위를 사용하고, 중앙값(p50)을 지표로 삼아라
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;89&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;89&quot;&gt;특이점 = p95, p99, p999&lt;/li&gt;
&lt;li data-line=&quot;90&quot;&gt;p9999부터는 최적화 비용이 너무 많이들어서 이점이 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;91&quot;&gt;특이점도 무시할 순 없다. 보통 데이터가 많기 때문에, 발생할 수 있는데, 중요한 고객이기 때문이다.&lt;/li&gt;
&lt;li data-line=&quot;92&quot;&gt;SLO(서비스 수준목표. Service Level Objective)와 SLA(서비스 수준 협약서. Service Level Agreement) 가 이럴때 자주 등장함
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;93&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;93&quot;&gt;ex) 환불의 기준&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;94&quot;&gt;큐 대기 지연은 응답시간의 상당 부분을 차지한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;95&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;95&quot;&gt;선두 차단(head-of-line blocking) : 서버는 병렬로 소수 작업만 처리할 수 있기때문에, 첫 요청을 처리하는데 느려진다면 후속 요청이 모두 영향받는다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;96&quot;&gt;시스템에 인위적으로 부하를 생성하는 경우, 클라이언트는 응답시간과 독립적으로 요청을 계속 보내야한다. 응답을 기다리고 다음 요청을 보내면 평가가 의미없다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;97&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;97&quot;&gt;cf) nGrinder 같은 전문 부하테스트기 대신 개발자가 부하 시뮬레이터를 만들 경우 고려해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;98&quot;&gt;꼬리 지연 증폭(tail latency amplification) : 여러 API를 호출했을 때, 적은 API만 느려도 전체가 느린게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;4-3-%EB%8C%80%EC%9D%91-%EB%B0%A9%EC%95%88&quot; data-line=&quot;100&quot; data-ke-size=&quot;size23&quot;&gt;4-3. 대응 방안&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;101&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;101&quot;&gt;용량확장=수직확장=scaling up 과 규모확장=수평확장=scaling out 사이에서 실용적인 조합을 찾아야한다.&lt;/li&gt;
&lt;li data-line=&quot;102&quot;&gt;스케일아웃이 항상 좋지는 않다. 적절히 스케일업된 장비들로 구성된게 더 좋을 때도 있다.&lt;/li&gt;
&lt;li data-line=&quot;103&quot;&gt;탄력적(elastic) : 부하 증가를 감지하면 자동으로 자원 추가
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;104&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;104&quot;&gt;이것도 항상 좋지는 않다. 수동 확장이 더 간단하고 예상치 못한 운영이슈가 적다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;105&quot;&gt;분산환경이 필요해질때 까진 스케일업하는게 보통이지만, 요새는 분산시스템과 추상화가 워낙 좋아져서, 처음부터 고려해볼만도 하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;5-%EC%9C%A0%EC%A7%80%EB%B3%B4%EC%88%98%EC%84%B1&quot; data-line=&quot;107&quot; data-ke-size=&quot;size26&quot;&gt;5. 유지보수성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;108&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;108&quot;&gt;많은 사람들은 레거시 시스템을 유지보수하는 것을 좋아하지 않는다.&lt;/li&gt;
&lt;li data-line=&quot;109&quot;&gt;처음부터 레거시를 만들지 않게 끔 설계해야한다.&lt;/li&gt;
&lt;li data-line=&quot;110&quot;&gt;유지보수성 원칙
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;111&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;111&quot;&gt;운용성 : 운영팀이 원활하게 운영할 수 있도록 쉽게 만들어라&lt;/li&gt;
&lt;li data-line=&quot;112&quot;&gt;단순성 : 복잡도를 최대한 제거해 새로운 팀원도 잘 이해할 수 있게 만들어라&lt;/li&gt;
&lt;li data-line=&quot;113&quot;&gt;발전성 : 이후 변경에 열려있어야 한다. 요구사항 변경같은 새로운 사용사례를 쉽게 적용할 수 있어야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;114&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;114&quot;&gt;유연성, 수정가능성, 적응성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;5-1-%EC%9A%B4%EC%9A%A9%EC%84%B1&quot; data-line=&quot;116&quot; data-ke-size=&quot;size23&quot;&gt;5-1. 운용성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;117&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;117&quot;&gt;운영 중 일부 측면은 자동화할 수 있고, 자동화해야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;118&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;118&quot;&gt;하지만 자동화를 처음 설정하고 제대로 동작하는 지 확인하는 것도 여전히 사람이 해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;119&quot;&gt;좋은 운영성 : 반복되는 태스크들을 쉽게 수행할 수 있도록 하는 것
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;120&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;120&quot;&gt;런타임 동작과 시스템 내부에 대한 가시성 제공&lt;/li&gt;
&lt;li data-line=&quot;121&quot;&gt;좋은 문서&lt;/li&gt;
&lt;li data-line=&quot;122&quot;&gt;이해하기 쉬운 운영모델 : X를 하면 Y가 발생&lt;/li&gt;
&lt;li data-line=&quot;123&quot;&gt;어드민 등으로 기본값을 재정의할 수 있는 권한 부여&lt;/li&gt;
&lt;li data-line=&quot;124&quot;&gt;자기 회복이 가능할 뿐아니라 관리자가 상태를 수동으로 제어할 수 있어야 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;5-2-%EB%8B%A8%EC%88%9C%EC%84%B1&quot; data-line=&quot;126&quot; data-ke-size=&quot;size23&quot;&gt;5-2. 단순성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;127&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;127&quot;&gt;시스템이 복잡해지면 모든 사람의 진행을 느리게하고, 유지보수 비용이 증가한다.&lt;/li&gt;
&lt;li data-line=&quot;128&quot;&gt;복잡성의 사례들
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;129&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;129&quot;&gt;상태 공간의 금증&lt;/li&gt;
&lt;li data-line=&quot;130&quot;&gt;모듈 간 강결합성&lt;/li&gt;
&lt;li data-line=&quot;131&quot;&gt;일관성 없는 네이밍&lt;/li&gt;
&lt;li data-line=&quot;132&quot;&gt;임시방편 코드들&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;133&quot;&gt;우발적 복잡도(accidental complexity)를 제거하기 위한 최상의 도구는 '추상화' 다
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;134&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;134&quot;&gt;좋은 추상화는 깔끔하고 직관적인 외관 아래로 세부 구현을 숨길 수 있다.&lt;/li&gt;
&lt;li data-line=&quot;135&quot;&gt;재사용성이 좋아진다.&lt;/li&gt;
&lt;li data-line=&quot;136&quot;&gt;ex) 기계어, cpu레지스터, 시스템콜들을 추상화한 것이 고수준 프로그래밍언어다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;5-3-%EB%B0%9C%EC%A0%84%EC%84%B1&quot; data-line=&quot;138&quot; data-ke-size=&quot;size23&quot;&gt;5-3. 발전성&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;139&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;139&quot;&gt;시스템 요구사항은 계속 바뀐다.&lt;/li&gt;
&lt;li data-line=&quot;140&quot;&gt;애자일(agile) 작업 패턴은 변화에 적응하기 위한 프로세스이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;141&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;141&quot;&gt;TDD, 리팩토링 등&lt;/li&gt;
&lt;li data-line=&quot;142&quot;&gt;작고 로컬규모에 초점을 맞춰라&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>database</category>
      <category>chaosmonkey</category>
      <category>ddia</category>
      <author>빨간색소년</author>
      <guid isPermaLink="true">https://sjh836.tistory.com/194</guid>
      <comments>https://sjh836.tistory.com/194#entry194comment</comments>
      <pubDate>Mon, 18 Apr 2022 03:06:08 +0900</pubDate>
    </item>
    <item>
      <title>Flask 기초</title>
      <link>https://sjh836.tistory.com/193</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;2&quot;&gt;참조문서
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;3&quot;&gt;&lt;a href=&quot;https://flask-docs-kr.readthedocs.io/ko/latest/quickstart.html&quot;&gt;https://flask-docs-kr.readthedocs.io/ko/latest/quickstart.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;flask.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;400&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cgu3vm/btrr4QxkchU/aopvXYSwCkyzUIOkOjlks0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cgu3vm/btrr4QxkchU/aopvXYSwCkyzUIOkOjlks0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cgu3vm/btrr4QxkchU/aopvXYSwCkyzUIOkOjlks0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fcgu3vm%2Fbtrr4QxkchU%2FaopvXYSwCkyzUIOkOjlks0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;381&quot; height=&quot;238&quot; data-filename=&quot;flask.png&quot; data-origin-width=&quot;640&quot; data-origin-height=&quot;400&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-line=&quot;5&quot; data-ke-size=&quot;size14&quot;&gt;flask 를 간단히 쓸 용도로, 공식문서를 보고 대강 메모해두었다.&lt;/p&gt;
&lt;h2 id=&quot;1-%EC%8B%9C%EC%9E%91%ED%95%98%EA%B8%B0&quot; data-line=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;1. 시작하기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;6&quot;&gt;설치 : pip install flask&lt;/li&gt;
&lt;li data-line=&quot;7&quot;&gt;hello world : app.py
&lt;div&gt;
&lt;pre id=&quot;code_1643786739631&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;from flask import Flask
app = Flask(__name__)

@app.route('/')
def home():
    return 'This is Home!'

if __name__ == '__main__':  
    app.run('0.0.0.0',port=5000,debug=True)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2-%EB%9D%BC%EC%9A%B0%ED%84%B0&quot; data-line=&quot;20&quot; data-ke-size=&quot;size26&quot;&gt;2. 라우터&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;21&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;21&quot;&gt;라우터에서 인자 받기
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;22&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;22&quot;&gt;GET :&lt;span&gt;&amp;nbsp;&lt;/span&gt;request.args.get(매개변수명)&lt;/li&gt;
&lt;li data-line=&quot;23&quot;&gt;POST :&lt;span&gt;&amp;nbsp;&lt;/span&gt;request.form[매개변수명]&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;24&quot;&gt;라우터에서 path 받기
&lt;div&gt;
&lt;pre id=&quot;code_1643786754763&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@app.route('/post/&amp;lt;int:post_id&amp;gt;')
def show_post(post_id):
    수행문&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-line=&quot;30&quot;&gt;json 반환 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;return jsonify({'result':'success', 'data': '...'})&lt;/li&gt;
&lt;li data-line=&quot;31&quot;&gt;url 반환 : url_for 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;32&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;32&quot;&gt;컨텍스트에 따라 상대경로를 알아서 생성해줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;33&quot;&gt;메소드 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;@app.route('/login', methods=['GET', 'POST'])&lt;/li&gt;
&lt;li data-line=&quot;34&quot;&gt;정적 파일 : static 이 기본값으로 특수설정 되어있음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;35&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;35&quot;&gt;url_for('static', filename='style.css')&lt;/li&gt;
&lt;li data-line=&quot;36&quot;&gt;템플릿에선&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;img src=&quot;{{ url_for('static', filename='rome.jpg') }}&quot;/&amp;gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;37&quot;&gt;리다이렉트 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;redirect(url_for('login'))&lt;/li&gt;
&lt;li data-line=&quot;38&quot;&gt;응답코드별 에러핸들러 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;@app.errorhandler(404) def page_not_found(error):&lt;/li&gt;
&lt;li data-line=&quot;39&quot;&gt;응답 반환 포맷 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;(response, status, headers)
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;40&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;40&quot;&gt;문자열이면 알아서 text/html 로 된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;41&quot;&gt;요청 전,후 interceptor :&lt;span&gt;&amp;nbsp;&lt;/span&gt;@app.before_request,&lt;span&gt;&amp;nbsp;&lt;/span&gt;@app.teardown_request
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;42&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;42&quot;&gt;하나의 요청 내 글로벌 변수인&lt;span&gt;&amp;nbsp;&lt;/span&gt;flask.g&lt;span&gt;&amp;nbsp;&lt;/span&gt;에 보통 바인딩해서 씀&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3-%EB%A1%9C%EA%B9%85&quot; data-line=&quot;44&quot; data-ke-size=&quot;size26&quot;&gt;3. 로깅&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;45&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;45&quot;&gt;기본 로거 사용
&lt;div&gt;
&lt;pre id=&quot;code_1643786769721&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;app.logger.debug('A value for debugging')
app.logger.warning('A warning occurred (%d apples)', 42)
app.logger.error('An error occurred')&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4-%ED%85%9C%ED%94%8C%EB%A6%BF-%EC%97%94%EC%A7%84&quot; data-line=&quot;52&quot; data-ke-size=&quot;size26&quot;&gt;4. 템플릿 엔진&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;53&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;53&quot;&gt;autoescaping 된다
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;54&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;54&quot;&gt;unescape 하려면&lt;span&gt;&amp;nbsp;&lt;/span&gt;{% autoescape false %} ... {% endautoescape %}&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;55&quot;&gt;템플릿으로 데이터 전달 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;return render_template('index.html', key = value)&lt;/li&gt;
&lt;li data-line=&quot;56&quot;&gt;템플릿에서 데이터 출력 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;div&amp;gt;{{key}}&amp;lt;/div&amp;gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;57&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;57&quot;&gt;문장 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;{% ... %}&lt;/li&gt;
&lt;li data-line=&quot;58&quot;&gt;표현식 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;{{ ... }}&lt;/li&gt;
&lt;li data-line=&quot;59&quot;&gt;주석 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;{# ... #}&lt;/li&gt;
&lt;li data-line=&quot;60&quot;&gt;라인문장? :&lt;span&gt;&amp;nbsp;&lt;/span&gt;# ... ##&lt;/li&gt;
&lt;li data-line=&quot;61&quot;&gt;반복문 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;{% for i in data %} {{ loop.index }}. {{ i }} {% endfor %}&lt;/li&gt;
&lt;li data-line=&quot;62&quot;&gt;조건문 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;{% if name %} ... {% else %} ... {% endif %}&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;63&quot;&gt;템플릿 상속 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://wikidocs.net/81051&quot;&gt;https://wikidocs.net/81051&lt;/a&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1643786806669&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;&amp;lt;!-- 부모 --&amp;gt;
&amp;lt;!doctype html&amp;gt;
&amp;lt;html&amp;gt;
&amp;lt;head&amp;gt;
    {% block head %}
    &amp;lt;link rel=&quot;stylesheet&quot; href=&quot;{{ url_for('static', filename='style.css') }}&quot;&amp;gt;
    &amp;lt;title&amp;gt;{% block title %}{% endblock %} - My Webpage&amp;lt;/title&amp;gt;
    {% endblock %}
&amp;lt;/head&amp;gt;
&amp;lt;body&amp;gt;
&amp;lt;div id=&quot;content&quot;&amp;gt;{% block content %}{% endblock %}&amp;lt;/div&amp;gt;
&amp;lt;div id=&quot;footer&quot;&amp;gt;
    {% block footer %}
    &amp;amp;copy; Copyright 2010 by &amp;lt;a href=&quot;http://domain.invalid/&quot;&amp;gt;you&amp;lt;/a&amp;gt;.
    {% endblock %}
&amp;lt;/div&amp;gt;
&amp;lt;/body&amp;gt;

&amp;lt;!-- 자식 --&amp;gt;
{% extends &quot;layout.html&quot; %}
{% block title %}Index{% endblock %}
{% block head %}
{{ super() }}
&amp;lt;style type=&quot;text/css&quot;&amp;gt;
    .important { color: #336699; }
&amp;lt;/style&amp;gt;
{% endblock %}
{% block content %}
&amp;lt;h1&amp;gt;Index&amp;lt;/h1&amp;gt;
&amp;lt;p class=&quot;important&quot;&amp;gt;
    Welcome on my awesome homepage.
{% endblock %}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-line=&quot;98&quot;&gt;custom filter
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;99&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;99&quot;&gt;등록 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;@app.template_filter('필터명') def 함수명(매개변수):&lt;/li&gt;
&lt;li data-line=&quot;100&quot;&gt;사용 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;{% for x in mylist | reverse %}&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;101&quot;&gt;context processor
&lt;div&gt;
&lt;pre id=&quot;code_1643786823043&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# .py
@app.context_processor
def utility_processor():
    def format_price(amount, currency=u'&amp;euro;'):
        return u'{0:.2f}{1}'.format(amount, currency)
    return dict(format_price=format_price)

# 템플릿
{{ format_price(0.33) }}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;5-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98-%ED%99%98%EA%B2%BD%EB%B3%84-%EC%84%A4%EC%A0%95-%EB%B0%A9%EC%8B%9D&quot; data-line=&quot;114&quot; data-ke-size=&quot;size26&quot;&gt;5. 어플리케이션 환경별 설정 방식&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;115&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;115&quot;&gt;class 상속 활용
&lt;div&gt;
&lt;pre id=&quot;code_1643786836647&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Config(object):
    DEBUG = False
    TESTING = False
    DATABASE_URI = 'sqlite://:memory:'

class ProductionConfig(Config):
    DATABASE_URI = 'mysql://user@localhost/foo'

class DevelopmentConfig(Config):
    DEBUG = True

class TestingConfig(Config):
    TESTING = True

config_by_name = dict(
    dev=DevelopmentConfig,
    test=TestingConfig,
    prod=ProductionConfig
)&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-line=&quot;137&quot;&gt;os.getenv(&quot;ENV&quot;)&lt;span&gt;&amp;nbsp;&lt;/span&gt;와 같이 가져와서 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;6-%EC%BA%90%EC%8B%B1&quot; data-line=&quot;139&quot; data-ke-size=&quot;size26&quot;&gt;6. 캐싱&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;140&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;140&quot;&gt;&lt;a href=&quot;https://flask-docs-kr.readthedocs.io/ko/latest/patterns/caching.html&quot;&gt;https://flask-docs-kr.readthedocs.io/ko/latest/patterns/caching.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>python</category>
      <category>flask</category>
      <category>Python</category>
      <author>빨간색소년</author>
      <guid isPermaLink="true">https://sjh836.tistory.com/193</guid>
      <comments>https://sjh836.tistory.com/193#entry193comment</comments>
      <pubDate>Wed, 2 Feb 2022 16:29:38 +0900</pubDate>
    </item>
    <item>
      <title>파이썬의 기초 (개발환경 세팅, 문법)</title>
      <link>https://sjh836.tistory.com/192</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;2&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;2&quot;&gt;참조문서
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;3&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;3&quot;&gt;점프 투 파이썬 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://wikidocs.net/book/1&quot;&gt;https://wikidocs.net/book/1&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;logo-python.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;675&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bL8ETY/btrsc1dKdvU/PKl3b1kLTKsKuWAY9u2XT1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bL8ETY/btrsc1dKdvU/PKl3b1kLTKsKuWAY9u2XT1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bL8ETY/btrsc1dKdvU/PKl3b1kLTKsKuWAY9u2XT1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbL8ETY%2Fbtrsc1dKdvU%2FPKl3b1kLTKsKuWAY9u2XT1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;366&quot; height=&quot;206&quot; data-filename=&quot;logo-python.png&quot; data-origin-width=&quot;1200&quot; data-origin-height=&quot;675&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;p data-line=&quot;5&quot; data-ke-size=&quot;size14&quot;&gt;다른 언어에 어느정도 다룰 수 있는 상태라면, 아래만 봐도 파이썬을 이해하는데 크게 문제는 없을 정도로만 정리했다.&lt;/p&gt;
&lt;h2 id=&quot;0-%EA%B0%9C%EB%B0%9C%ED%99%98%EA%B2%BD-%EC%84%B8%ED%8C%85&quot; data-line=&quot;5&quot; data-ke-size=&quot;size26&quot;&gt;0. 개발환경 세팅&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;6&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;6&quot;&gt;window10 기준&lt;/li&gt;
&lt;li data-line=&quot;7&quot;&gt;pyenv 설치 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://github.com/pyenv-win/pyenv-win&quot;&gt;https://github.com/pyenv-win/pyenv-win&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;8&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;8&quot;&gt;여러 파이썬 버전을 설치하고 관리해줌. nodejs의 nvm 같은 것&lt;/li&gt;
&lt;li data-line=&quot;9&quot;&gt;환경변수 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;10&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;10&quot;&gt;PYENV,&lt;span&gt;&amp;nbsp;&lt;/span&gt;PYENV_ROOT,&lt;span&gt;&amp;nbsp;&lt;/span&gt;PYENV_HOME&lt;span&gt;&amp;nbsp;&lt;/span&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;%USERPROFILE%\.pyenv\pyenv-win&lt;/li&gt;
&lt;li data-line=&quot;11&quot;&gt;PATH&lt;span&gt;&amp;nbsp;&lt;/span&gt;+=&lt;span&gt;&amp;nbsp;&lt;/span&gt;%USERPROFILE%\.pyenv\pyenv-win\bin,&lt;span&gt;&amp;nbsp;&lt;/span&gt;%USERPROFILE%\.pyenv\pyenv-win\shims&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;12&quot;&gt;pyenv rehash&lt;/li&gt;
&lt;li data-line=&quot;13&quot;&gt;pyenv install --list 하면 최신버전이 안오미. pyenv update 해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;14&quot;&gt;가상환경 venv
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;15&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;15&quot;&gt;virtualenv 는 pip로 별도 설치해야하나, venv 는 3.3부터 기본 라이브러리로 포함&lt;/li&gt;
&lt;li data-line=&quot;16&quot;&gt;venv는 가상환경을 파이썬 버전별로 임의로 만들 수 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1643693887210&quot; class=&quot;html xml&quot; data-ke-language=&quot;html&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;cd 프로젝트 디렉터리
python -m venv .venv # .venv는 자유롭게 이름설정
.venv\Scripts\activate # 가상환경 활성화
.venv\Scripts\deactivate # 비활성화&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;1-%EB%B3%80%EC%88%98&quot; data-line=&quot;24&quot; data-ke-size=&quot;size26&quot;&gt;1. 변수&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;25&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;25&quot;&gt;서로 다른 형끼리 덧셈하면 TypeError 발생
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;26&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;26&quot;&gt;3 + &quot;hi&quot; =&amp;gt; str(3) + &quot;hi&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;27&quot;&gt;자료형 확인 : type(a)&lt;/li&gt;
&lt;li data-line=&quot;28&quot;&gt;메모리 주소 확인 : id(a)&lt;/li&gt;
&lt;li data-line=&quot;29&quot;&gt;동일한 인스턴스 확인 : a is b&lt;/li&gt;
&lt;li data-line=&quot;30&quot;&gt;여러 할당
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;31&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;31&quot;&gt;a, b = 'python', 'life'&lt;/li&gt;
&lt;li data-line=&quot;32&quot;&gt;튜플이라 괄호 생략 가능&lt;/li&gt;
&lt;li data-line=&quot;33&quot;&gt;리스트도 가능 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;[a, b] = 'python', 'life'&lt;/li&gt;
&lt;li data-line=&quot;34&quot;&gt;swap :&lt;span&gt;&amp;nbsp;&lt;/span&gt;a, b = b, a&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-1-%EC%88%AB%EC%9E%90%ED%98%95&quot; data-line=&quot;36&quot; data-ke-size=&quot;size23&quot;&gt;1-1. 숫자형&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;37&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;37&quot;&gt;정수형 : a = 123&lt;/li&gt;
&lt;li data-line=&quot;38&quot;&gt;실수형 : a = 1.2&lt;/li&gt;
&lt;li data-line=&quot;39&quot;&gt;8진수, 16진수 : a = 0o177&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;1-1-1-%EC%82%AC%EC%B9%99%EC%97%B0%EC%82%B0&quot; data-line=&quot;41&quot; data-ke-size=&quot;size20&quot;&gt;1-1-1. 사칙연산&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;42&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;42&quot;&gt;덧셈 : +&lt;/li&gt;
&lt;li data-line=&quot;43&quot;&gt;뺄셈 : -&lt;/li&gt;
&lt;li data-line=&quot;44&quot;&gt;곱셈 : *&lt;/li&gt;
&lt;li data-line=&quot;45&quot;&gt;제곱 : **
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;46&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;46&quot;&gt;ex) 3 ** 4 = 3의 4제곱 = 81&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;47&quot;&gt;나눗셈
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;48&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;48&quot;&gt;소수로 표현 : /&lt;/li&gt;
&lt;li data-line=&quot;49&quot;&gt;몫만 반환 : //&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;50&quot;&gt;나머지 : %&lt;/li&gt;
&lt;li data-line=&quot;51&quot;&gt;몫과 나머지를 한번에 : divmod(50, 8)&lt;/li&gt;
&lt;li data-line=&quot;52&quot;&gt;대입연산 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;53&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;53&quot;&gt;ex) *=&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-2-%EB%AC%B8%EC%9E%90%EC%97%B4&quot; data-line=&quot;55&quot; data-ke-size=&quot;size23&quot;&gt;1-2. 문자열&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;56&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;56&quot;&gt;형태
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;57&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;57&quot;&gt;&quot;문자열&quot;&lt;/li&gt;
&lt;li data-line=&quot;58&quot;&gt;'문자열'&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;59&quot;&gt;여러줄 형태
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;60&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;60&quot;&gt;\n 으로 해도 되지만 길어짐. 가독성 떨어짐&lt;/li&gt;
&lt;li data-line=&quot;61&quot;&gt;&quot;&quot;&quot;문자열&quot;&quot;&quot;&lt;/li&gt;
&lt;li data-line=&quot;62&quot;&gt;'''문자열'''&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;63&quot;&gt;escape : \&lt;/li&gt;
&lt;li data-line=&quot;64&quot;&gt;문자열의 요소값은 바꿀 수 없음. 불변이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;1-2-1-%EB%AC%B8%EC%9E%90%EC%97%B4-%EC%97%B0%EC%82%B0&quot; data-line=&quot;66&quot; data-ke-size=&quot;size20&quot;&gt;1-2-1. 문자열 연산&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;67&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;67&quot;&gt;연결 : a + b&lt;/li&gt;
&lt;li data-line=&quot;68&quot;&gt;반복 : a * 2&lt;/li&gt;
&lt;li data-line=&quot;69&quot;&gt;길이 : len(a)&lt;/li&gt;
&lt;li data-line=&quot;70&quot;&gt;인덱스로 접근 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;a[0]
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;71&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;71&quot;&gt;거꾸로 접근&lt;span&gt;&amp;nbsp;&lt;/span&gt;a[-1]&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 뒤에서 첫번째 단어&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;72&quot;&gt;인덱스로 자르기 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;a[0:4]&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 끝번호는 포함하지 않음
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;73&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;73&quot;&gt;끝번호 생략하면 끝까지 자른다&lt;/li&gt;
&lt;li data-line=&quot;74&quot;&gt;앞번호도 마찬가지&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;1-2-2-%EB%AC%B8%EC%9E%90%EC%97%B4-%ED%8F%AC%EB%A7%A4%ED%8C%85&quot; data-line=&quot;76&quot; data-ke-size=&quot;size20&quot;&gt;1-2-2. 문자열 포매팅&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;77&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;77&quot;&gt;포맷 코드
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;78&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;78&quot;&gt;%s : 문자열(String), 숫자형이 오더라도 문자열로 바꾸고 대입해주므로 가장 유연함&lt;/li&gt;
&lt;li data-line=&quot;79&quot;&gt;%c : 문자 1개(character)&lt;/li&gt;
&lt;li data-line=&quot;80&quot;&gt;%d : 정수(Integer)&lt;/li&gt;
&lt;li data-line=&quot;81&quot;&gt;%f : 부동소수(floating-point)&lt;/li&gt;
&lt;li data-line=&quot;82&quot;&gt;%o : 8진수&lt;/li&gt;
&lt;li data-line=&quot;83&quot;&gt;%x : 16진수&lt;/li&gt;
&lt;li data-line=&quot;84&quot;&gt;%% : Literal % (문자 % 자체)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;85&quot;&gt;%10d&lt;span&gt;&amp;nbsp;&lt;/span&gt;는 10자리 공간으로 오른쪽 정렬시키고 대입시킴. 반대는&lt;span&gt;&amp;nbsp;&lt;/span&gt;%-10d&lt;/li&gt;
&lt;li data-line=&quot;86&quot;&gt;format 함수도 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;87&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;87&quot;&gt;named parameter =&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;I ate {number} apples. so I was sick for {day} days.&quot;.format(number=10, day=3)&lt;/li&gt;
&lt;li data-line=&quot;88&quot;&gt;왼쪽 정렬 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;{0:&amp;lt;10}&quot;.format(&quot;hi&quot;)&lt;/li&gt;
&lt;li data-line=&quot;89&quot;&gt;오른쪽 정렬 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;{0:&amp;gt;10}&quot;.format(&quot;hi&quot;)&lt;/li&gt;
&lt;li data-line=&quot;90&quot;&gt;중앙 정렬 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;{0:^10}&quot;.format(&quot;hi&quot;)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;91&quot;&gt;예제
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;92&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;92&quot;&gt;&quot;I eat %d apples.&quot; %3&lt;span&gt;&amp;nbsp;&lt;/span&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;I eat {0} apples&quot;.format(3)&lt;/li&gt;
&lt;li data-line=&quot;93&quot;&gt;&quot;I eat %s apples.&quot; % &quot;five&quot;&lt;span&gt;&amp;nbsp;&lt;/span&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;I eat {0} apples&quot;.format(&quot;five&quot;)&lt;/li&gt;
&lt;li data-line=&quot;94&quot;&gt;&quot;I ate %d apples. so I was sick for %s days.&quot; % (number, day)&lt;span&gt;&amp;nbsp;&lt;/span&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;I ate {0} apples. so I was sick for {1} days.&quot;.format(number, day)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;95&quot;&gt;접두사 f를 붙이면 변수를 바인딩해서 더 편하게 사용가능.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;96&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;96&quot;&gt;파이썬 3.6부터 제공&lt;/li&gt;
&lt;li data-line=&quot;97&quot;&gt;f'나의 이름은 {name}입니다. 나이는 {age}입니다.'&lt;/li&gt;
&lt;li data-line=&quot;98&quot;&gt;표현식도 제공하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;{age+1}&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 것도 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;1-2-3-%EC%9E%90%EC%A3%BC%EC%93%B0%EB%8A%94-%ED%95%A8%EC%88%98&quot; data-line=&quot;100&quot; data-ke-size=&quot;size20&quot;&gt;1-2-3. 자주쓰는 함수&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;101&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;101&quot;&gt;문자 세기 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;hobby&quot;.count(&quot;b&quot;)&lt;/li&gt;
&lt;li data-line=&quot;102&quot;&gt;문자 찾기 : 문자열.find(단어)
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;103&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;103&quot;&gt;찾으면 첫 index&lt;/li&gt;
&lt;li data-line=&quot;104&quot;&gt;못찾으면 -1&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;105&quot;&gt;문자 삽입 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&quot;,&quot;.join('abcd')&lt;span&gt;&amp;nbsp;&lt;/span&gt;=&amp;gt; 'a,b,c,d'&lt;/li&gt;
&lt;li data-line=&quot;106&quot;&gt;대소문자 : upper(), lower()&lt;/li&gt;
&lt;li data-line=&quot;107&quot;&gt;공백제거 : lstrip, rstrip, strip&lt;/li&gt;
&lt;li data-line=&quot;108&quot;&gt;문자열 치환 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;a.replace(&quot;Life&quot;, &quot;Your leg&quot;)&lt;/li&gt;
&lt;li data-line=&quot;109&quot;&gt;문자열 나누기 : b.split(':')
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;110&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;110&quot;&gt;split() 만 하면 공백을 기준으로 나눈다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-3-%EB%A6%AC%EC%8A%A4%ED%8A%B8&quot; data-line=&quot;112&quot; data-ke-size=&quot;size23&quot;&gt;1-3. 리스트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;113&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;113&quot;&gt;생성&amp;nbsp;&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1643694112231&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;a = []
a = list()
b = [1, 2, 3]
c = ['Life', 'is', 'too', 'short']
d = [1, 2, 'Life', 'is']
e = [1, 2, ['Life', 'is']]&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li data-line=&quot;122&quot;&gt;복사하기
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;123&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;123&quot;&gt;b = a[:]&lt;/li&gt;
&lt;li data-line=&quot;124&quot;&gt;b = a.copy()&lt;/li&gt;
&lt;li data-line=&quot;125&quot;&gt;from copy import copy&lt;span&gt;&amp;nbsp;&lt;/span&gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;b = copy(a)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;1-3-1-%EB%A6%AC%EC%8A%A4%ED%8A%B8-%EC%97%B0%EC%82%B0&quot; data-line=&quot;127&quot; data-ke-size=&quot;size20&quot;&gt;1-3-1. 리스트 연산&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;128&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;128&quot;&gt;음수 인덱스는 뒤에서부터 찾는다.&lt;/li&gt;
&lt;li data-line=&quot;129&quot;&gt;슬라이싱 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;[0:2]&lt;/li&gt;
&lt;li data-line=&quot;130&quot;&gt;더하기 : +
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;131&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;131&quot;&gt;중복제거 안된다&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;132&quot;&gt;반복하기 : *&lt;/li&gt;
&lt;li data-line=&quot;133&quot;&gt;수정 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;a[1] = 2&lt;/li&gt;
&lt;li data-line=&quot;134&quot;&gt;삭제 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;del a[1]
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;135&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;135&quot;&gt;del a[2:]&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 3번째 원소부터 전부 삭제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;1-3-2-%EC%9E%90%EC%A3%BC%EC%93%B0%EB%8A%94-%ED%95%A8%EC%88%98&quot; data-line=&quot;137&quot; data-ke-size=&quot;size20&quot;&gt;1-3-2. 자주쓰는 함수&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;138&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;138&quot;&gt;길이 구하기 : len(리스트)&lt;/li&gt;
&lt;li data-line=&quot;139&quot;&gt;요소를 뒤에 추가 : append(x)&lt;/li&gt;
&lt;li data-line=&quot;140&quot;&gt;요소 삽입 : insert(위치, 요소값)&lt;/li&gt;
&lt;li data-line=&quot;141&quot;&gt;정렬 : sort()&lt;/li&gt;
&lt;li data-line=&quot;142&quot;&gt;뒤집기 : reverse()&lt;/li&gt;
&lt;li data-line=&quot;143&quot;&gt;위치반환 : index(x)&lt;/li&gt;
&lt;li data-line=&quot;144&quot;&gt;첫번째로 일치하는 요소를 제거 : remove(요소값)&lt;/li&gt;
&lt;li data-line=&quot;145&quot;&gt;마지막 요소 꺼내고 삭제 : pop()
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;146&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;146&quot;&gt;pop(index)는 index를 꺼내고 삭제한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;147&quot;&gt;특정 값 갯수 세기 : count(값)&lt;/li&gt;
&lt;li data-line=&quot;148&quot;&gt;더하기의 함수버전 : a.extend(b)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-4-%ED%8A%9C%ED%94%8C&quot; data-line=&quot;150&quot; data-ke-size=&quot;size23&quot;&gt;1-4. 튜플&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;151&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;151&quot;&gt;리스트와 거의 동일하지만 아래와 같은 차이점이 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;152&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;152&quot;&gt;리스트는 [] 이지만 튜플은 () 이다.&lt;/li&gt;
&lt;li data-line=&quot;153&quot;&gt;리스트는 생성, 삭제, 수정이 가능하지만 튜플은 불변이다.&lt;/li&gt;
&lt;li data-line=&quot;154&quot;&gt;1개의 요소만을 가질 때는 콤마로 이어줘야함.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;155&quot;&gt;생성
&lt;div&gt;
&lt;pre id=&quot;code_1643694099419&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;t1 = ()
t2 = (1,)
t3 = (1, 2, 3)
t4 = 1, 2, 3
t5 = ('a', 'b', ('ab', 'cd'))&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-5-%EB%94%95%EC%85%94%EB%84%88%EB%A6%AC&quot; data-line=&quot;164&quot; data-ke-size=&quot;size23&quot;&gt;1-5. 딕셔너리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;165&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;165&quot;&gt;Key와 Value를 한 쌍으로 갖는 자료형&lt;/li&gt;
&lt;li data-line=&quot;166&quot;&gt;쌍 추가 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;a[2] = 'b'&lt;/li&gt;
&lt;li data-line=&quot;167&quot;&gt;쌍 삭제 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;del a[2]&lt;/li&gt;
&lt;li data-line=&quot;168&quot;&gt;주의사항
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;169&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;169&quot;&gt;중복 key 불가능&lt;/li&gt;
&lt;li data-line=&quot;170&quot;&gt;튜플은 key로 쓸수 있지만, list는 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;1-5-1-%EC%9E%90%EC%A3%BC%EC%93%B0%EB%8A%94-%ED%95%A8%EC%88%98&quot; data-line=&quot;172&quot; data-ke-size=&quot;size20&quot;&gt;1-5-1. 자주쓰는 함수&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;173&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;173&quot;&gt;key 목록 : keys()
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;174&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;174&quot;&gt;list는 아니고&lt;span&gt;&amp;nbsp;&lt;/span&gt;dict_keys&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체이다. iterator는 가능하다. 순수 list를 얻고 싶다면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;list(a.keys())&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 해줘야함&lt;/li&gt;
&lt;li data-line=&quot;175&quot;&gt;dict_keys&lt;span&gt;&amp;nbsp;&lt;/span&gt;이므로, append, insert, pop 같은 리스트의 함수들은 실행 불가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;176&quot;&gt;value 목록 : values()
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;177&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;177&quot;&gt;list는 아니고&lt;span&gt;&amp;nbsp;&lt;/span&gt;dict_values&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;178&quot;&gt;쌍 목록 : items()
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;179&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;179&quot;&gt;key와 value를 튜플로 묶은&lt;span&gt;&amp;nbsp;&lt;/span&gt;dict_items&lt;span&gt;&amp;nbsp;&lt;/span&gt;객체이다.&lt;/li&gt;
&lt;li data-line=&quot;180&quot;&gt;a.items()&lt;span&gt;&amp;nbsp;&lt;/span&gt;=&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;dict_items([('name', 'pey'), ('phone', '0119993323'), ('birth', '1118')])&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;181&quot;&gt;조회 : a.get(key)
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;182&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;182&quot;&gt;a[key]&lt;span&gt;&amp;nbsp;&lt;/span&gt;는 없으면 오류발생,&lt;span&gt;&amp;nbsp;&lt;/span&gt;a.get(key)는 없으면 None&lt;/li&gt;
&lt;li data-line=&quot;183&quot;&gt;a.get(key, defaultValue) : key 가 없으면 미리 정해둔 기본값으로 가져옴&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;184&quot;&gt;해당 key 가 딕셔너리 안에 있는지 검사 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;'name' in a&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-6-%EC%A7%91%ED%95%A9&quot; data-line=&quot;186&quot; data-ke-size=&quot;size23&quot;&gt;1-6. 집합&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;187&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;187&quot;&gt;생성
&lt;div&gt;
&lt;pre id=&quot;code_1643694084178&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;s1 = set([1,2,3]) # {1, 2, 3}
s2 = set(&quot;Hello&quot;) # {'e', 'H', 'l', 'o'}
s3 = set()&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-line=&quot;193&quot;&gt;특징
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;194&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;194&quot;&gt;중복을 허용하지 않음&lt;/li&gt;
&lt;li data-line=&quot;195&quot;&gt;순서가 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;1-6-1-%EC%A7%91%ED%95%A9%EC%9D%98-%EC%97%B0%EC%82%B0&quot; data-line=&quot;197&quot; data-ke-size=&quot;size20&quot;&gt;1-6-1. 집합의 연산&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;198&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;198&quot;&gt;교집합 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;s1 &amp;amp; s2&lt;span&gt;&amp;nbsp;&lt;/span&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;s1.intersection(s2)&lt;/li&gt;
&lt;li data-line=&quot;199&quot;&gt;합집합 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;s1 | s2&lt;span&gt;&amp;nbsp;&lt;/span&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;s1.union(s2)&lt;/li&gt;
&lt;li data-line=&quot;200&quot;&gt;차집합 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;s1 - s2&lt;span&gt;&amp;nbsp;&lt;/span&gt;=&lt;span&gt;&amp;nbsp;&lt;/span&gt;s1.difference(s2)&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;1-6-2-%EC%9E%90%EC%A3%BC%EC%93%B0%EB%8A%94-%ED%95%A8%EC%88%98&quot; data-line=&quot;202&quot; data-ke-size=&quot;size20&quot;&gt;1-6-2. 자주쓰는 함수&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;203&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;203&quot;&gt;값 추가 : add(x)&lt;/li&gt;
&lt;li data-line=&quot;204&quot;&gt;여러 값 추가 : update([a, b, c])&lt;/li&gt;
&lt;li data-line=&quot;205&quot;&gt;값 제거 : remove(x)&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;1-7-boolean&quot; data-line=&quot;207&quot; data-ke-size=&quot;size23&quot;&gt;1-7. Boolean&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;208&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;208&quot;&gt;bool(값) : True/False&lt;/li&gt;
&lt;li data-line=&quot;209&quot;&gt;자료형의 값이 비어있으면 False
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;210&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;210&quot;&gt;1 : True&lt;/li&gt;
&lt;li data-line=&quot;211&quot;&gt;0 : False&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;212&quot;&gt;None도 False&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2-%EC%A0%9C%EC%96%B4%EB%AC%B8&quot; data-line=&quot;214&quot; data-ke-size=&quot;size26&quot;&gt;2. 제어문&lt;/h2&gt;
&lt;h3 id=&quot;2-1-if%EB%AC%B8&quot; data-line=&quot;215&quot; data-ke-size=&quot;size23&quot;&gt;2-1. if문&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;216&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;216&quot;&gt;구조
&lt;div&gt;
&lt;pre id=&quot;code_1643694072380&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if 조건문1:
    수행문
elif 조건문2:
    수행문
else:
    수행문&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-line=&quot;225&quot;&gt;비교연산자 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;,&lt;span&gt;&amp;nbsp;&lt;/span&gt;==,&lt;span&gt;&amp;nbsp;&lt;/span&gt;!=,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;gt;=,&lt;span&gt;&amp;nbsp;&lt;/span&gt;&amp;lt;=&lt;/li&gt;
&lt;li data-line=&quot;226&quot;&gt;조건연산자 : and, or, not&lt;/li&gt;
&lt;li data-line=&quot;227&quot;&gt;in : x in 리스트 , x in 튜플 , x in 문자열
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;228&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;228&quot;&gt;not in 도 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;229&quot;&gt;pass : 실행할 코드가 없는 것을 명시적으로 나타냄. 중단되진 않고 다음 코드를 계속해서 실행한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-2-while%EB%AC%B8&quot; data-line=&quot;231&quot; data-ke-size=&quot;size23&quot;&gt;2-2. while문&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;232&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;232&quot;&gt;구조
&lt;div&gt;
&lt;pre id=&quot;code_1643694059460&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;while 조건문:
    수행문&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-line=&quot;237&quot;&gt;break : 반복문을 빠져나감&lt;/li&gt;
&lt;li data-line=&quot;238&quot;&gt;continue : 조건문으로 바로 돌아간다&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;2-3-for%EB%AC%B8&quot; data-line=&quot;240&quot; data-ke-size=&quot;size23&quot;&gt;2-3. for문&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;241&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;241&quot;&gt;구조
&lt;div&gt;
&lt;pre id=&quot;code_1643694051032&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;for 변수 in 리스트(또는 튜플, 문자열):
    수행문&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-line=&quot;246&quot;&gt;다양한 for문 형태
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;247&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;247&quot;&gt;for element in ['one', 'two', 'three']:&lt;/li&gt;
&lt;li data-line=&quot;248&quot;&gt;for (first, last) in [(1,2), (3,4), (5,6)]:&lt;/li&gt;
&lt;li data-line=&quot;249&quot;&gt;for index in range(len(리스트)):&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;250&quot;&gt;range(start, end) : end는 미만이다. for문과 함께 잘쓰임&lt;/li&gt;
&lt;li data-line=&quot;251&quot;&gt;리스트 내포
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;252&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;252&quot;&gt;구조 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;[표현식 for 항목 in 반복가능객체 if 조건문]&lt;/li&gt;
&lt;li data-line=&quot;253&quot;&gt;예제
&lt;div&gt;
&lt;pre id=&quot;code_1643694039959&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;a = [1,2,3,4]
result = [num * 3 for num in a if num % 2 == 0]
print(result) # [6, 12]&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3-%ED%95%A8%EC%88%98&quot; data-line=&quot;260&quot; data-ke-size=&quot;size26&quot;&gt;3. 함수&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;261&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;261&quot;&gt;구조
&lt;div&gt;
&lt;pre id=&quot;code_1643694030267&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;def 함수명(매개변수):
    수행문&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-line=&quot;266&quot;&gt;return문이 없으면 None 이다.&lt;/li&gt;
&lt;li data-line=&quot;267&quot;&gt;add(1, 2) 대신 매개변수를 지정하여, add(a=1, b=2) 로 호출할 수 있다.&lt;/li&gt;
&lt;li data-line=&quot;268&quot;&gt;입력받을 수 있는 매개변수가 여러개라면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수명(*args)&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 선언할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;269&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;269&quot;&gt;내부적으로 args는 튜플로 만들어져서 넘겨진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;270&quot;&gt;키워드 파라미터 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수명(**kwargs)&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 선언하며, 딕셔너리 형태로 넘겨진다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;271&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;271&quot;&gt;ex)&lt;span&gt;&amp;nbsp;&lt;/span&gt;print_kwargs(name='foo', age=3)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;272&quot;&gt;함수의 반환은 1개밖에 못한다. 따라서 여러개를 반환하고 싶다면 튜플로 묶던지 해야한다.&lt;/li&gt;
&lt;li data-line=&quot;273&quot;&gt;매개변수 초기값설정이 가능하다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;함수명(a, b, c=True)
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;274&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;274&quot;&gt;뒤에 써야한다.&lt;/li&gt;
&lt;li data-line=&quot;275&quot;&gt;입력값이 없다면 이걸 쓴다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;276&quot;&gt;로컬 변수
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;277&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;277&quot;&gt;함수 내 선언된 변수는 함수 스코프를 가진다. 밖으로 가면 유효하지 않는다.&lt;/li&gt;
&lt;li data-line=&quot;278&quot;&gt;함수 밖 변수를 사용해야한다면,&lt;span&gt;&amp;nbsp;&lt;/span&gt;global a&lt;span&gt;&amp;nbsp;&lt;/span&gt;로 접근가능하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;279&quot;&gt;lambda 는 def 와 비슷하지만, 한줄로 간결하게 쓸 때 사용한다.&amp;nbsp;&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1643694017585&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;add = lambda a, b: a + b
result = add(3, 4)&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4-%EC%9E%85%EC%B6%9C%EB%A0%A5&quot; data-line=&quot;285&quot; data-ke-size=&quot;size26&quot;&gt;4. 입출력&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;286&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;286&quot;&gt;프롬프트 입력 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;number = input(&quot;숫자를 입력하세요: &quot;)&lt;/li&gt;
&lt;li data-line=&quot;287&quot;&gt;개행없이 출력 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;print(i, end=&quot; &quot;)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;5-%ED%81%B4%EB%9E%98%EC%8A%A4&quot; data-line=&quot;289&quot; data-ke-size=&quot;size26&quot;&gt;5. 클래스&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;290&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;290&quot;&gt;클래스의 함수의 첫번째 매개변수는 self로, 인스턴스가 자동으로 할당된다.a = Cal() a.함수(4, 2) Cal.함수(a, 4, 2)&lt;/li&gt;
&lt;li data-line=&quot;296&quot;&gt;생성자 함수명 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;__init__&lt;/li&gt;
&lt;li data-line=&quot;297&quot;&gt;상속 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;class 자식(부모)&lt;/li&gt;
&lt;li data-line=&quot;298&quot;&gt;클래스 변수 : 객체변수는 객체마다 다르게 생성되는 변수이지만, 클래스 변수는 static한 변수다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;299&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;299&quot;&gt;따라서, 여러 객체가 하나의 클래스 변수를 공유한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div&gt;
&lt;pre id=&quot;code_1643693992865&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;class Family:
    lastname = &quot;김&quot;&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;6-%EB%AA%A8%EB%93%88&quot; data-line=&quot;305&quot; data-ke-size=&quot;size26&quot;&gt;6. 모듈&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;306&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;306&quot;&gt;모듈은 하나의 .py 파일이다.&lt;/li&gt;
&lt;li data-line=&quot;307&quot;&gt;import 모듈명
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;308&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;308&quot;&gt;같은 디렉터리에 있거나, 파이썬 라이브러리여야 한다.&lt;/li&gt;
&lt;li data-line=&quot;309&quot;&gt;.py 확장자는 생략&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;310&quot;&gt;from 모듈명 import 특정부분
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;311&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;311&quot;&gt;ex)&lt;span&gt;&amp;nbsp;&lt;/span&gt;from mod1 import add, sub&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;312&quot;&gt;__name__&lt;span&gt;&amp;nbsp;&lt;/span&gt;변수에는 내가 실행한 py일 경우,&lt;span&gt;&amp;nbsp;&lt;/span&gt;__main__&lt;span&gt;&amp;nbsp;&lt;/span&gt;이 저장되고, 아닐 경우, 해당 모듈명이 저장된다.
&lt;div&gt;
&lt;pre id=&quot;code_1643693977275&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;if __name__ == &quot;__main__&quot;:
    print(add(1, 4))
    print(sub(4, 2))&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;7-%ED%8C%A8%ED%82%A4%EC%A7%80&quot; data-line=&quot;319&quot; data-ke-size=&quot;size26&quot;&gt;7. 패키지&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;320&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;320&quot;&gt;패키지(Packages)는 도트(.)를 사용하여 파이썬 모듈을 계층적(디렉터리 구조)으로 관리&lt;/li&gt;
&lt;li data-line=&quot;321&quot;&gt;패키지 디렉터리의 구조
&lt;div&gt;
&lt;pre id=&quot;code_1643693960892&quot; class=&quot;shell&quot; data-ke-language=&quot;shell&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;game/
    __init__.py
    sound/
        __init__.py
        echo.py
        wav.py
    graphic/
        __init__.py
        screen.py
        render.py
    play/
        __init__.py
        run.py
        test.py&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-line=&quot;338&quot;&gt;__init__.py&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일은 해당 디렉터리가 패키지의 일부임을 알려주는 역할
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;339&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;339&quot;&gt;python3.3 버전부터는&lt;span&gt;&amp;nbsp;&lt;/span&gt;__init__.py&lt;span&gt;&amp;nbsp;&lt;/span&gt;파일이 없어도 패키지로 인식&lt;/li&gt;
&lt;li data-line=&quot;340&quot;&gt;별다른 코드는 없음. import * 에 대비하여&lt;span&gt;&amp;nbsp;&lt;/span&gt;__all__&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 만지는 정도?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;8-%EC%98%88%EC%99%B8%EC%B2%98%EB%A6%AC&quot; data-line=&quot;342&quot; data-ke-size=&quot;size26&quot;&gt;8. 예외처리&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;343&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;343&quot;&gt;구조
&lt;div&gt;
&lt;pre id=&quot;code_1643693922027&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try:
    수행문1
except [발생 오류[as 오류 메시지 변수]]:
    수행문2
else: # 오류가 없으면 수행
    수행문3
finally:
    수행문4&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-line=&quot;354&quot;&gt;예제
&lt;div&gt;
&lt;pre id=&quot;code_1643693933029&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;try:
    a = [1,2]
    print(a[3])
    4/0
except ZeroDivisionError as e:
    print(e)
except IndexError as e:
    print(e)
# except (ZeroDivisionError, IndexError) as e:&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li data-line=&quot;366&quot;&gt;의도적으로 오류 발생시키기 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;raise NotImplementedError&lt;/li&gt;
&lt;li data-line=&quot;367&quot;&gt;커스텀 예외 작성 :&lt;span&gt;&amp;nbsp;&lt;/span&gt;class MyError(Exception):
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;368&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;368&quot;&gt;raise MyError()&lt;/li&gt;
&lt;li data-line=&quot;369&quot;&gt;__str__&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 구현하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;print(e)&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 할때 오류메세지가 출력된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;9-%EB%82%B4%EC%9E%A5%ED%95%A8%EC%88%98&quot; data-line=&quot;371&quot; data-ke-size=&quot;size26&quot;&gt;9. 내장함수&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;372&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;372&quot;&gt;import 필요없이 바로 사용 가능&lt;/li&gt;
&lt;li data-line=&quot;373&quot;&gt;&lt;a href=&quot;https://wikidocs.net/32&quot;&gt;https://wikidocs.net/32&lt;/a&gt;&lt;/li&gt;
&lt;li data-line=&quot;374&quot;&gt;enumerate :&lt;span&gt;&amp;nbsp;&lt;/span&gt;for i, name in enumerate(['body', 'foo', 'bar']):&lt;/li&gt;
&lt;li data-line=&quot;375&quot;&gt;filter :&lt;span&gt;&amp;nbsp;&lt;/span&gt;list(filter(lambda x: x &amp;gt; 0, [1, -3, 2, 0, -5, 6]))&lt;/li&gt;
&lt;li data-line=&quot;376&quot;&gt;int : 정수형으로 변환&lt;/li&gt;
&lt;li data-line=&quot;377&quot;&gt;isinstance(object, class)&lt;/li&gt;
&lt;li data-line=&quot;378&quot;&gt;map :&lt;span&gt;&amp;nbsp;&lt;/span&gt;list(map(lambda a: a*2, [1, 2, 3, 4]))&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;10-%EB%9D%BC%EC%9D%B4%EB%B8%8C%EB%9F%AC%EB%A6%ACstandard-library&quot; data-line=&quot;380&quot; data-ke-size=&quot;size26&quot;&gt;10. 라이브러리(standard library)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;381&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;381&quot;&gt;&lt;a href=&quot;https://wikidocs.net/33&quot;&gt;https://wikidocs.net/33&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;11-%EA%B8%B0%ED%83%80&quot; data-line=&quot;383&quot; data-ke-size=&quot;size26&quot;&gt;11. 기타&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;384&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;384&quot;&gt;with 문
&lt;ul style=&quot;list-style-type: disc;&quot; data-line=&quot;385&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;385&quot;&gt;java의 try with resource 랑 비슷한 듯&lt;/li&gt;
&lt;li data-line=&quot;385&quot;&gt;
&lt;pre id=&quot;code_1643786686267&quot; class=&quot;python&quot; data-ke-language=&quot;python&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;# AS-IS
f = open(&quot;test.txt&quot;, &quot;r&quot;) # 자원 획득
text = f.read() # 자원 사용
f.close() # 자원 반납

# TO-BE
with open(&quot;test.txt&quot;, &quot;r&quot;) as f:
    text = f.read()&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>python</category>
      <category>Python</category>
      <author>빨간색소년</author>
      <guid isPermaLink="true">https://sjh836.tistory.com/192</guid>
      <comments>https://sjh836.tistory.com/192#entry192comment</comments>
      <pubDate>Tue, 1 Feb 2022 14:43:29 +0900</pubDate>
    </item>
    <item>
      <title>DDD Quickly (도메인 주도 설계, 전술적-전략적 설계, Spring @Service의 유래, @Configurable를 통한 도메인 모델에 bean 주입)</title>
      <link>https://sjh836.tistory.com/191</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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://martinfowler.com/tags/domain%20driven%20design.html&quot;&gt;https://martinfowler.com/tags/domain%20driven%20design.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p data-ke-size=&quot;size16&quot;&gt;Domain&amp;nbsp;Driven&amp;nbsp;Design&amp;nbsp;Quickly&amp;nbsp;(도메인&amp;nbsp;주도&amp;nbsp;설계란&amp;nbsp;무엇인가?&amp;nbsp;쉽고&amp;nbsp;간략하게&amp;nbsp;이해하는&amp;nbsp;DDD,&amp;nbsp;플로이드&amp;nbsp;마리네스쿠&amp;nbsp;지음,&amp;nbsp;최수경&amp;nbsp;옮김)을&amp;nbsp;읽고&amp;nbsp;이해한&amp;nbsp;내용을&amp;nbsp;정리해보았다. &lt;br /&gt;&lt;br /&gt;Evans Eric 의 Domain-Driven Design: Tackling Complexity in the Heart of Software 는 2003년 8월 22일 초판인데, 이걸 기반으로 2011년에 쓴 책이라고 한다. &lt;br /&gt;&lt;br /&gt;책에 있는 내용을 기반으로 썼지만, 좀 더 쉬운 이해를 위하여 예제를 덧붙이거나, 주관적 해석, 책에 없는 내용(반 버논의 주석, Spring 프레임워크에서 DDD 이야기 등)도 약간 적어보았다.&amp;nbsp; &lt;/p&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;dddq.png&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;599&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cCciJp/btrqXxc8Qrp/qWKmoa7yrvkvDTnPqpywm0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cCciJp/btrqXxc8Qrp/qWKmoa7yrvkvDTnPqpywm0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cCciJp/btrqXxc8Qrp/qWKmoa7yrvkvDTnPqpywm0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcCciJp%2FbtrqXxc8Qrp%2FqWKmoa7yrvkvDTnPqpywm0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;252&quot; height=&quot;334&quot; data-filename=&quot;dddq.png&quot; data-origin-width=&quot;452&quot; data-origin-height=&quot;599&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;DDDQuickly-1장.도메인주도설계란무엇인가?&quot; data-ke-size=&quot;size26&quot;&gt;1장. 도메인 주도 설계란 무엇인가?&lt;/h2&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 회계 프로그램이 복잡한 것은 코드나 기술이 문제가 아니라, 회계 도메인 자체가 어렵고 난해하기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;좋은 소프트웨어를 만들기 위해서는, 그 소프트웨어가 무엇에 관련된 것인지를 알아야한다.&lt;/li&gt;
&lt;li&gt;소프트웨어는 도메인을 모델링해야 한다.&lt;/li&gt;
&lt;li&gt;추상화 : 도메인을 표현한 모델&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;소프트웨어 설계 : 청사진&lt;/li&gt;
&lt;li&gt;코드 설계 : 세부 작업들&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;DDDQuickly-2장.유비쿼터스언어&quot; data-ke-size=&quot;size26&quot;&gt;2장. 유비쿼터스 언어&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;유비쿼터스(Qbiquitous) 뜻 : 언제 어디서나 동시에 존재하는&lt;/li&gt;
&lt;li&gt;개발자는 클래스, 메소드, 알고리즘, 패턴 등으로 머리가 가득차 있고, 늘 현실 세계를 프로그램으로 매핑하려는 경향을 지닌다.&lt;/li&gt;
&lt;li&gt;소통이 가장 중요하다. 도메인전문가와 소프트웨어전문가 중 한쪽이 이해를 잘못했다면 성공할 수 없다.&lt;/li&gt;
&lt;li&gt;공통 언어의 필요성 = 모델 기반 언어&lt;/li&gt;
&lt;li&gt;클래스, 메소드, 모듈의 이름도 늘 쓰는 단어의 의미에 서로 합의된 것을 사용하라
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;데이터중심적 사고, DB엔지니어들이 비즈니스 용어를 사용하지 않고 알아보기 힘든 약자 등을 사용하는 것을 지양&lt;/li&gt;
&lt;li&gt;지금이야 업계에서 이런 네이밍이 많이 사라졌지만, DDD가 주장될 쯤은 2000년대 초였다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cf)&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://jang8584.tistory.com/35&quot;&gt;https://jang8584.tistory.com/35&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;ex) 사용자이름 USER_NM, 중분류코드명 J_CODE_IDX&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;UML은 핵심 개념들을 클래스로 표현하고 클래스간 관계로 표현하는데 훌륭하다.
&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;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;DDDQuickly-3장.모델주도설계(전술적설계,TacticalDesign)&quot; data-ke-size=&quot;size26&quot;&gt;3장. 모델 주도 설계 (전술적 설계, Tactical Design)&lt;/h2&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1안) 도메인 분석과 코드 구현을 별개로 분리함. 개발자는&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;분석모델&lt;/b&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;/li&gt;
&lt;li&gt;&lt;b&gt;2안)&lt;/b&gt;&lt;span&gt;&amp;nbsp;&lt;/span&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;2안을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;전술적 설계&lt;/b&gt;라고 할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;5장의 전략적 설계와 다르게, 분할된 컨텍스트(특정 모델) 내부를 모델링한다고 보면 될 듯&lt;/li&gt;
&lt;li&gt;반 버논(Vaughn Vernon)은 전술적 설계 중 일부만 차용한 것을 DDD-Lite 라고 정의한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;설계 블록
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&amp;nbsp;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ddd_모델주도설계_블록.png&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;636&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/9Jjqj/btrqMQy14cl/szzHtxAmAQM8VdkyfrRpFk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/9Jjqj/btrqMQy14cl/szzHtxAmAQM8VdkyfrRpFk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/9Jjqj/btrqMQy14cl/szzHtxAmAQM8VdkyfrRpFk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2F9Jjqj%2FbtrqMQy14cl%2FszzHtxAmAQM8VdkyfrRpFk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;948&quot; height=&quot;636&quot; data-filename=&quot;ddd_모델주도설계_블록.png&quot; data-origin-width=&quot;948&quot; data-origin-height=&quot;636&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DDDQuickly-3-1.계층형아키텍처(LayeredArchitecture)&quot; data-ke-size=&quot;size23&quot;&gt;3-1. 계층형 아키텍처 (Layered Architecture)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어플리케이션은 도메인, 비즈니스 외에도 DB, 네트워크, 파일 등 인프라스트럭처 코드들이 상당하다.&lt;/li&gt;
&lt;li&gt;도메인 코드가 다른 레이어와 섞여있으면, 이해하기가 어려워지고 외부변경에 사이드이펙트가 있을 수 있다.&lt;/li&gt;
&lt;li&gt;아래와 같이 레이어를 분할해야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프리젠테이션 : UI영역, view&lt;/li&gt;
&lt;li&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;2003년에 나온 개념이라 현재 씬에서 딱 맵핑되는 건 없는 것 같다. 이미 DDD 철학이 다 녹아들어있기때문에.. MVC로 치면 컨트롤러 느낌? 마이크로서비스에서 로케이터 느낌?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;도메인 : 비즈니스 객체를 다룬다. 서비스와 모델로 구성되어 있는 듯&lt;/li&gt;
&lt;li&gt;인프라스트럭처 : 영속성 구현, 레이어 간 통신 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DDDQuickly-3-2.엔티티(Entity)&quot; data-ke-size=&quot;size23&quot;&gt;3-2. 엔티티 (Entity)&lt;/h3&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;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;/li&gt;
&lt;li&gt;엔티티를 구현한다는 것은 식별자를 만들어내는 것이라고 볼 수 있다.&lt;/li&gt;
&lt;li&gt;도메인 객체 &amp;sup; 엔티티&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DDDQuickly-3-3.값객체(ValueObject)&quot; data-ke-size=&quot;size23&quot;&gt;3-3. 값 객체 (Value Object)&lt;/h3&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;쉽게 생성되고 쉽게 폐기되며 영속화도 신경안써도 된다. 설계를 단순하게 만들고, VO의 단순함을 유지해라.&lt;/li&gt;
&lt;li&gt;불변으로 사용해야 한다. 상태를 변경해야한다면 새로운 VO를 만들어서 사용해라.&lt;/li&gt;
&lt;li&gt;VO 역시 도메인 로직을 갖는다. 예를들면 변환, 포매팅, 검증 등이 있겠다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DDDQuickly-3-4.서비스&quot; data-ke-size=&quot;size23&quot;&gt;3-4. 서비스&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;Object Oriented Design 와의 큰 차이점&lt;/b&gt;이라고 볼 수도 있겠다. 행동을 객체에서 의도적으로 분리시키기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이러한 행위들을 서비스 객체로 정의한다.&lt;/li&gt;
&lt;li&gt;내부적인 상태를 갖지 않으면서 도메인의 기능을 제공한다.&lt;/li&gt;
&lt;li&gt;서비스의 특징
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티, VO에 속할 수 없는 도메인의 개념&lt;/li&gt;
&lt;li&gt;도메인의 다른 객체를 참조&lt;/li&gt;
&lt;li&gt;stateless&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;서비스가 어느 레이어에 속해야하는지는 상황과 개념에 따라 다르다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;이 부분에서 문득, 자주 다루는 Spring 프레임워크의&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;&amp;nbsp;&lt;/span&gt;@Service&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;과는 무슨 상관이 있을까? 궁금했는데..&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;spring-service.png&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;340&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ml3MZ/btrqNxSWJY3/RMKnvWpwErFPKJ0kwkBdeK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ml3MZ/btrqNxSWJY3/RMKnvWpwErFPKJ0kwkBdeK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ml3MZ/btrqNxSWJY3/RMKnvWpwErFPKJ0kwkBdeK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fml3MZ%2FbtrqNxSWJY3%2FRMKnvWpwErFPKJ0kwkBdeK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;686&quot; height=&quot;340&quot; data-filename=&quot;spring-service.png&quot; data-origin-width=&quot;686&quot; data-origin-height=&quot;340&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;DDD 에서 따온 개념이라 한다.. 소름..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DDDQuickly-3-5.모듈&quot; data-ke-size=&quot;size23&quot;&gt;3-5. 모듈&lt;/h3&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;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;높은 응집도(High Cohesion)와 낮은 결합도(Loose Coupling)를 추구해야 한다는 원칙&lt;/b&gt;을 지키기 위해 노력해야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;통신(communicational) 응집도 : 비슷한 데이터들을 그룹핑하여 모듈화&lt;/li&gt;
&lt;li&gt;기능(functional) 응집도&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;모듈은 다른 모듈에서 쉽게 접근 할 수 있는 인터페이스를 가져야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;구체적인 클래스를 참조하지 않아도 되므로&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;결합도 감소&lt;/b&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;모듈은 관점에 따라 MSA의 마이크로서비스 단위가 될 수도 있을 듯?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cf) 반 버논은 분할된 컨텍스트 내 여러 마이크로서비스가 들어갈 수도 있다고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DDDQuickly-3-6.집합(Aggregate)&quot; data-ke-size=&quot;size23&quot;&gt;3-6. 집합 (Aggregate)&lt;/h3&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;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;1대N 관계는 N이 1의 집합으로 들어감으로써 단순화&lt;/li&gt;
&lt;li&gt;N대M 관계는 아래와 같이 단순화하라
&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 자동차에 엔진이 있고, 엔진에는 호환되는 자동차 목록이 있음. 그러나 전자만 고려하여 단방향으로 바꿔버릴 수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;복잡한 참조관계를 유지하며 데이터 무결성을 지키기 위해, DBMS의 트랜잭션을 사용한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;모델이 잘못 설계되었을 수록 DB에 기대야하며, lock 등으로 인하여 높은 비용이 소모된다.&lt;/li&gt;
&lt;li&gt;따라서, 모델 설계수준에서 직접 해결할 수 있다면 가장 좋다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;집합은 하나의 root(=엔티티)를 갖는다. root는 집합된 다른 객체들을 참조하고 있고 관계를 맺고 있다.&lt;/li&gt;
&lt;li&gt;집합을 사용하면 데이터 무결성을 보장하고 불변식을 강제할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하위의 집합된 객체들은 root만이 참조하고 변경할 수 있기때문이다. 외부에서는 root만 public하므로..&lt;/li&gt;
&lt;li&gt;외부에서 하위 객체를 꼭 참조해야한다면 데이터 무결성을 위하여 복사하여 VO으로 전달해야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이미 VO가 불변이라면 굳이 복사는 안해도 될듯..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;영속성 레벨에서는 집합된 데이터를 어떻게 저장해야 좋을까?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;NoSQL 같은 경우, 통으로 저장할 수도 있겠다.&lt;/li&gt;
&lt;li&gt;RDB라면 table 다 쪼개서 저장하고 join 하는 형태로?&lt;/li&gt;
&lt;li&gt;하위 집합의 객체들이 DB에 별도 table 에 저장되어있다면, 참조가 필요해졌을 때, select 로 얻어오면 될 듯?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;이런 거에서 출발해서 JPA의 LazyLoading 개념이 나온건가..?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;cf) 반 버논은 하나의 트랜잭션으로 join 하여 가져오지말고, id를 통해 필요할 때 여러번 조회하여 참조하라고 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DDDQuickly-3-7.팩토리(Factory)&quot; data-ke-size=&quot;size23&quot;&gt;3-7. 팩토리 (Factory)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;거대한 엔티티를 생성자를 통해 생성하기에는 너무 크고 복잡하다. 이러한 복잡한 객체 생성의 절차를 캡슐화한 것이 팩토리다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;실제 도메인을 예로 들어보면, 자동차는 아래 공정을 거친다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;프레스(new FramePress(철판, ...),&lt;span&gt;&amp;nbsp;&lt;/span&gt;new BodyPanel(...)) -&amp;gt; 차체(new 차량구조(각종 판넬, 용접기)) -&amp;gt; 도장 -&amp;gt; 의장(조립) -&amp;gt; 검사 -&amp;gt; 완성&lt;/li&gt;
&lt;li&gt;여러 모듈들이 먼저 완성되고 이것들을 조합한 것이다. (new Car(제조공정의 결과물들))&lt;/li&gt;
&lt;li&gt;이렇게 복잡한 것을 별도로 분리하여 캡슐화한다는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;팩토리 구현 방식
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;하나의 종류당 1개의 팩토리이며 단일메소드를 가짐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;root를 생성하려면 집합된 객체들도 생성하고 관계를 잡아줘야한다. 이런 로직은 어떤 객체에도 포함하는게 어색하기 때문에 factory 객체가 맡는게 적절하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;마치 컴퓨터라는 도메인 객체가 CPU, RAM, 디스크 등 여러 하위 객체들로 구성된 것처럼..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cf) 전략 패턴 : 객체가 할 수 있는 행위들을 각각의 전략으로 만들고, 행위의 수정이 필요하다면 전략을 단순히 교체만하면 되도록 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;인자에 따라 생성되는 클래스가 달라지지 않고, 자기 자신 일때&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;팩토리는 순수한 도메인 레이어다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DDDQuickly-3-8.리파지토리(Repository)&quot; data-ke-size=&quot;size23&quot;&gt;3-8. 리파지토리 (Repository)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;클라이언트가 DB에서 필요한 정보(객체)를 조회하고 바로 사용한다면, 도메인 모델 설계에 위반한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;sql 결과로 필요이상으로 많은 데이터가 노출된다.&lt;/li&gt;
&lt;li&gt;도메인 개념보다 인프라스트럭처를 더 다루게 된다.&lt;/li&gt;
&lt;li&gt;모델은 단순히 데이터운반 역할만 하게 될 것이다.&lt;/li&gt;
&lt;li&gt;인프라스트럭처 변경에도 취약해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;하위 집합의 어떤 객체가 필요하더라도, 도메인 모델 설계에 맞게 root(=엔티티, 도메인객체)를 조회하고, root부터 참조하라&lt;/li&gt;
&lt;li&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를 들면, root(=엔티티)를 리파지토리가 갖고있으면서, 어떤 메소드를 호출하면 하위 집합의 객체들을 반환해주는 형태&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;리파지토리는 객체 조회 시, cache hit 하기도 하지만, cache miss 라면 영속성 스토리지에서 읽어오는 경우가 더 많다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;리파지토리는 팩토리와 다르게 인프라스트럭처 레이어와도 연결이 포함될 수 있다.&lt;/li&gt;
&lt;li&gt;리파지토리는 CRUD를 수행할 수 있다. 여기서 Create는 아무것도 없는 상태에서 객체를 생성하는 것이 아니라, 팩토리에서 넘겨받은 객체를 추가하는 것이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ddd_repository.png&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;684&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/m3sOg/btrqN3cYulm/8kJoJ4fTBkaD3iYPB4zp7K/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/m3sOg/btrqN3cYulm/8kJoJ4fTBkaD3iYPB4zp7K/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/m3sOg/btrqN3cYulm/8kJoJ4fTBkaD3iYPB4zp7K/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fm3sOg%2FbtrqN3cYulm%2F8kJoJ4fTBkaD3iYPB4zp7K%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;949&quot; height=&quot;684&quot; data-filename=&quot;ddd_repository.png&quot; data-origin-width=&quot;949&quot; data-origin-height=&quot;684&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;Spring 프레임워크의&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;&amp;nbsp;&lt;/span&gt;@Repository&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;&amp;nbsp;&lt;/span&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;도 역시 DDD 에서 따온 개념이라 한다.&lt;/span&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;spring-repository.png&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;455&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dig1Xy/btrqUSuYEr1/BBwHXMKQKXR5Eo6QKmkOp0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dig1Xy/btrqUSuYEr1/BBwHXMKQKXR5Eo6QKmkOp0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dig1Xy/btrqUSuYEr1/BBwHXMKQKXR5Eo6QKmkOp0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdig1Xy%2FbtrqUSuYEr1%2FBBwHXMKQKXR5Eo6QKmkOp0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;684&quot; height=&quot;455&quot; data-filename=&quot;spring-repository.png&quot; data-origin-width=&quot;684&quot; data-origin-height=&quot;455&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;cf) Spring 2.0 은 06년 10월에 출시&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;DDDQuickly-4장.깊은통찰을향한리팩터링&quot; data-ke-size=&quot;size26&quot;&gt;4장. 깊은 통찰을 향한 리팩터링&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;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;/li&gt;
&lt;li&gt;도메인에 대한 통찰, 모델이나 코드에 드러나는 표현을 가다듬기 위한 것도 리팩토링이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;정교한 모델, 좋은 모델은 도메인 전문가와 개발자들이 밀접하게 엮여서 반복적으로 리팩토링을 해야 만들어진다.&lt;/li&gt;
&lt;li&gt;도메인 전문가와 이야기할 때, 모델에 표현되지 않은 암시적 개념이 설계에서 핵심적 역할을 한다면, 이것을 모델에 추가함으로써 명시적으로 만들어야한다.&lt;/li&gt;
&lt;li&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;li&gt;해당 도메인의 문헌을 활용하라&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;명시적인 것을 만들어낼 때, 유용한 것은 아래 3가지다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;제약 조건(constraint)
&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;/li&gt;
&lt;li&gt;처리(process)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;대개 절차적 코드로 표현된다. 처리를 표현하는 권장방법은 Service 를 이용하는 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;명세(specification)
&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;pre id=&quot;code_1642364560628&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;Customer customer = customerRepository.findCustomer(id);
Specification customerEligibleForRefund = new Specification(new CustomerPaidHisDebtsInThePast(), new CustomerHasNoOutstandingBalances());
if (customerEligibleForRefund.isSatisfiedBy(customer)) { ... }&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;코드만 읽어도 명세파악이 가능해야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;DDDQuickly-5장.모델무결성보존(전략적설계,StrategicDesign)&quot; data-ke-size=&quot;size26&quot;&gt;5장. 모델 무결성 보존 (전략적 설계, Strategic Design)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;이것을&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;b&gt;전략적 설계&lt;/b&gt;라고 부를 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ddd_strategic_design.png&quot; data-origin-width=&quot;1776&quot; data-origin-height=&quot;974&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/cC9w4l/btrqNfL7B09/5EnR9AZPKYKURRe670wDkk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/cC9w4l/btrqNfL7B09/5EnR9AZPKYKURRe670wDkk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/cC9w4l/btrqNfL7B09/5EnR9AZPKYKURRe670wDkk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FcC9w4l%2FbtrqNfL7B09%2F5EnR9AZPKYKURRe670wDkk%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1776&quot; height=&quot;974&quot; data-filename=&quot;ddd_strategic_design.png&quot; data-origin-width=&quot;1776&quot; data-origin-height=&quot;974&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DDDQuickly-5-1.분할된컨텍스트(BoundedContext)&quot; data-ke-size=&quot;size23&quot;&gt;5-1. 분할된 컨텍스트 (Bounded Context)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;분할된 컨텍스트는 모듈이 아니다. 모듈을 포함하는 개념이다. 분할된 컨텍스트는 논리적으로 1개의 모델을 갖는다.&lt;/li&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ddd_bounded_context.png&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;666&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/sC27A/btrqQeTdi0K/aXIopSCBe0T0awf2j7Zjr0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/sC27A/btrqQeTdi0K/aXIopSCBe0T0awf2j7Zjr0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/sC27A/btrqQeTdi0K/aXIopSCBe0T0awf2j7Zjr0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FsC27A%2FbtrqQeTdi0K%2FaXIopSCBe0T0awf2j7Zjr0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1084&quot; height=&quot;666&quot; data-filename=&quot;ddd_bounded_context.png&quot; data-origin-width=&quot;1084&quot; data-origin-height=&quot;666&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;li&gt;예를들어, 전자상거래 서비스가 있다면 domain은 전자상거래이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;여기서 회원, 결제, 주문, 리포팅은 subdomain 이다.&lt;/li&gt;
&lt;li&gt;결제라는 Bounded Context에서는 회원의 신용카드정보 등이 보일 것이다.&lt;/li&gt;
&lt;li&gt;주문이라는 Bounded Context에서는 회원의 주소 등이 보일 것이다.&lt;/li&gt;
&lt;li&gt;즉, 모델이 Bounded Context에 따라서 취급되거나 중요해지는 요소들이 달라진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;subdomain 과 Bounded Context 가 꼭 1대1은 아닐 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;2개의 subdomain 이 1개의 Bounded Context 를 가질 수도 있다.&lt;/li&gt;
&lt;li&gt;1개의 subdomain 이 2개의 Bounded COntext 를 가질 수도 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;분할된 컨텍스트는 유비쿼터스 언어의 일부인 고유한 이름이 있어야한다. 팀 간 의사소통이 원활해진다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DDDQuickly-5-2.지속적인통합&quot; data-ke-size=&quot;size23&quot;&gt;5-2. 지속적인 통합&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/li&gt;
&lt;li&gt;새로운 구성 요소가 기존 모델에 조화롭게 추가되고, 올바르게 구현되도록 보장할 수 있는 프로세스가 필요하다.&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;통합된 코드는 테스트할 수 있도록 자동으로 빌드되어야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DDDQuickly-5-3.컨텍스트맵(ContextMap)&quot; data-ke-size=&quot;size23&quot;&gt;5-3. 컨텍스트 맵 (Context Map)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;/li&gt;
&lt;li&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;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;DDDQuickly-5-3-1.공유-커널(SharedKernel)&quot; data-ke-size=&quot;size20&quot;&gt;5-3-1. 공유-커널 (Shared Kernel)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;각 분할된 컨텍스트가 하나의 모델을 공유해야한다면, 명시적으로 공유된 부분으로 지정할 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단순 모델 공유가 아니라, 코드 및 DB도 포함될 수 있다.&lt;/li&gt;
&lt;li&gt;중복 개발을 줄여준다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;공유된 부분은 상대 팀과 의사소통을 하면서 변경해야한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;DDDQuickly-5-3-2.고객-공급자(Customer-Supplier)&quot; data-ke-size=&quot;size20&quot;&gt;5-3-2. 고객-공급자 (Customer-Supplier)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;A와 B가 있을 때, B가 A에게 전적으로 의존을 한다면, A는 공급자이고, B는 고객이다.&lt;/li&gt;
&lt;li&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;/li&gt;
&lt;li&gt;고객-공급자 관계는 양쪽 팀이 관계 유지에 관심이 있을 때, 성공할 수 있다.&lt;/li&gt;
&lt;li&gt;고객은 공급자에게 요구사항을 표현하고, 공급자는 계획을 제공하며 의사소통하라.&lt;/li&gt;
&lt;li&gt;예를들어, 전자상거래 서비스는 리포팅 서비스가 하나도 안중요하지만, 리포팅 서비스는 전자상거래의 모든 데이터를 사용한다.&lt;/li&gt;
&lt;li&gt;컨텍스트 상호 간 인터페이스가 정교하게 정의되어야하고, 테스트케이스로 안전하게 검증되어야 한다.&lt;/li&gt;
&lt;li&gt;이 경우가 가장 많다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;DDDQuickly-5-3-3.순응(Conformity)&quot; data-ke-size=&quot;size20&quot;&gt;5-3-3. 순응 (Conformity)&lt;/h4&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;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;/li&gt;
&lt;li&gt;공유 커널과 차이점은 고객이라서 공유된 부분에 변경을 가할 수 없다는 것이다.&lt;/li&gt;
&lt;li&gt;cf) 주로 API를 일방적으로 사용하는 경우&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;DDDQuickly-5-3-4.변질방지레이어(Anticorruption)&quot; data-ke-size=&quot;size20&quot;&gt;5-3-4. 변질 방지 레이어 (Anticorruption)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DDD로 만들어진 어플리케이션은 레거시 어플리케이션이나 분리된 어플리케이션과 상호작용을 할 일이 있을 수 있다.
&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;네트워크나 DB를 통해 외부 데이터를 가져올 수 있으나, 원시 데이터이다. 여기에는 모델에 대한 정보가 없고 시맨틱이 숨어있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;변질 방지 레이어는 외부 시스템을 추상화하고 외부에는 외부언어로, 내부에는 클라이언트언어로, 양방향 번역자의 역할을 한다.&lt;/li&gt;
&lt;li&gt;&lt;span style=&quot;background-color: #ffffff; color: #172b4d;&quot;&gt;변질 방지 레이어의 변환을 통해 컨텍스트 내 순수함을 지킬 수 있다.&lt;/span&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;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;ddd_anticorruption.png&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;497&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/K5KCp/btrqOgcGKsJ/NUE9UwT2drNSdHKhP3Eya0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/K5KCp/btrqOgcGKsJ/NUE9UwT2drNSdHKhP3Eya0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/K5KCp/btrqOgcGKsJ/NUE9UwT2drNSdHKhP3Eya0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FK5KCp%2FbtrqOgcGKsJ%2FNUE9UwT2drNSdHKhP3Eya0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;749&quot; height=&quot;497&quot; data-filename=&quot;ddd_anticorruption.png&quot; data-origin-width=&quot;749&quot; data-origin-height=&quot;497&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;실제 구현은 Facade 패턴을 활용한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Facade : 서브시스템에 있는 인터페이스들을 통합하여 사용하기 쉽고 고수준의 인터페이스를 제공&lt;/li&gt;
&lt;li&gt;Adapter : 클라이언트가 이해할 수 있는 것으로 변환. 두 시스템간 번역&lt;/li&gt;
&lt;li&gt;Translator : 객체와 데이터를 번역&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;DDDQuickly-5-3-5.분할방식(SeparateWay)&quot; data-ke-size=&quot;size20&quot;&gt;5-3-5. 분할 방식 (Separate Way)&lt;/h4&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;h4 id=&quot;DDDQuickly-5-3-6.오픈호스트서비스(OpenHostService)&quot; data-ke-size=&quot;size20&quot;&gt;5-3-6. 오픈 호스트 서비스 (Open Host Service)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;어떤 어플리케이션에서 여러 서브시스템을 통합하려 할 때, 번역(내부와 외부의 완충지대)을 위한 레이어를 만든다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약, N개의 서브시스템을 통해 데이터를 얻어온다면, N개마다 번역레이어가 필요할 것이고 유사한 코드가 반복될 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;오픈 호스트 서비스는 외부 서브시스템을 서비스의 제공자로 바라보는 관점으로, 외부 서브시스템을 서비스로 감싼다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;자신의 시스템에 접근할 수 있는 프로토콜(ex. http, rpc, socket)을 서비스의 집합으로 정의하고 호환프로토콜을 공개하라.&lt;/li&gt;
&lt;li&gt;고객-공급자 패턴에서는 고객이 요청을 해야하지만, 오픈호스트서비스 패턴은 요청이 예상될만한 것들을 미리 만들어 제공하는 느낌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;어떤 예외적인 서브시스템에는 일회용 번역기를 사용하라&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;DDDQuickly-5-4.증류(Distillation)&quot; data-ke-size=&quot;size23&quot;&gt;5-4. 증류 (Distillation)&lt;/h3&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;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;/li&gt;
&lt;li&gt;핵심(core) 도메인과 서브(generic) 도메인을 분리해내는데 많은 노력을 쏟아라.
&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;/li&gt;
&lt;li&gt;개발자들은 인프라스트럭처, 최신기술에 매력을 느끼지만, 이것보다 도메인이 더욱 중요하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자 입장 ex) 비행도메인을 잘 아는게 개발자에게 뭐가 중요하겠는가? 프로젝트 끝나면 아무 의미가 없다.&lt;/li&gt;
&lt;li&gt;도메인 비즈니스는 도메인의 심장이고 핵심 설계나 구현의 실수는 프로젝트의 실패로 귀결된다.&lt;/li&gt;
&lt;li&gt;아무리 멋진 기술적 부가기능(인프라스트럭처, 기술스택 등)은 핵심 비즈니스가 제 역할을 하지못한다면 모두 무용지물이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;서브도메인 작업에 핵심 개발자를 투입X. 도메인 관련 지식을 얻을 수 없기 때문이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;서브도메인(ex. 검색)을 구현하는데 아래 방법들을 검토해봐라
&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;li&gt;사내(In-House) 개발 : 높은 수준의 통합을 이룰 수 있으나, 비용이 많이 든다.&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;DDDQuickly-6장.에릭에반스인터뷰)오늘날DDD는중요하다.&quot; data-ke-size=&quot;size26&quot;&gt;6장. 에릭 에반스 인터뷰) 오늘날 DDD는 중요하다.&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;cf) InfoQ에서 이 책을 낸 것은 2011년이다. 아래는 에릭 에반스가 답하는 내용의 요약이다.&lt;/li&gt;
&lt;li&gt;도메인에 집중해야하는 것은 시대와 상관없이 가장 중요하다.&lt;/li&gt;
&lt;li&gt;DDD를 적용하기 쉬운 개발플랫폼, 애자일 프로세스 환경으로 성숙되가고 있다.&lt;/li&gt;
&lt;li&gt;새로운 기술이나 프로세스는 도메인에 좀 더 집중하기 좋아졌느냐로 평가해야한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;90년대말~2000년초 J2EE, EJB 시절 도메인 모델링이 거의 불가능했던 암흑기였으나,&lt;/li&gt;
&lt;li&gt;2000년대 후반부터 등장하며 새로운 트렌드를 가져온 Spring 프레임워크, Hibernate, Ajax 를 극찬..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&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;컨텍스트 맵을 그리고 어느 부분엔 DDD를 적용하고, 경계 바깥에 있는 것들은 신경쓰지말아라&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;DDDQuickly-Spring프레임워크에서는얼마나도메인중심적일까?&quot; data-ke-size=&quot;size26&quot;&gt;Spring 프레임워크에서는 얼마나 도메인 중심적일까?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Spring 에서 어떤 상황에서는 도메인 모델에 넣고 싶어도, bean 이 아니기 때문에 넣을 수 없는 문제가 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;예를들어, phase 별로 imageDomain 이 다르다고 해보자.&lt;/li&gt;
&lt;li&gt;User 도메인모델에서 프로필이미지 url 을 얻으려면 아래와 같은 식일 것이다.
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;highlighter_229154&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1642364840947&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Component
class ApplicationEnvironment {
    @Value(&quot;${imageDomain}&quot;)
    private String imageDomain;
}
 
class User {
    public getProfileImageUrl() {
        // imageDomain 을 생성자나 setter 로 외부에서 주입받음. 아니면 해당 메소드의 인자로 받거나
        return imageDomain + this.profileImagePath;
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;도메인 모델에서 imageDomain 을 외부에서 주입받을 수 있으나, 불편하기 때문에 구조적으로 도메인 중심적 사고를 못하게 방해한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;static + lazy holder 등으로 우아하진 않으나, 우회하는 방안이 있긴하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;Spring 2.0 부터는 이것을 AspectJ 의 weaving 을 이용하여 주입해준다..
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;사실 Spring 에서 이게 안되는줄알고, 한계에 대한 이야기로 마무리하려 했는데 release note 에 있길래 놀랐다..&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-framework/docs/2.0.x/reference/new-in-2.html&quot;&gt;https://docs.spring.io/spring-framework/docs/2.0.x/reference/new-in-2.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;Section 6.8.1, &quot;Using AspectJ to dependency inject domain objects with Spring&quot;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;도메인 모델에 bean 을 주입하려면 아래와 같이 해야한다.&lt;br /&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://docs.spring.io/spring-framework/docs/5.3.15/reference/html/core.html#aop-atconfigurable&quot;&gt;https://docs.spring.io/spring-framework/docs/5.3.15/reference/html/core.html#aop-atconfigurable&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;
&lt;pre id=&quot;code_1642364886574&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Configurable
class User {
    @Autowired
    private ApplicationEnvironment environment;
 
    public getProfileImageUrl() {
        return environment.getImageDomain() + this.profileImagePath;
    }
}
 
// 아래와 같은 설정이 필요하다
@EnableAspectJAutoProxy // = &amp;lt;aop:aspectj-autoproxy /&amp;gt;
@EnableSpringConfigured // = &amp;lt;context:spring-configured /&amp;gt;
@EnableLoadTimeWeaving // &amp;lt;context:load-time-weaver /&amp;gt;&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li&gt;debug 해보면, 잘 주입된 것을 확인할 수 있다.&lt;/li&gt;
&lt;li&gt;&lt;figure class=&quot;imageblock alignLeft&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;주입성공.png&quot; data-origin-width=&quot;395&quot; data-origin-height=&quot;149&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dz6JR8/btrqNMiBX6U/4hxCIXtnIYKPNvsIdXqpB0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dz6JR8/btrqNMiBX6U/4hxCIXtnIYKPNvsIdXqpB0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dz6JR8/btrqNMiBX6U/4hxCIXtnIYKPNvsIdXqpB0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fdz6JR8%2FbtrqNMiBX6U%2F4hxCIXtnIYKPNvsIdXqpB0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;395&quot; height=&quot;149&quot; data-filename=&quot;주입성공.png&quot; data-origin-width=&quot;395&quot; data-origin-height=&quot;149&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;대부분의 한글문서에서는 위의 weaving을 하려면 -javaagent 를 jvm 실행인자로 꼭 넘겨야한다고 이야기하는데, spring-aspects.jar 가 있다면 ClassLoader 가 알아서 해주는 듯하다. (위 테스트에서도 설정하지 않음)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;aop.xml 도 언급이 많은데, spring-aspects.jar 에 기본으로 포함되어있다. 별도 설정이 필요할때만 검토해보면 될 것 같다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;다만, 위와 같은 경우를 빼고 CUD같은 것도 도메인 모델에서 다뤄야할까? 에 대해선 회의적이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;User 도메인 객체가 스스로 인프라스트럭처 레이어를 통해 영속화시킨다. 는 말은 되지만 객체간 종속성이나 트랜잭션 처리 등이 복잡해질 것 같다. 또한, Update를 해야하므로 불변으로 쓸 수도 없어진다.&lt;/li&gt;
&lt;li&gt;위와 관련된 질문도 참고해볼만 하다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;&lt;a href=&quot;https://stackoverflow.com/questions/20185808/java-where-should-i-put-my-domain-object-logic/20189373&quot;&gt;https://stackoverflow.com/questions/20185808/java-where-should-i-put-my-domain-object-logic/20189373&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;이외에도, 이 책에서 다루진 않지만 도메인 이벤트에 대해서도 스프링은 지원한다. 전반적으로, Spring 진영이 DDD에게 많은 영향을 받았고, feature들을 지원하기 위해 노력해온 듯하다.
&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://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/DomainEvents.html&quot;&gt;https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/DomainEvents.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/AfterDomainEventPublication.html&quot;&gt;https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/AfterDomainEventPublication.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>디자인패턴</category>
      <category>AspectJ</category>
      <category>CI</category>
      <category>ddd</category>
      <category>domain</category>
      <category>Entity</category>
      <category>Factory</category>
      <category>Model</category>
      <category>MVC</category>
      <category>Qbiquitous</category>
      <category>spring</category>
      <author>빨간색소년</author>
      <guid isPermaLink="true">https://sjh836.tistory.com/191</guid>
      <comments>https://sjh836.tistory.com/191#entry191comment</comments>
      <pubDate>Mon, 17 Jan 2022 05:35:33 +0900</pubDate>
    </item>
    <item>
      <title>Spring Data JPA 살펴보기 (JpaRepository, Bean으로 만들어지는 원리, 메소명으로 쿼리 만들기)</title>
      <link>https://sjh836.tistory.com/190</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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://docs.spring.io/spring-data/jpa/docs/2.5.2/reference/html/#reference&quot;&gt;https://docs.spring.io/spring-data/jpa/docs/2.5.2/reference/html/#reference&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://arahansa.github.io/docs_spring/jpa.html&quot;&gt;https://arahansa.github.io/docs_spring/jpa.html&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;spring_data_jpa.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;541&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/TMGdK/btrpU0gQiFD/ZI88KKgKa0FaGjykQ26IR0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/TMGdK/btrpU0gQiFD/ZI88KKgKa0FaGjykQ26IR0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/TMGdK/btrpU0gQiFD/ZI88KKgKa0FaGjykQ26IR0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FTMGdK%2FbtrpU0gQiFD%2FZI88KKgKa0FaGjykQ26IR0%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;259&quot; height=&quot;350&quot; data-filename=&quot;spring_data_jpa.png&quot; data-origin-width=&quot;400&quot; data-origin-height=&quot;541&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h2 id=&quot;SpringDataJPA-1.개요&quot; data-ke-size=&quot;size26&quot;&gt;1. 개요&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Repository 추상화를 통해 interface 선언만으로도 JPA 사용 가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;만약, Spring Data JPA 를 사용하지 않는다면, javax.persistence 패키지의 EntityManager 를 사용하거나 JPQL을 이용해서 쿼리를 날렸어야 했음.&lt;/li&gt;
&lt;li&gt;JpaRepository&amp;lt;T, ID&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;상속&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;메소드 이름으로 쿼리 생성 지원&lt;/li&gt;
&lt;li&gt;이외에도 다양한 지원(페이징, 정렬, 도메인 클래스 변환)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;SpringDataJPA-2.JpaRepository&amp;lt;T,ID&amp;gt;살펴보기&quot; data-ke-size=&quot;size26&quot;&gt;2.&lt;span&gt;&amp;nbsp;&lt;/span&gt;JpaRepository&amp;lt;T, ID&amp;gt;&lt;span&gt;&amp;nbsp;&lt;/span&gt;살펴보기&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JpaRepository는 CrudRepository, PagingAndSortingRepository 등을 상속받음&lt;/li&gt;
&lt;li&gt;&amp;lt;S extends T&amp;gt; S save(S entity)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: insert/update&lt;/li&gt;
&lt;li&gt;T findOne(ID id)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: select&lt;/li&gt;
&lt;li&gt;long count()&lt;span&gt;&amp;nbsp;&lt;/span&gt;: count&lt;/li&gt;
&lt;li&gt;void delete(ID id)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: delete&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;SpringDataJPA-2-1.JpaRepository상속한인터페이스가SpringBean에어떻게등록될까?&quot; data-ke-size=&quot;size23&quot;&gt;2-1. JpaRepository 상속한 인터페이스가 Spring Bean 에 어떻게 등록될까?&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;인터페이스 타입의 인스턴스는 누가 만들어 주는것인가? (Impl 도 없는데..)&lt;/li&gt;
&lt;li&gt;JpaRepository 를 상속받은 클래스는 Autowired 처럼 DI 되는것도 아닌 것 같고, 구현체를 정의한 적도 없는데, 해당 타입의 Bean 이 만들어지고 등록된다. 어떻게 가능한 것인지?&lt;/li&gt;
&lt;li&gt;리플렉션 패키지 내부의 Proxy 클래스를 활용한다. 이것을 사용하기 쉽게, 스프링AOP 는 ProxyFactory 로 추상화해서 만들어두었음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;아래 spring data JPA는 아래의 RepositoryFactorySupport 를 사용해서, Proxy 객체를 bean 으로 등록한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-hasbody=&quot;true&quot; data-macro-name=&quot;code&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;highlighter_398675&quot;&gt;
&lt;pre id=&quot;code_1641391901673&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public abstract class RepositoryFactorySupport implements BeanClassLoaderAware, BeanFactoryAware {
    public &amp;lt;T&amp;gt; T getRepository(Class&amp;lt;T&amp;gt; repositoryInterface, RepositoryFragments fragments) {
        // Create proxy
        ProxyFactory result = new ProxyFactory();
        result.setTarget(target);
        result.setInterfaces(repositoryInterface, Repository.class, TransactionalProxy.class);
 
        // 생략...
    }
}&lt;/code&gt;&lt;/pre&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;h2 id=&quot;SpringDataJPA-3.메소드이름으로쿼리자동생성&quot; data-ke-size=&quot;size26&quot;&gt;3. 메소드이름으로 쿼리 자동생성&lt;/h2&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://docs.spring.io/spring-data/jpa/docs/2.5.2/reference/html/#repository-query-keywords&quot;&gt;https://docs.spring.io/spring-data/jpa/docs/2.5.2/reference/html/#repository-query-keywords&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;이름 규칙에 맞춰 interface 메소드를 선언하면 자동으로 쿼리를 생성해줌&lt;/li&gt;
&lt;li&gt;ex1)&lt;span&gt;&amp;nbsp;&lt;/span&gt;findByItemNameLike(String itemName)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: SELECT * FROM item WHERE itemName LIKE '%{itemName}';&lt;/li&gt;
&lt;li&gt;ex2)&lt;span&gt;&amp;nbsp;&lt;/span&gt;deleteByPriceBetween(int price1, int price2)&lt;span&gt;&amp;nbsp;&lt;/span&gt;: DELETE FROM item WHERE price BETWEEN {price1} AND {price2};&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;SpringDataJPA-5.페이징&quot; data-ke-size=&quot;size26&quot;&gt;5. 페이징&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Pageable : Spring Data 에서 제공하는 web support 중 하나
&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;/li&gt;
&lt;li&gt;컨트롤러에 인자로 추가 후 JpaRepository로 전달&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;SpringDataJPA-6.복잡한쿼리작성&quot; data-ke-size=&quot;size26&quot;&gt;6. 복잡한 쿼리 작성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPQL : 엔티티 객체를 조회하는 객체 지향 쿼리&lt;/li&gt;
&lt;li&gt;Criteria API : JPQL을 생성하는 빌더 클래스&lt;/li&gt;
&lt;li&gt;써드파티 라이브러리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;QueryDSL&lt;/li&gt;
&lt;li&gt;JOOQ&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring</category>
      <category>hibernate</category>
      <category>jooq</category>
      <category>JPA</category>
      <category>jpaRepository</category>
      <category>JPQL</category>
      <category>QueryDSL</category>
      <category>spring</category>
      <author>빨간색소년</author>
      <guid isPermaLink="true">https://sjh836.tistory.com/190</guid>
      <comments>https://sjh836.tistory.com/190#entry190comment</comments>
      <pubDate>Wed, 5 Jan 2022 23:15:20 +0900</pubDate>
    </item>
    <item>
      <title>JPA란 무엇인가 (Before JPA, 영속성 컨텍스트, Entity, JPQL, 트랜잭션, N+1 문제)</title>
      <link>https://sjh836.tistory.com/189</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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://hibernate.org/orm/documentation/5.4/&quot;&gt;https://hibernate.org/orm/documentation/5.4/&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html&quot;&gt;https://docs.jboss.org/hibernate/orm/5.4/userguide/html_single/Hibernate_User_Guide.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://docs.jboss.org/hibernate/jpa/2.2/api/overview-summary.html&quot;&gt;https://docs.jboss.org/hibernate/jpa/2.2/api/overview-summary.html&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;자바 ORM 표준 JPA 프로그래밍 (김영한 저)&lt;/li&gt;
&lt;li&gt;&lt;a href=&quot;https://www.slideshare.net/NHNFORWARD/2018-mybatis-jpa&quot;&gt;https://www.slideshare.net/NHNFORWARD/2018-mybatis-jpa&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;JPA-1.JPA란?&quot; data-ke-size=&quot;size26&quot;&gt;1. JPA 란?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Java Persistence API 의 약자로 java진영의 ORM 표준스펙이다.&lt;/li&gt;
&lt;li&gt;높은 생산성을 가져다주며,&lt;/li&gt;
&lt;li&gt;동아시아(한중일)를 제외하고는 Data Access 레이어에 JPA를 대부분 사용중&lt;/li&gt;
&lt;li&gt;JPA는 이미 검증된 기술&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-1-1.BeforeJPA&quot; data-ke-size=&quot;size23&quot;&gt;1-1. Before JPA&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JDBC&lt;/li&gt;
&lt;li&gt;Spring JdbcTemplate&lt;/li&gt;
&lt;li&gt;iBatis, MyBatis
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JDBC를 좀 더 편하게 사용할 수 있도록, 객체를 메소드(=SQL)와 맵핑함 (JPA는 테이블과 맵핑)&lt;/li&gt;
&lt;li&gt;SQL Mapper 이며 단순하면서도, SQL을 직접 다루어서 강력함&lt;/li&gt;
&lt;li&gt;소스코드와 SQL을 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;단점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;반복적인 코드, SQL을 개발자가 직접 작성&lt;/li&gt;
&lt;li&gt;SQL과 DBMS 벤더에 종속성&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-1-2.역사&quot; data-ke-size=&quot;size23&quot;&gt;1-2. 역사&lt;/h3&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;java 진영에서 하이버네이트를 기반으로 JPA 명세 작성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Hibernate (de facto)&lt;/li&gt;
&lt;li&gt;EclipseLink&lt;/li&gt;
&lt;li&gt;DataNuclues&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JPA 1.0 (JSR 220, 2006년) : 초기버전, 복합 키와 연관관계 기능 부족&lt;/li&gt;
&lt;li&gt;JPA 2.0 (JSR 317, 2009년) : 대부분의 ORM 기능 포함, JPA Criteria 추가&lt;/li&gt;
&lt;li&gt;JPA 2.1 (JSR 338, 2013년) : 스토어드 프로시저 접근, 컨버터, 엔티티 그래프 기능이 추가&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-1-3.단점&quot; data-ke-size=&quot;size23&quot;&gt;1-3. 단점&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;높은 학습 곡선&lt;/li&gt;
&lt;li&gt;JPQL의 한계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 인라인뷰&lt;/li&gt;
&lt;li&gt;다만, Native SQL 작성 지원&lt;/li&gt;
&lt;li&gt;CQRS(커맨드와 쿼리를 분리)로 극복 가능 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;JPA-2.JPA를사용하지않았을때(JPA의필요성의대두)&quot; data-ke-size=&quot;size26&quot;&gt;2. JPA를 사용하지 않았을 때 (JPA의 필요성의 대두)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;SQL 위주의 반복적인 개발
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;개발자가 코드수정시 항상 SQL 맵퍼적인 작업까지 해줘야함&lt;/li&gt;
&lt;li&gt;ex) 필드 추가 시 SQL 모두 수정필요&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;특정 벤더, DBMS 에 강결합
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex) 페이징 처리시 Oracle은 ROWNUM, MySQL은 LIMIT 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체와 RDB의 차이에 따른 제약
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;상속
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;생성 : RDB는 INSERT를 두번 쳐야함&lt;/li&gt;
&lt;li&gt;조회 : 1대1 JOIN을 통해 구현&lt;/li&gt;
&lt;li&gt;위 SQL들이 복잡해지다보니, RDB에서 상속구조를 잘사용치않고 풀어써버림&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;연관관계
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JOIN을 통해 표현 가능하나, RDB table에는 FK 칼럼만 있음&lt;/li&gt;
&lt;li&gt;보통, 객체모델에 이 FK 칼럼명으로 필드를 선언함&lt;/li&gt;
&lt;li&gt;즉, 객체를 테이블에 맞춰서 모델링하게 됌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;객체 그래프
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;객체 모델이 정의되어있어도, SQL 로 Select 해서 채우지않는다면, null 이 들어있음. 엔티티 신뢰의 문제&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;데이터 타입&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;결론적으로, 객체답게 모델링할 수록, SQL 맵핑작업이 매우 늘어나게 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;JPA-3.JPA의내부동작방식&quot; data-ke-size=&quot;size26&quot;&gt;3. JPA의 내부 동작 방식&lt;/h2&gt;
&lt;h3 id=&quot;JPA-3-1.JPA는JDBCAPI를사용&quot; data-ke-size=&quot;size23&quot;&gt;3-1. JPA는 JDBC API를 사용&lt;/h3&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jpa_jdbc.png&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;772&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnRgO4/btrpHGQzNDM/YrtgNl8yCqbdMUzp9RqhEK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnRgO4/btrpHGQzNDM/YrtgNl8yCqbdMUzp9RqhEK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnRgO4/btrpHGQzNDM/YrtgNl8yCqbdMUzp9RqhEK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnRgO4%2FbtrpHGQzNDM%2FYrtgNl8yCqbdMUzp9RqhEK%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1618&quot; height=&quot;772&quot; data-filename=&quot;jpa_jdbc.png&quot; data-origin-width=&quot;1618&quot; data-origin-height=&quot;772&quot;/&gt;&lt;/span&gt;&lt;/figure&gt;
&lt;/p&gt;
&lt;h3 id=&quot;JPA-3-2.영속성컨텍스트&quot; data-ke-size=&quot;size23&quot;&gt;3-2. 영속성 컨텍스트&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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;Map&amp;lt;Id, Entity&amp;gt; 으로 관리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;엄밀하게 말하면&lt;span&gt;&amp;nbsp;&lt;/span&gt;entityManager.persist(member);&lt;span&gt;&amp;nbsp;&lt;/span&gt;를 실행하면, DB에 저장하는게 아니라 영속성 컨텍스트에 저장한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션이 커밋되는 시점에 DB로 쿼리가 날라가는 것. (flush 된다고 함)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;엔티티의 생명주기
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;비영속(new/transient): 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태&lt;/li&gt;
&lt;li&gt;영속(managed): 영속성 컨텍스트에 관리되는 상태&lt;/li&gt;
&lt;li&gt;준영속(detached): 영속성 컨텍스트에 저장되었다가 분리된 상태&lt;/li&gt;
&lt;li&gt;삭제(removed): 삭제된 상태&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-ke-mobileStyle=&quot;widthOrigin&quot; data-filename=&quot;jpa_entity.png&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;1106&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bnALzg/btrpPE7sPfM/s4kqOkgt8IimcL3A5m45m1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bnALzg/btrpPE7sPfM/s4kqOkgt8IimcL3A5m45m1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bnALzg/btrpPE7sPfM/s4kqOkgt8IimcL3A5m45m1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbnALzg%2FbtrpPE7sPfM%2Fs4kqOkgt8IimcL3A5m45m1%2Fimg.png&quot; onerror=&quot;this.onerror=null; this.src='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png'; this.srcset='//t1.daumcdn.net/tistory_admin/static/images/no-image-v1.png';&quot; loading=&quot;lazy&quot; width=&quot;1476&quot; height=&quot;1106&quot; data-filename=&quot;jpa_entity.png&quot; data-origin-width=&quot;1476&quot; data-origin-height=&quot;1106&quot;/&gt;&lt;/span&gt;&lt;/figure&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;span style=&quot;color: #000000;&quot;&gt;엔티티의 생명주기에 따라 특정 이벤트를 처리하기&lt;/span&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;a href=&quot;https://www.baeldung.com/jpa-entity-lifecycle-events&quot;&gt;https://www.baeldung.com/jpa-entity-lifecycle-events&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;@PostLoad, @PrePersist, @PreUdate, @PreRemove, @PostPersist, @PostUpdate, @PostRemove엔티티의 생명주기&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;영속성 컨텍스트가 있음으로서 얻는 장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1차캐시와 동일성(identity) 보장
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;동일한 트랜잭션에서 조회한 엔티티는 같음을 보장한다.&lt;span&gt;&amp;nbsp;&lt;/span&gt;member1 == member2&lt;/li&gt;
&lt;li&gt;DB Isolation Level 이 Read Commit 이어도, 어플리케이션에서 Repeatable Read 보장&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;트랜잭션을 지원하는 쓰기 지연
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;트랜잭션 커밋할때까지 버퍼에 INSERT 쿼리 모았다가 배치 전송&lt;/li&gt;
&lt;li&gt;UPDATE, DELETE 은 row lock 최소화하는 효과&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;변경 감지(Dirty Checking)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;setter 로 엔티티모델이 수정되면, 컨텍스트 내 스냅샷과 비교해보고, flush 될때 update SQL 로 실행&lt;/li&gt;
&lt;li&gt;변경된 필드만 update set 하지는 않고, 전체 필드를 update한다.
&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;/li&gt;
&lt;li&gt;영속 상태의 엔티티에만 적용된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;지연 로딩과 즉시 로딩
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;지연로딩 : 객체가 실제 사용될 때 로딩
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ex)&lt;span&gt;&amp;nbsp;&lt;/span&gt;member.getTeam().getName();&lt;span&gt;&amp;nbsp;&lt;/span&gt;// name 을 참조할 때,&lt;span&gt;&amp;nbsp;&lt;/span&gt;SELECT * FROM TEAM&lt;span&gt;&amp;nbsp;&lt;/span&gt;쿼리 전송&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;즉시로딩 : JOIN SQL 로 한번에 연관된 객체까지 미리 조회&lt;/li&gt;
&lt;li&gt;상황에 따라 옵션을 통해 조정가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-3-3.flush&quot; data-ke-size=&quot;size23&quot;&gt;3-3. flush&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;영속성 컨텍스트의 변경 내용을 DB에 반영한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;flush 된다고, 영속성 컨텍스트를 비우지는 않는다. 동기화시키는 것 뿐&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;flush 의 실행순서
&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;수정된 엔티티는 update sql 을 만들어서 SQL저장소에 등록&lt;/li&gt;
&lt;li&gt;SQL저장소에 있는 insert, update, delete 쿼리들을 DB로 전송&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;flush 를 수행하는 방법
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;flush() 메소드 실행&lt;/li&gt;
&lt;li&gt;commit 하면 자동 호출됨&lt;/li&gt;
&lt;li&gt;jpql 쿼리 실행&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;li&gt;FlushModeType
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;AUTO : 커밋이나 쿼리(jpql)할 때 실행&lt;/li&gt;
&lt;li&gt;COMMIT : 커밋할 때만 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-3-4.2차캐시&quot; data-ke-size=&quot;size23&quot;&gt;3-4. 2차 캐시&lt;/h3&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;1차캐시 조회 시 없으면, 2차캐시를 조회하게된다. 기본 키를 기준으로 캐시한다.&lt;/li&gt;
&lt;li&gt;동시성 이슈때문에 항상 복사본을 반환해준다.&lt;/li&gt;
&lt;li&gt;영속성 컨텍스트가 다르면, 객체동일성을 보장하지 않는다.&lt;/li&gt;
&lt;li&gt;provider 로 ehcache, redis 등을 사용 가능&lt;/li&gt;
&lt;li&gt;SharedCacheMode 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;ALL : 모든 엔티티를 캐시&lt;/li&gt;
&lt;li&gt;NONE : 캐시 사용안함&lt;/li&gt;
&lt;li&gt;ENABLE_SELECTIVE : Cacheable(true)로 설정된 엔티티만 캐시를 적용&lt;/li&gt;
&lt;li&gt;DISABLE_SELECTIVE : 모든 엔티티를 캐시하는데 Cacheable(false)로 설정된 엔티티만 제외하여 캐시함&lt;/li&gt;
&lt;li&gt;UNSPECIFIED : JPA 구현체가 정의한 설정에 따름&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;JPA-4.HelloWorld!&quot; data-ke-size=&quot;size26&quot;&gt;4. Hello World!&lt;/h2&gt;
&lt;h3 id=&quot;JPA-4-1.설정관련&quot; data-ke-size=&quot;size23&quot;&gt;4-1. 설정 관련&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;dialect 설정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;JPA는 특정 벤더, DBMS에 종속되지 않음&lt;/li&gt;
&lt;li&gt;그러나, 각각의 DBMS가 제공하는 SQL 문법과 함수가 조금씩 다름
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;가변문자 데이터타입 : MySQL 은 VARCHAR, Oracle 은 VARCHAR2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;따라서, 구현체가 맵핑해놓은 dialect 를 설정하여 특정 DBMS의 고유한 기능들을 이용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;org.hibernate.dialect.MySQL5InnoDBDialect&lt;/li&gt;
&lt;li&gt;hibernate는 40여가지를 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;JpaTransactionManager
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;MyBatis에서는 DataSourceTransactionManager 를 사용했으나,&lt;/li&gt;
&lt;li&gt;JPA 에서는 자체 트랜잭션 매니저 사용&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-4-2.핵심코드&quot; data-ke-size=&quot;size23&quot;&gt;4-2. 핵심 코드&lt;/h3&gt;
&lt;pre id=&quot;code_1641228005065&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;EntityManagerFactory emf = Persistence.createEntityManagerFactory(&quot;unit명&quot;); // 어플리케이션 당 1개만 존재
EntityManager em = emf.createEntityManager(); // thread-safe 하지 않음 (한번쓰고 버려야함)
EntityTranscation tx = em.getTranscation(); // 모든 데이터 변경은 트랜잭션 안에서 실행해야함
tx.begin();
// 비즈니스 코드
tx.commit();
em.close()
emf.close();&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;JPA-4-3.EntityManagerFactory&quot; data-ke-size=&quot;size23&quot;&gt;4-3. EntityManagerFactory&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;EntityManager 를 생성하는 Factory 다.&lt;/li&gt;
&lt;li&gt;MyBatis 에서의 SqlSessionFactory 에 해당한다.&lt;/li&gt;
&lt;li&gt;어플리케이션 전체에서 하나만 생성해서 공유해야한다.&lt;/li&gt;
&lt;li&gt;Persistence 를 통해 생성가능
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Persistence.createEntityManagerFactory(&quot;unit명&quot;);&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-4-4.EntityManager&quot; data-ke-size=&quot;size23&quot;&gt;4-4. EntityManager&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;내부적으로 DB커넥션을 물고 동작한다.&lt;/li&gt;
&lt;li&gt;MyBatis 로 치면, SqlSession 이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;div data-hasbody=&quot;true&quot; data-macro-name=&quot;code&quot;&gt;
&lt;div&gt;
&lt;div&gt;
&lt;div id=&quot;highlighter_619706&quot;&gt;&amp;nbsp;&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;/div&gt;
&lt;pre id=&quot;code_1641228028817&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;public interface EntityManager {
    public &amp;lt;T&amp;gt; T find(Class&amp;lt;T&amp;gt; entityClass, Object primaryKey);
    public void persist(Object entity);
    public &amp;lt;T&amp;gt; T merge(T entity);
    public void remove(Object entity);
    // 생략...
}&lt;/code&gt;&lt;/pre&gt;
&lt;h3 id=&quot;JPA-4-5.CRUD&quot; data-ke-size=&quot;size23&quot;&gt;4-5. CRUD&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;저장:&lt;span&gt;&amp;nbsp;&lt;/span&gt;jpa.persist(member)&lt;/li&gt;
&lt;li&gt;조회:&lt;span&gt;&amp;nbsp;&lt;/span&gt;Member member = jpa.find(memberId)&lt;/li&gt;
&lt;li&gt;수정:&lt;span&gt;&amp;nbsp;&lt;/span&gt;member.setName(&quot;devljh&quot;)&lt;/li&gt;
&lt;li&gt;삭제:&lt;span&gt;&amp;nbsp;&lt;/span&gt;jpa.remove(member)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;JPA-5.Entity&quot; data-ke-size=&quot;size26&quot;&gt;5. Entity&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Entity : JPA를 이용해서 DB 테이블과 맵핑할 클래스&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-5-1.엔티티모델어노테이션&quot; data-ke-size=&quot;size23&quot;&gt;5-1. 엔티티 모델 어노테이션&lt;/h3&gt;
&lt;h4 id=&quot;JPA-5-1-1.기본&quot; data-ke-size=&quot;size20&quot;&gt;5-1-1. 기본&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@Entity&lt;span&gt;&amp;nbsp;&lt;/span&gt;: JPA가 관리할 객체임을 명시
&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;/li&gt;
&lt;li&gt;@Table&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 맵핑할 DB 테이블 이름을 명시&lt;/li&gt;
&lt;li&gt;@Id&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 기본 키(PK)&lt;/li&gt;
&lt;li&gt;@GeneratedValue&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 기본 키 맵핑 전략
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;strategy : 자동할당
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;TABLE : 채번 테이블을 사용&lt;/li&gt;
&lt;li&gt;SEQUENCE : DB 시퀀스를 사용&lt;/li&gt;
&lt;li&gt;IDENTITY : DB에 위임&lt;/li&gt;
&lt;li&gt;AUTO : 설정된 Dialect 에 따라 자동 선택 (MySQL은 IDENTITY, Oracle은 SEQUENCE)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;generator : 직접할당&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@Column&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 필드와 칼럼을 맵핑
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;name : 칼럼명&lt;/li&gt;
&lt;li&gt;nullable : NOT NULL 여부&lt;/li&gt;
&lt;li&gt;unique&lt;/li&gt;
&lt;li&gt;updatable&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@Temporal&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 날짜 타입 매핑
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DATE : 날짜만&lt;/li&gt;
&lt;li&gt;TIME : 시간만&lt;/li&gt;
&lt;li&gt;TIMESTAMP : 날짜+시간&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@Transient&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 특정 필드를 칼럼에 맵핑하지 않음&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;JPA-5-1-2.복합키&quot; data-ke-size=&quot;size20&quot;&gt;5-1-2. 복합키&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;복합키 설정 방식 2가지&lt;/li&gt;
&lt;li&gt;@IdClass
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Entity 레벨에서 선언&lt;/li&gt;
&lt;li&gt;필드레벨에서는&lt;span&gt;&amp;nbsp;&lt;/span&gt;@Id&lt;span&gt;&amp;nbsp;&lt;/span&gt;N개 선언&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@EmbeddedId
&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;span&gt;&amp;nbsp;&lt;/span&gt;@Embeddable&lt;span&gt;&amp;nbsp;&lt;/span&gt;선언&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;JPA-5-1-3.외래키(FK)&quot; data-ke-size=&quot;size20&quot;&gt;5-1-3. 외래키(FK)&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@JoinColumn&lt;/li&gt;
&lt;/ul&gt;
&lt;h4 id=&quot;JPA-5-1-4.연관관계설정,다중성&quot; data-ke-size=&quot;size20&quot;&gt;5-1-4. 연관관계 설정, 다중성&lt;/h4&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;@OneToOne&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 일대일(1:1)&lt;/li&gt;
&lt;li&gt;@OneToMany&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 일대다(1:N)
&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;Class targetEntity() default void.class;&lt;/li&gt;
&lt;li&gt;CascadeType[] cascade() default {};&lt;/li&gt;
&lt;li&gt;FetchType fetch() default LAZY;&lt;/li&gt;
&lt;li&gt;String mappedBy() default &quot;&quot;;&lt;/li&gt;
&lt;li&gt;boolean orphanRemoval() default false;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;@ManyToOne&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 다대일(N:1)&lt;/li&gt;
&lt;li&gt;@ManyToMany&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 다대다(N:M)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;JPA-5-2.연관관계&quot; data-ke-size=&quot;size26&quot;&gt;5-2. 연관관계&lt;/h2&gt;
&lt;h3 id=&quot;JPA-5-2-1.방향성&quot; data-ke-size=&quot;size23&quot;&gt;5-2-1. 방향성&lt;/h3&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;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;관계의 주인 설정 필요
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연관 관계의 주인은 외래 키(FK)가 있는 곳&lt;/li&gt;
&lt;li&gt;연관 관계의 주인이 아닌 경우, mappedBy 속성으로 연관 관계의 주인을 지정해야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;cf) 단방향으로 설정하는 것을 권장
&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;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-5-2-2.영속성전이(CascadeType)&quot; data-ke-size=&quot;size23&quot;&gt;5-2-2. 영속성 전이(CascadeType)&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;연관 관계가 설정된 Entity 간, 영속성 전이를 설정할 수 있음&lt;/li&gt;
&lt;li&gt;ALL : 모든 변경 상황에, 하나의 엔티티가 변경되면 연관관계에 있는 다른 엔티티도 변경된다&lt;/li&gt;
&lt;li&gt;PERSIST&lt;/li&gt;
&lt;li&gt;MERGE&lt;/li&gt;
&lt;li&gt;REMOVE&lt;/li&gt;
&lt;li&gt;REFRESH&lt;/li&gt;
&lt;li&gt;DETACH&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-5-2-3.FetchStartegy&quot; data-ke-size=&quot;size23&quot;&gt;5-2-3. Fetch Startegy&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하나의 Entity 가 로딩될 때, 연관 관계가 있는 다른 Entity 의 로딩 전략을 설정할 수 있음&lt;/li&gt;
&lt;li&gt;EAGER : 즉시로딩&lt;/li&gt;
&lt;li&gt;LAZY : 지연로딩 (default)&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;JPA-5-3.스키마자동생성&quot; data-ke-size=&quot;size26&quot;&gt;5-3. 스키마 자동생성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;hibernate.hbm2ddl.auto 속성&lt;/li&gt;
&lt;li&gt;개발장비에서만 사용할 것, 운영장비에서는 위험하다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;JPA-6.JPQL(JavaPersistenceQueryLanguage)&quot; data-ke-size=&quot;size26&quot;&gt;6. JPQL (Java Persistence Query Language)&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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://data-make.tistory.com/614&quot;&gt;https://data-make.tistory.com/614&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;테이블이 아닌 엔티티 객체를 대상으로 검색하는 객체지향 쿼리&lt;/li&gt;
&lt;li&gt;SQL을 추상화해서 특정 데이터베이스의 SQL에 의존하지 않음&lt;/li&gt;
&lt;li&gt;JPA는 JPQL을 분석한 후, 적절한 SQL을 만들어서 DBMS에 질의
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Dialect 만 수정해주면 됨&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-6-1.문법메모&quot; data-ke-size=&quot;size23&quot;&gt;6-1. 문법 메모&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;Select
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;테이블명 대신 엔티티명(@Entity(name=&quot;엔티티명&quot;))을 사용&lt;/li&gt;
&lt;li&gt;별칭 필수&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-6-2.fetchjoin&quot; data-ke-size=&quot;size23&quot;&gt;6-2. fetch join&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&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://stackoverflow.com/questions/17431312/what-is-the-difference-between-join-and-join-fetch-when-using-jpa-and-hibernate&quot;&gt;https://stackoverflow.com/questions/17431312/what-is-the-difference-between-join-and-join-fetch-when-using-jpa-and-hibernate&lt;/a&gt;&lt;/li&gt;
&lt;li&gt;페치조인은 연관관계까지 고려하여 select 절 작성&lt;/li&gt;
&lt;li&gt;일반조인은 연관관계를 고려하지 않고 select 절이 작성되기 때문에, 컬렉션이 채워지지 않게되며, null 이기 때문에 추가로딩이 발생함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;JPA-7.트랜잭션&quot; data-ke-size=&quot;size26&quot;&gt;7. 트랜잭션&lt;/h2&gt;
&lt;h3 id=&quot;JPA-7-1.낙관적Lock&quot; data-ke-size=&quot;size23&quot;&gt;7-1. 낙관적 Lock&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 모델에 버전 필드를 만들고&lt;span&gt;&amp;nbsp;&lt;/span&gt;@Version&lt;span&gt;&amp;nbsp;&lt;/span&gt;선언&lt;/li&gt;
&lt;li&gt;최초에 0으로 초기화되며, 변경이 있을때마다 1씩 증가&lt;/li&gt;
&lt;li&gt;엔티티 수정 시, 조회 시점의 버전과 수정 시점의 버전이 다르면 OptimisticLockException 발생&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-7-2.비관적Lock&quot; data-ke-size=&quot;size23&quot;&gt;7-2. 비관적 Lock&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;DBMS의 트랜잭션 락 매커니즘에 의존&lt;/li&gt;
&lt;li&gt;ex) select for update&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;JPA-8.설계시참고할것&quot; data-ke-size=&quot;size26&quot;&gt;8. 설계 시 참고할 것&lt;/h2&gt;
&lt;h3 id=&quot;JPA-8-1.N+1문제&quot; data-ke-size=&quot;size23&quot;&gt;8-1. N+1 문제&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;원인 : A엔티티를 조회하는데, A가&lt;span&gt;&amp;nbsp;&lt;/span&gt;List&amp;lt;B&amp;gt;를 갖고있어서, B를 즉시 또는 지연 로딩해오는데, N회 쿼리가 발생함. 즉, N+1 회 쿼리 발생&lt;/li&gt;
&lt;li&gt;해결
&lt;ol style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;하이버네이트 @BatchSize : 지정한 size만큼 SQL의 in절을 이용해서 한번에 조회한다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단점 : 패치전략의 변경, size 정적 고정&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;조인을 사용 : 페치 조인을 사용하면 연관관계를 함꼐 조회함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;단점 : 페이징API 사용불가능, 두개이상의 컬렉션부터는 페치 조인 불가능 (MultipleBagFetchException)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ol&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;JPA-8-2.도메인모델과엔티티모델의분리&quot; data-ke-size=&quot;size23&quot;&gt;8-2. 도메인 모델과 엔티티 모델의 분리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티 모델은 JPA의 어노테이션들이 붙을 수 밖에 없음&lt;/li&gt;
&lt;li&gt;도메인 모델은 가능한 모든 기술로부터 독립되는 것이 이상적
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;엔티티모델에서&lt;span&gt;&amp;nbsp;&lt;/span&gt;to도메인모델()&lt;span&gt;&amp;nbsp;&lt;/span&gt;같은 메소드를 통해 변환메소드 제공&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li&gt;엔티티 모델을 그대로 사용할 경우, 컨트롤러, 뷰 레이어까지 트랜잭션이나 지연로딩 등을 활용할 순 있으나, 권장하진 않음.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li&gt;1안) OSIV(Open Session In View)을 false로 설정하자&lt;/li&gt;
&lt;li&gt;2안) DTO 로 분리&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Java</category>
      <category>Entity</category>
      <category>hibernate</category>
      <category>JDBC</category>
      <category>JPA</category>
      <category>JPQL</category>
      <category>OSIV</category>
      <category>SQL</category>
      <author>빨간색소년</author>
      <guid isPermaLink="true">https://sjh836.tistory.com/189</guid>
      <comments>https://sjh836.tistory.com/189#entry189comment</comments>
      <pubDate>Tue, 4 Jan 2022 01:49:29 +0900</pubDate>
    </item>
    <item>
      <title>[스프링배치 완벽가이드] 2장 스프링 배치</title>
      <link>https://sjh836.tistory.com/188</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;2&quot;&gt;참조문서
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;3&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/index.html&quot;&gt;https://docs.spring.io/spring-batch/docs/current/reference/html/index.html&lt;/a&gt;&lt;/li&gt;
&lt;li data-line=&quot;4&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-batch&quot;&gt;https://github.com/spring-projects/spring-batch&lt;/a&gt;&lt;/li&gt;
&lt;li data-line=&quot;5&quot;&gt;&lt;a href=&quot;https://terasoluna-batch.github.io/guideline/5.0.0.RELEASE/en/Ch02_SpringBatchArchitecture.html&quot; target=&quot;_blank&quot; rel=&quot;noopener&quot;&gt;https://terasoluna-batch.github.io/guideline/5.0.0.RELEASE/en/Ch02_SpringBatchArchitecture.html&lt;/a&gt;&lt;/li&gt;
&lt;li data-line=&quot;5&quot;&gt;스프링 배치 완벽 가이드(마이클 미넬라, 에이콘)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-src=&quot;https://image.kyobobook.co.kr/images/book/xlarge/168/x9791161755168.jpg&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;862&quot; width=&quot;320&quot; height=&quot;400&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/dZ437u/btrctuhDB0C/X1ioCOHZJjzIznrINoe2Ik/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/dZ437u/btrctuhDB0C/X1ioCOHZJjzIznrINoe2Ik/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/dZ437u/btrctuhDB0C/X1ioCOHZJjzIznrINoe2Ik/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdZ437u%2FbtrctuhDB0C%2FX1ioCOHZJjzIznrINoe2Ik%2Fimg.jpg&quot; data-src=&quot;https://image.kyobobook.co.kr/images/book/xlarge/168/x9791161755168.jpg&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;862&quot; width=&quot;320&quot; height=&quot;400&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-line=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;초심자의 눈으로 이해한 내용을 정리해보았다. 책에 있는 내용을 기반으로 썼지만, 책에 없는 내용도 조금씩 적어보았다. 책은 꼭 사서 보시길 바랍니다..&lt;/p&gt;
&lt;h2 id=&quot;1-job%EA%B3%BC-step&quot; data-line=&quot;95&quot; data-ke-size=&quot;size26&quot;&gt;1. job과 step&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;96&quot;&gt;&amp;nbsp;&lt;figure class=&quot;imageblock alignCenter&quot; data-src=&quot;https://terasoluna-batch.github.io/guideline/5.0.0.RELEASE/en/images/ch02/SpringBatchArchitecture/Ch02_SpringBatchArchitecture_Architecture_ProcessFlow.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1443&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bK42FU/btrcj1gokHD/ZDrlzwQLE8PlcFKkUKEuk1/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bK42FU/btrcj1gokHD/ZDrlzwQLE8PlcFKkUKEuk1/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bK42FU/btrcj1gokHD/ZDrlzwQLE8PlcFKkUKEuk1/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbK42FU%2Fbtrcj1gokHD%2FZDrlzwQLE8PlcFKkUKEuk1%2Fimg.png&quot; data-src=&quot;https://terasoluna-batch.github.io/guideline/5.0.0.RELEASE/en/images/ch02/SpringBatchArchitecture/Ch02_SpringBatchArchitecture_Architecture_ProcessFlow.png&quot; data-origin-width=&quot;2048&quot; data-origin-height=&quot;1443&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/li&gt;
&lt;li data-line=&quot;97&quot;&gt;job은 단순하게 말하면 state machine 이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;98&quot;&gt;현재 상태를 나타내줌&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;99&quot;&gt;청크 처리, 트랜잭션 단위는 step 이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;100&quot;&gt;job은 1개 이상의 step 으로 구성된다. (가장 작은 단위)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;101&quot;&gt;step은 tasklet 기반(Tasklet 구현)과 chunk 기반(Item *)으로 나눌 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;102&quot;&gt;tasklet : execute 메소드가 호출되며, chunk 보다 간단함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;103&quot;&gt;독립적인 step을 실행할 때 주로 사용 (ex. 알림전송 등)&lt;/li&gt;
&lt;li data-line=&quot;104&quot;&gt;ETL 패턴을 tasklet 으로 각각의 step 으로 구현한다면 메모리에 모두 올렸다가 내려야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;105&quot;&gt;chunk : commit interval 만큼 읽고 쓴다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;106&quot;&gt;ItemReader - ItemProcessor - ItemWriter&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;107&quot;&gt;개별 step 의 독립성을 보장함으로써 얻는 이점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;108&quot;&gt;유연성&lt;/li&gt;
&lt;li data-line=&quot;109&quot;&gt;유지보수성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;110&quot;&gt;각 step 을 단위테스트할 수 있다.&lt;/li&gt;
&lt;li data-line=&quot;111&quot;&gt;재사용 가능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;112&quot;&gt;확장성 : step 병렬화&lt;/li&gt;
&lt;li data-line=&quot;113&quot;&gt;신뢰성 : 예외 발생 시, 해당 Item 을 retry하거나 skip 등을 할수 있음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2-jobrepository&quot; data-line=&quot;115&quot; data-ke-size=&quot;size26&quot;&gt;2. JobRepository&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;116&quot;&gt;현재 실행중인 job의 메타정보들을 RDB에 기록
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;117&quot;&gt;실행된 step, 현재 상태, 읽은 아이템, 처리된 아이템 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;118&quot;&gt;상황에 따라 JobExecution, StepExecution 에서 상태를 갱신&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3-joblauncher&quot; data-line=&quot;120&quot; data-ke-size=&quot;size26&quot;&gt;3. JobLauncher&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;121&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-stereotypes-parameters.png&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;343&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/br5N7f/btrb9GD9kCf/bpCKmDsKVVW49bSIslmnKk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/br5N7f/btrb9GD9kCf/bpCKmDsKVVW49bSIslmnKk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/br5N7f/btrb9GD9kCf/bpCKmDsKVVW49bSIslmnKk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fbr5N7f%2Fbtrb9GD9kCf%2FbpCKmDsKVVW49bSIslmnKk%2Fimg.png&quot; data-src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/job-stereotypes-parameters.png&quot; data-origin-width=&quot;663&quot; data-origin-height=&quot;343&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/li&gt;
&lt;li data-line=&quot;122&quot;&gt;job 실행 : job.execute&lt;/li&gt;
&lt;li data-line=&quot;123&quot;&gt;job 의 재실행 가능여부 검증&lt;/li&gt;
&lt;li data-line=&quot;124&quot;&gt;job의 실행방법 결정 : 현재 스레드에서 수행할지, 다른 스레드에서 실행할지&lt;/li&gt;
&lt;li data-line=&quot;125&quot;&gt;파라미터 유효성 검증&lt;/li&gt;
&lt;li data-line=&quot;126&quot;&gt;cf) spring boot 는 job을 바로 실행할 수 있어서 JobLauncher 를 안 다뤄도 된다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4-jobinstance&quot; data-line=&quot;128&quot; data-ke-size=&quot;size26&quot;&gt;4. JobInstance&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;129&quot;&gt;job의 이름과 JobParameters의 조합으로 unique 한 인스턴스&lt;/li&gt;
&lt;li data-line=&quot;130&quot;&gt;job이 실행될 때, 새로 생성되나, 이미 같은 조합으로 만들어진 인스턴스가 있다면, 새로 생성되지 않을 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;131&quot;&gt;ex) 실패한 job을 재실행(파라미터가 같을테니)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;132&quot;&gt;개발자가 설정을 통해 커스텀하게 unique 인스턴스 식별규칙을 바꿀 수는 있는 듯&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;5-jobexecution&quot; data-line=&quot;134&quot; data-ke-size=&quot;size26&quot;&gt;5. JobExecution&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;135&quot;&gt;job의 실행에 따른 메타 데이터를 운반한다.&lt;/li&gt;
&lt;li data-line=&quot;136&quot;&gt;job을 실행할 때마다 새로운 JobExecution 이 생성된다.&lt;/li&gt;
&lt;li data-line=&quot;137&quot;&gt;필드
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;138&quot;&gt;status : STARTED , FAILED , COMPLETED&lt;/li&gt;
&lt;li data-line=&quot;139&quot;&gt;startTime&lt;/li&gt;
&lt;li data-line=&quot;140&quot;&gt;endTime&lt;/li&gt;
&lt;li data-line=&quot;141&quot;&gt;exitStatus&lt;/li&gt;
&lt;li data-line=&quot;142&quot;&gt;createTime&lt;/li&gt;
&lt;li data-line=&quot;143&quot;&gt;lastUpdated&lt;/li&gt;
&lt;li data-line=&quot;144&quot;&gt;executionContext&lt;/li&gt;
&lt;li data-line=&quot;145&quot;&gt;failureExceptions&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;6-%EB%B3%91%EB%A0%AC%ED%99%94&quot; data-line=&quot;147&quot; data-ke-size=&quot;size26&quot;&gt;6. 병렬화&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;148&quot;&gt;가장 단순한 job 처리는 단일 스레드에서 처음부터 끝까지 처리되는 것이다.&lt;/li&gt;
&lt;li data-line=&quot;149&quot;&gt;그러나, 상황에 따라 병렬화해야할 수도 있다.&lt;/li&gt;
&lt;li data-line=&quot;150&quot;&gt;이 때, 다중스레드스텝, step병렬실행, 비동기 ItemProcessor - ItemWriter, 원격 chunk처리, 파티셔닝이 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;6-1-%EB%A9%80%ED%8B%B0%EC%8A%A4%EB%A0%88%EB%93%9C%EB%A1%9C-step-%EC%8B%A4%ED%96%89&quot; data-line=&quot;152&quot; data-ke-size=&quot;size23&quot;&gt;6-1. 멀티스레드로 step 실행&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;153&quot;&gt;각 chunk는 트랜잭션의 단위로, 독립적으로 처리된다.&lt;/li&gt;
&lt;li data-line=&quot;154&quot;&gt;어떤 step 내 chunk 처리를 멀티스레드로 병렬화하는 것이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;155&quot;&gt;step 설정에 TaskExecutor 를 지정&lt;/li&gt;
&lt;li data-line=&quot;156&quot;&gt;다만, thread safe 하게 Reader, Processor, Writer 가 구현되어야 함
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;157&quot;&gt;thread safe 하지 않다면 SynchronizedItemStreamReader 으로 감싸야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;158&quot;&gt;단점 : chunk 처리를 병렬화하면 실패 지점에서 재시작하는 것이 불가능해짐
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;159&quot;&gt;따라서 saveState 를 false로..&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;6-2-step%EC%9D%84-%EB%B3%91%EB%A0%AC-%EC%8B%A4%ED%96%89&quot; data-line=&quot;161&quot; data-ke-size=&quot;size23&quot;&gt;6-2. step을 병렬 실행&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;162&quot;&gt;하나의 job에 A 스텝과 B 스텝이 서로 관련이 없다면 병렬로 처리가 가능하다.&lt;/li&gt;
&lt;li data-line=&quot;163&quot;&gt;Split Flows 구성 : split 에 TaskExecutor 지정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;164&quot;&gt;A 스텝은 A스레드가 실행, B 스텝은 B스레드가 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;6-3-%EB%B9%84%EB%8F%99%EA%B8%B0-itemprocessor---itemwriter&quot; data-line=&quot;166&quot; data-ke-size=&quot;size23&quot;&gt;6-3. 비동기 ItemProcessor - ItemWriter&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;167&quot;&gt;어떤 step 내 특정 processor 나 writer 가 병목일 수 있다.&lt;/li&gt;
&lt;li data-line=&quot;168&quot;&gt;이러한 경우에, 이 부분만 비동기화시킬 수 있다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;169&quot;&gt;spring-batch-integration 의존성&lt;/li&gt;
&lt;li data-line=&quot;170&quot;&gt;AsyncItemProcessor : process 메소드가 Future 를 반환&lt;/li&gt;
&lt;li data-line=&quot;171&quot;&gt;AsyncItemWriter : future.get 을 통해 write 처리
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;172&quot;&gt;이거 안써도 되지만, future 를 직접 다뤄야함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;173&quot;&gt;결국, 특정 처리단위를 다른 스레드에 물리는 꼴이라, 특정 단위의 병목 상황에만 고려해볼만 하겠다.&lt;/li&gt;
&lt;li data-line=&quot;174&quot;&gt;스레드 단순 스위칭을 통한 스레딩모델은 최근 리액티브스트림 진영에 의해 대체되고 있는 추세인데, 이 AsyncItem* 도 마찬가지이겠다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;175&quot;&gt;배치의 특성과 맞물려 재밌는 논의들이 있는데..&lt;/li&gt;
&lt;li data-line=&quot;176&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-batch/issues/1008&quot;&gt;https://github.com/spring-projects/spring-batch/issues/1008&lt;/a&gt;&lt;/li&gt;
&lt;li data-line=&quot;177&quot;&gt;요약하면 유한의 데이터를 다루는 chunk지향 처리의 배치 패러다임에서 리액티브 스트림의 무한데이터는 다루는 대상부터가 잘못되었다는 이야기다.&lt;/li&gt;
&lt;li data-line=&quot;178&quot;&gt;(배치 코어를 아예 다시 작성해야한다는 것도..)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;6-4-%EC%9B%90%EA%B2%A9-chunk-%EC%B2%98%EB%A6%AC&quot; data-line=&quot;180&quot; data-ke-size=&quot;size23&quot;&gt;6-4. 원격 chunk 처리&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;181&quot;&gt;위의 3가지가 단일 jvm 에서 스레딩을 통한 병렬처리였다면, 6-4번과 6-5번은 분산노드 처리이다.&lt;/li&gt;
&lt;li data-line=&quot;182&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/remote-chunking.png&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;491&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/ZpvGn/btrcwwlAVux/JG0ZSmxPPgaCHSc3DGISiK/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/ZpvGn/btrcwwlAVux/JG0ZSmxPPgaCHSc3DGISiK/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/ZpvGn/btrcwwlAVux/JG0ZSmxPPgaCHSc3DGISiK/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FZpvGn%2FbtrcwwlAVux%2FJG0ZSmxPPgaCHSc3DGISiK%2Fimg.png&quot; data-src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/remote-chunking.png&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;491&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/li&gt;
&lt;li data-line=&quot;183&quot;&gt;master 노드에서 read한 Item을 메세지가 유실되지 않는 미들웨어(ex. MQ)를 통해 slave 노드에 전송하고, slave 노드에서 Processor (또는 Writer까지) 를 처리하는 방식이다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;184&quot;&gt;처리는 slave에서 하더라도, 완료여부를 master 에게 계속 알려주며 통신해야해서, 네트워크 통신량이 많을 수 있다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;185&quot;&gt;이미 존재하는 Processor, Writer 를 활용할 수 있는게 장점
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;186&quot;&gt;코드변경이 별로 없음&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id=&quot;6-5-%ED%8C%8C%ED%8B%B0%EC%85%94%EB%8B%9D&quot; data-line=&quot;188&quot; data-ke-size=&quot;size23&quot;&gt;6-5. 파티셔닝&lt;/h3&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;189&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/partitioning-overview.png&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;491&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/blz2lw/btrcgPOHPwE/bjF7PA5V6zYg9MhMOrO7L0/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/blz2lw/btrcgPOHPwE/bjF7PA5V6zYg9MhMOrO7L0/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/blz2lw/btrcgPOHPwE/bjF7PA5V6zYg9MhMOrO7L0/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2Fblz2lw%2FbtrcgPOHPwE%2FbjF7PA5V6zYg9MhMOrO7L0%2Fimg.png&quot; data-src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/partitioning-overview.png&quot; data-origin-width=&quot;642&quot; data-origin-height=&quot;491&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/li&gt;
&lt;li data-line=&quot;190&quot;&gt;6-4번과의 차이
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;191&quot;&gt;코드 작업이 꽤 필요 (partitionHandler 등)&lt;/li&gt;
&lt;li data-line=&quot;192&quot;&gt;메세지가 유실되지 않는 미들웨어(ex. MQ)를 사용하지 않아도 됨&lt;/li&gt;
&lt;li data-line=&quot;193&quot;&gt;마스터 노드가 워커의 스텝 수집을 위한 컨트롤러 역할만 함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;194&quot;&gt;워커의 step은 독립적으로 동작하며 각각 별도로 StepExecution 을 갖는다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;195&quot;&gt;StepExecution 을 별개로 가지므로, JobRepository 에 메타데이터들을 저장할 수 있게되고, MQ가 필요없어진 것이다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;196&quot;&gt;로컬 파티셔닝
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;197&quot;&gt;멀티스레드로 동작&lt;/li&gt;
&lt;li data-line=&quot;198&quot;&gt;6-1번과 비슷해보이나, 6-1번은 StepExecution 이 하나다. (가장 큰 차이점)&lt;/li&gt;
&lt;li data-line=&quot;199&quot;&gt;따라서, Reader 등의 thread safe 여부가 중요치 않다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;200&quot;&gt;원격 파티셔닝
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;201&quot;&gt;분산된 노드에서 동작&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;7-hello-world&quot; data-line=&quot;203&quot; data-ke-size=&quot;size26&quot;&gt;7. Hello World!&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;204&quot;&gt;의존성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;205&quot;&gt;spring-boot-starter-batch&lt;/li&gt;
&lt;li data-line=&quot;206&quot;&gt;spring-boot-starter-jdbc&lt;/li&gt;
&lt;li data-line=&quot;207&quot;&gt;h2&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;208&quot;&gt;@EnableBatchProcessing&lt;span&gt;&amp;nbsp;&lt;/span&gt;: 배치 인프라스트럭처를 부트스트랩하는 데 사용한다. 따라서 개발자는 바로 bean 을 주입받아서 사용이 가능하다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;209&quot;&gt;JobRepository&lt;/li&gt;
&lt;li data-line=&quot;210&quot;&gt;JobLauncher&lt;/li&gt;
&lt;li data-line=&quot;211&quot;&gt;JobExplorer : JobRepository 위에서 readonly 만 수행&lt;/li&gt;
&lt;li data-line=&quot;212&quot;&gt;JobRegistry : 특정한 Launcher 구현체를 사용할 때, job 을 찾는 용도&lt;/li&gt;
&lt;li data-line=&quot;213&quot;&gt;PlatfromTransactionManager : job 실행 전반에서 트랜잭션을 관리&lt;/li&gt;
&lt;li data-line=&quot;214&quot;&gt;JobBuilderFactory&lt;/li&gt;
&lt;li data-line=&quot;215&quot;&gt;StepBuilderFactory&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;216&quot;&gt;Step 구성&lt;/li&gt;
&lt;li data-line=&quot;216&quot;&gt;
&lt;pre id=&quot;code_1629214022418&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public Step step() {
    return this.stepBuilderFactory.get(&quot;step1&quot;).tasklet(new Tasklet() {
        @Override
        public RepeatStatus execute(StepContribution contribution, ChunkContext chunkContext) throws Exception {
            System.out.println(&quot;hello world!&quot;);
            return RepeatStatus.FINISHED; // CONTINUABLE 을 반환하면 다시 execute가 호출된다.
        }
    }).build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li data-line=&quot;229&quot;&gt;Job 구성&lt;/li&gt;
&lt;li data-line=&quot;229&quot;&gt;
&lt;pre id=&quot;code_1629214044946&quot; class=&quot;java&quot; data-ke-language=&quot;java&quot; data-ke-type=&quot;codeblock&quot;&gt;&lt;code&gt;@Bean
public Job job() {
    return this.jobBuilderFactory.get(&quot;job&quot;).start(step()).build();
}&lt;/code&gt;&lt;/pre&gt;
&lt;/li&gt;
&lt;li data-line=&quot;236&quot;&gt;스프링 부트는 ApplicationContext 내 모든 job 을 구동될 때 실행하므로, run 만해주면 실행된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;237&quot;&gt;내부적으로 스프링부트 autoconfigure의 BatchAutoConfiguration가 JobLauncherApplicationRunner 를 생성한다
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;238&quot;&gt;책에서는 JobLauncherCommandLineRunner 를 이야기하고 있지만, 2.3.0 부터 deprecated 되었다.&lt;/li&gt;
&lt;li data-line=&quot;239&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/issues/19442&quot;&gt;https://github.com/spring-projects/spring-boot/issues/19442&lt;/a&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;240&quot;&gt;CommandLineRunner 를 구현하던 것에서 ApplicationRunner 를 구현하는 쪽으로 변경&lt;/li&gt;
&lt;li data-line=&quot;241&quot;&gt;-- 같은 것도 raw String 으로 취급되어 넘어온게 거슬렸던 듯..?&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;242&quot;&gt;JobLauncherApplicationRunner 가 실행되지 않게하려면 spring.batch.job.enabled=false 를 설정해줘야 한다.&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;243&quot;&gt;JobLauncherApplicationRunner 은 jobNames 이 없다면 등록된 job을 반복문을 수행하며 일괄 실행한다.&lt;/li&gt;
&lt;li data-line=&quot;244&quot;&gt;cf) jobNames 관련해서는 N개 지정 실행을 더이상 지원하지 않는 쪽으로 논의가 되고있나보다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;245&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-boot/issues/23411&quot;&gt;https://github.com/spring-projects/spring-boot/issues/23411&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring</category>
      <category>Batch</category>
      <category>chunk</category>
      <category>job</category>
      <category>spring</category>
      <category>STEP</category>
      <author>빨간색소년</author>
      <guid isPermaLink="true">https://sjh836.tistory.com/188</guid>
      <comments>https://sjh836.tistory.com/188#entry188comment</comments>
      <pubDate>Wed, 18 Aug 2021 00:28:20 +0900</pubDate>
    </item>
    <item>
      <title>[스프링배치 완벽가이드] 1장 배치와 스프링</title>
      <link>https://sjh836.tistory.com/187</link>
      <description>&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;2&quot;&gt;참조문서
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;3&quot;&gt;&lt;a href=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/index.html&quot;&gt;https://docs.spring.io/spring-batch/docs/current/reference/html/index.html&lt;/a&gt;&lt;/li&gt;
&lt;li data-line=&quot;4&quot;&gt;&lt;a href=&quot;https://github.com/spring-projects/spring-batch&quot;&gt;https://github.com/spring-projects/spring-batch&lt;/a&gt;&lt;/li&gt;
&lt;li data-line=&quot;5&quot;&gt;스프링 배치 완벽 가이드(마이클 미넬라, 에이콘)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;p&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-src=&quot;https://image.kyobobook.co.kr/images/book/xlarge/168/x9791161755168.jpg&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;862&quot; width=&quot;320&quot; height=&quot;400&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/drbL60/btrb9FrKMbV/J53uCiXbr2taE0q2qV1Zf0/img.jpg&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/drbL60/btrb9FrKMbV/J53uCiXbr2taE0q2qV1Zf0/img.jpg&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/drbL60/btrb9FrKMbV/J53uCiXbr2taE0q2qV1Zf0/img.jpg&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FdrbL60%2Fbtrb9FrKMbV%2FJ53uCiXbr2taE0q2qV1Zf0%2Fimg.jpg&quot; data-src=&quot;https://image.kyobobook.co.kr/images/book/xlarge/168/x9791161755168.jpg&quot; data-origin-width=&quot;690&quot; data-origin-height=&quot;862&quot; width=&quot;320&quot; height=&quot;400&quot; data-ke-mobilestyle=&quot;widthOrigin&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 data-line=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;초심자의 눈으로 이해한 내용을 정리해보았다. 책에 있는 내용을 기반으로 썼지만, 책에 없는 내용도 조금씩 적어보았다. 책은 꼭 사서 보시길 바랍니다..&lt;/p&gt;
&lt;p data-line=&quot;9&quot; data-ke-size=&quot;size16&quot;&gt;&amp;nbsp;&lt;/p&gt;
&lt;h2 id=&quot;1-%EB%B0%B0%EC%B9%98-%EC%B2%98%EB%A6%AC%EB%8A%94-%EC%96%B8%EC%A0%9C&quot; data-line=&quot;14&quot; data-ke-size=&quot;size26&quot;&gt;1. 배치 처리는 언제?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;12&quot;&gt;배치 처리는 상호 작용이나 중단없이 유한한 양의 데이터를 일괄로 처리하는 것&lt;/li&gt;
&lt;li data-line=&quot;12&quot;&gt;실시간 처리가 불필요하거나 최선이 아니고, 주기적으로 반복해야할 때
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;16&quot;&gt;월별 거래명세서&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;17&quot;&gt;자원의 효율적 사용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;18&quot;&gt;데이터과학분야&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;2-%EB%B0%B0%EC%B9%98-%EC%97%AD%EC%82%AC&quot; data-line=&quot;20&quot; data-ke-size=&quot;size26&quot;&gt;2. 배치 역사&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;21&quot;&gt;과거에는 메인프레임 서버에서 코볼로 개별 배치들을 작업하고 수행&lt;/li&gt;
&lt;li data-line=&quot;22&quot;&gt;2007년 Accenture 의 도메인 지식 + Pivotal 의 스프링 기술을 결합하여 오픈소스 배치를 개발&lt;/li&gt;
&lt;li data-line=&quot;23&quot;&gt;2008년 3월 : 스프링배치 1.0 출시&lt;/li&gt;
&lt;li data-line=&quot;24&quot;&gt;2009년 4월 : 스프링배치 2.0
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;25&quot;&gt;jdk 1.5&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;26&quot;&gt;2014년 : 스프링배치 3.0
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;27&quot;&gt;JSR 352 구현&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;28&quot;&gt;2017년 : 스프링배치 4.0
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;29&quot;&gt;java config&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;3-%EB%B0%B0%EC%B9%98-%EC%96%B4%ED%94%8C%EB%A6%AC%EC%BC%80%EC%9D%B4%EC%85%98%EC%9D%84-%EA%B0%9C%EB%B0%9C%ED%95%A0-%EB%95%8C%EC%9D%98-%ED%8A%B9%EC%84%B1&quot; data-line=&quot;31&quot; data-ke-size=&quot;size26&quot;&gt;3. 배치 어플리케이션을 개발할 때의 특성&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;32&quot;&gt;사용성 : 오류 처리 및 유지보수가 쉬운가?
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;33&quot;&gt;job이 실패했을 때, 디버깅에 오랜시간을 쏟진 않을지?&lt;/li&gt;
&lt;li data-line=&quot;34&quot;&gt;테스트 용이성 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;35&quot;&gt;확장성 : 배치 어플리케이션은 왠만한 웹어플리케이션보다 대용량을 다룬다.&lt;/li&gt;
&lt;li data-line=&quot;36&quot;&gt;가용성 : 수행되어야만 하는 시간에 바로 수행이 가능한지?, 다른시스템에 영향은 없는지?&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;4-%EC%9E%90%EB%B0%94or-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B0%B0%EC%B9%98%EC%9D%98-%EC%9D%B4%EC%A0%90&quot; data-line=&quot;38&quot; data-ke-size=&quot;size26&quot;&gt;4. 자바(or 스프링) 배치의 이점&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;39&quot;&gt;유지보수성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;40&quot;&gt;배치는 다른 어플리케이션의 코드들보다 수명이 길다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;41&quot;&gt;밖으로 들어나지 않기때문&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;42&quot;&gt;테스트 용이성, 풍부한 API&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;43&quot;&gt;유연성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;44&quot;&gt;JVM 을 이용한 이식성 (기존의 코볼, C++ 등과 비교했을 때)&lt;/li&gt;
&lt;li data-line=&quot;45&quot;&gt;코드 공유능력 (POJO 재활용 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;46&quot;&gt;확장성
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;47&quot;&gt;과거의 메인프레임 방식이나, 커스텀하게 처리하던 방식은 병렬 처리를 하려면 고려할게 많음. 확장성과 안정성이 떨어짐&lt;/li&gt;
&lt;li data-line=&quot;48&quot;&gt;자바(or스프링) 배치는 단일 처리, 병렬 처리 등이 모두 가능함&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;49&quot;&gt;개발인력
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;50&quot;&gt;자바, 스프링 프레임워크를 기반&lt;/li&gt;
&lt;li data-line=&quot;51&quot;&gt;커뮤니티의 강력한 지원&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;52&quot;&gt;비용
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;53&quot;&gt;오픈소스&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;5-%EB%B0%B0%EC%B9%98-%ED%94%84%EB%A0%88%EC%9E%84%EC%9B%8C%ED%81%AC-%EC%A4%91-%EC%8A%A4%ED%94%84%EB%A7%81%EB%B0%B0%EC%B9%98%EB%A5%BC-%EC%93%B0%EB%A9%B4-%EB%AD%90%EA%B0%80-%EC%A2%8B%EB%82%98&quot; data-line=&quot;55&quot; data-ke-size=&quot;size26&quot;&gt;5. 배치 프레임워크 중 스프링배치를 쓰면 뭐가 좋나?&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;56&quot;&gt;ETL(추출=extract - 변환=transform - 적재=load) 패턴에 적합
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;57&quot;&gt;청크 기반 처리&lt;/li&gt;
&lt;li data-line=&quot;58&quot;&gt;다양한 확장 기능&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;59&quot;&gt;데이터 마이그레이션
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;60&quot;&gt;보통 1회성으로 대충 만들 가능성이 높으나, 스프링 배치를 사용한다면, 풍부한 지원(커밋수, 롤백 등)&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;61&quot;&gt;병렬 처리&lt;/li&gt;
&lt;li data-line=&quot;62&quot;&gt;워크로드 조정
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;63&quot;&gt;Spring Cloud Data Flow 등을 통해 GUI로 태스크 조정 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;6-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B0%B0%EC%B9%98%EC%9D%98-3%EA%B0%80%EC%A7%80-%EB%A0%88%EC%9D%B4%EC%96%B4&quot; data-line=&quot;65&quot; data-ke-size=&quot;size26&quot;&gt;6. 스프링 배치의 3가지 레이어&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;66&quot;&gt;&amp;nbsp;&lt;figure class=&quot;imageblock alignCenter&quot; data-src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/spring-batch-layers.png&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;338&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bdBKI3/btrcp1AzI8X/84QJcFoPK2iKOBIJJJtbnk/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bdBKI3/btrcp1AzI8X/84QJcFoPK2iKOBIJJJtbnk/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bdBKI3/btrcp1AzI8X/84QJcFoPK2iKOBIJJJtbnk/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbdBKI3%2Fbtrcp1AzI8X%2F84QJcFoPK2iKOBIJJJtbnk%2Fimg.png&quot; data-src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/spring-batch-layers.png&quot; data-origin-width=&quot;322&quot; data-origin-height=&quot;338&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/li&gt;
&lt;li data-line=&quot;67&quot;&gt;어플리케이션 : 개발자가 작성한 비즈니스 로직&lt;/li&gt;
&lt;li data-line=&quot;68&quot;&gt;코어 : 배치 도메인을 정의하는 모든 부분
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;69&quot;&gt;Job, Step 등&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;70&quot;&gt;인프라스트럭처 : 각종 reader, writer, 템플릿, 헬퍼
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;71&quot;&gt;IO다루기&lt;/li&gt;
&lt;li data-line=&quot;72&quot;&gt;job 실패 시 정책&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;73&quot;&gt;cf) 배치 프레임워크 내에는 스케줄링 기능이 없다.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2 id=&quot;7-%EC%8A%A4%ED%94%84%EB%A7%81-%EB%B0%B0%EC%B9%98-job-%EA%B0%9C%EC%9A%94&quot; data-line=&quot;75&quot; data-ke-size=&quot;size26&quot;&gt;7. 스프링 배치 job 개요&lt;/h2&gt;
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;76&quot;&gt;&lt;figure class=&quot;imageblock alignCenter&quot; data-src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/spring-batch-reference-model.png&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;294&quot; data-ke-mobilestyle=&quot;widthOrigin&quot;&gt;&lt;span data-url=&quot;https://blog.kakaocdn.net/dn/bsLKKz/btrcqyrf3Ld/KFimVCkt02YuqXHnKMRx5k/img.png&quot; data-phocus=&quot;https://blog.kakaocdn.net/dn/bsLKKz/btrcqyrf3Ld/KFimVCkt02YuqXHnKMRx5k/img.png&quot;&gt;&lt;img src=&quot;https://blog.kakaocdn.net/dn/bsLKKz/btrcqyrf3Ld/KFimVCkt02YuqXHnKMRx5k/img.png&quot; srcset=&quot;https://img1.daumcdn.net/thumb/R1280x0/?scode=mtistory2&amp;fname=https%3A%2F%2Fblog.kakaocdn.net%2Fdn%2FbsLKKz%2Fbtrcqyrf3Ld%2FKFimVCkt02YuqXHnKMRx5k%2Fimg.png&quot; data-src=&quot;https://docs.spring.io/spring-batch/docs/current/reference/html/images/spring-batch-reference-model.png&quot; data-origin-width=&quot;738&quot; data-origin-height=&quot;294&quot; data-ke-mobilestyle=&quot;widthOrigin&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;/li&gt;
&lt;li data-line=&quot;77&quot;&gt;job은 중단이나 상호작용없이 처음부터 끝까지 실행되는 처리&lt;/li&gt;
&lt;li data-line=&quot;78&quot;&gt;job은 여러개의 step 으로 구성&lt;/li&gt;
&lt;li data-line=&quot;79&quot;&gt;각 step 은 입력과 출력이 있음 (=chunk processing, 하나의 트랜잭션)
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;80&quot;&gt;ItemReader : 입력&lt;/li&gt;
&lt;li data-line=&quot;81&quot;&gt;ItemProcessor : Optional&lt;/li&gt;
&lt;li data-line=&quot;82&quot;&gt;ItemWriter : 출력&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;83&quot;&gt;step이 실패했을 때, 반복실행할 수도 있고 못할수도 있다.&lt;/li&gt;
&lt;li data-line=&quot;84&quot;&gt;job의 flow는 조건부일 수 있음
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;85&quot;&gt;ex) 총 점이 100점이 넘었을 경우에만 특정 step 실행&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;86&quot;&gt;JobLauncher 가 job을 실행핸다&lt;/li&gt;
&lt;li data-line=&quot;87&quot;&gt;현재 실행중인 job의 메타정보들은 JobRepository 에 저장된다.
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;88&quot;&gt;일반적으로 RDB 에 기록&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;li data-line=&quot;89&quot;&gt;job의 병렬화
&lt;ul style=&quot;list-style-type: disc;&quot; data-ke-list-type=&quot;disc&quot;&gt;
&lt;li data-line=&quot;90&quot;&gt;병렬 chunk/step 처리&lt;/li&gt;
&lt;li data-line=&quot;91&quot;&gt;원격 chunk 처리&lt;/li&gt;
&lt;li data-line=&quot;92&quot;&gt;파티셔닝&lt;/li&gt;
&lt;/ul&gt;
&lt;/li&gt;
&lt;/ul&gt;</description>
      <category>Spring</category>
      <category>Batch</category>
      <category>job</category>
      <category>spring</category>
      <category>STEP</category>
      <author>빨간색소년</author>
      <guid isPermaLink="true">https://sjh836.tistory.com/187</guid>
      <comments>https://sjh836.tistory.com/187#entry187comment</comments>
      <pubDate>Wed, 18 Aug 2021 00:24:53 +0900</pubDate>
    </item>
  </channel>
</rss>