카프카는 메시지 브로커의 일종으로 Producer, Topic, Consumer, Broker 로 구성된다. 

Producer를 이용하여 이벤트를 브로커의 토픽으로 발행하고 토픽을 구독하고 있는 Consumer 가 이벤트를 처리하는 구조를 갖는다. 

 

Producer

특정 토픽으로 이벤트를 발행하는 역할

여러개의 프로듀서가 동시에 한토픽으로 이벤트를 발행할 수 있다. 

Serializer를 이용하여 JSON, Avro, Protobuf 등 다양한 이벤트를 발행할 수 있다. 

필수 설정

  • bootstrap.servers : 브로커 설정
  • key.serializer 
  • value.serializer

메커니즘

1. ProducerRecord 생성

  • Key 지정 : 키를 해싱하여 파티션을 정하는데 사용됨
  • Partition 지정 : 특정 파티션을 지정하여 전송
  • Vlaue

2. RecordBatch

  • 전송할 레코드는 파티션별로 RecordBatch 에 모이고 별도의 스레드가 전송

3. ProducerRecord 전송

  • 파티션이 지정되지 않았다면 파티셔너로 전송되고 파티셔너가 파티션을 지정함

4. 브로커 응답

  • 성공시 토픽, 파티션, 오프셋을 담은 RecordMetadata 객체를 리턴

전송방법

  • Fire and forget : 전송 후 성공/실패 여부를 신경쓰지 않음, 재시도 할 수 없는 경우 유실가능, 
  • Synchronous : Future 를 이용하여 처리
  • Asynchronous : 콜백 함수 이용

acks

  • 얼마나 많은 파티션 레플리카가 해당  레코드를 받아야 하는지 결정
  • acks=0 
  • acks=1 : 리더 레플리카가 메시지를 받는 순간 응답
  • acks=all : 모든 in-sync 레플리카에 전달된 후 응답

순서보장

  • 파티션내에서 순서는 보장됨
  • 설정에 따라 순서가 변경될 수 있음 

Broker

이벤트, Topic, Partition 등을 저장하고 관리하기 위한 서버

여러개의 브로커에 파티션을 복제하여 고가용성을 확보할 수 있다

 

Topic

이벤트를 관리하기 위한 단위 

여러개의 파티션으로 구성될 수 있다

 

Partition

이벤트를 저장하는 물리적인 단위이며, 파티션당 한개의 컨슈머를 갖을 수 있다. 

여러개의 파티션을 이용하여 멀티 컨슈머 처리가 가능하다. 

각각의 이벤트는 offset으로 관리된다. 

이벤트는 저장소에 리텐션주기에 따라 저장되어 보관된다. 

Offset을 이용하여 이전 데이터의 재처리가 가능해진다.

 

Offset

프로듀서가 발행한 이벤트는 한개의 오프셋을 갖는다. 

이벤트가 증가함에 따라 오프셋도 증가한다. 

컨슈머는 오프셋 단위로 이벤트를 처리한다. 

 

Consumer

오프셋별로 토픽에서 이벤트를 가져와 처리하고 오프셋을 커밋한다. 

이벤트의 형식에 따라 Deserialize 한다. 

일반적으로 컨슈머는 컨슈머 그룹에 속함

 

컨슈머그룹

  • 파티션수 > 컨슈머수 : 여러 파티션을 하나의 컨슈머에서 처리하게 됨
  • 파티션수 = 컨슈머수 : 각각의 파티션당 하나의 컨슈머가 처리
  • 파티션수 < 컨슈머수 : 유휴 컨슈머가 있음

 

리밸런스

  • 컨슈머에 파티션을 재할당 하는 작업
  • eager rebalance : 모든 컨슈머가 작업을 멈추고 파티션을 재 할당 받음
  • cooperative rebalance : 점진적인 재할당

메커니즘

1. subscribe topic

2. 폴링 루프

3. poll

4. deserialize

5. offset commit

 

파티션 할당 전략(PartitionAssigner)

  • Range
  • RoundRobin
  • Sticky
  • Cooperative sticky

오프셋 커밋

  • 중복처리 : 1 ~ 10까지 오프셋을 처리하는 중 10번까지 처리 되었으나 5번 오프셋까지만 커밋된 상태에서 리밸런싱이 일어나는 경우 6 ~ 10은 중복 처리됨
  • 유실 :  1 ~ 10까지 오프셋을 처리하는 중 10번 오프셋이 커밋된 상태에서 리밸런싱이 일어나는 경우 
  • 자동커밋
    • enable.auto.commit=true
    • auto.commit.interval.ms=5000(default)
  • 동기 커밋 : commitSync 사용, 브로커로부터 응답이 올때까지 블록됨, 실패시 재시도.
  • 비동기 커밋 : commitAsync 사용, 응답을 기다리지 않고 다음 커밋을 바로 처리함, 재시도 하지 않음. 커밋 순서가 꼬일 수 있음
  • 현재 오프셋 커밋 : 현재 오프셋 + 1 의 오프셋이 커밋됨

스탠드얼론 컨슈머

컨슈머 그룹에 속하지 않고 특정 토픽을 컨슘처리

1. partitionsFor(topic) : 토픽의 파티션 정보 획득

2. assign(partitions) : 파티션 할당

3. poll

4. commit

 

콘트롤러

KRaft 

복제

리더레플리카

팔로워레플리카

선호리더

in sync replica

out of sync replica

 

계층화된저장소

  • 각 계층별 리텐션 주기설정
  • 로컬계층과 원격계층 독립적인 읽기 가능. 원격계층의 메시지를 캐싱하거나 카피하지 않고 바로 네트워크 계층 전송가능
  • 로컬계층 : 브로커 로컬 디스크
  • 원격계층 : HDFS / S3 등 원격저장소

파티션할당

  • 브로커 간에 고르게 분산
  • 서로다른 브로커에 배치
  • 랙 구분
  • 라운드 로빈

파일관리

  • 세그먼트 단위로 관리. 파티션을 여러개의 세그먼트로 분리
  • 액티브 세그먼트 : 현재 쓰여지고 있는 세그먼트, 삭제 안됨

파일형식

  • 세그먼트는 하나의 파일로 저장
  • 프로듀서 - 브로커 - 컨슈머 까지 전달되는 동일한 형태로 저장(제로카피 최적화)
  • 버전별 형식으로 변환하여 컨슈머 전달 (FetchMessageConversionsPerSec, MessageConversionsTimeMs 지표 확인, 클라이언트 업데이트)

인덱스

  • 오프셋 세그먼트 인덱스 : 오프셋 - 세그먼트 파일 - 파일내 위치
  • 타임스탬프 오프셋 인덱스 : 카프카 스트림즈에서 자주 사용

보존정책

  • delete : 리텐션기간이 지난 데이터 삭제
  • compact : 가장 최근 상태만 보관
    • clean
    • dirty
  • delete,compact : 보존기간이 지난 compact 메시지도 삭제

대상

  • spring boot application
  • mysql (docker)

모니터링 구성

  • prometheus
  • grafana
  • mysql_exporter

prometheus docker 설치

  • prometheus.yml boot application 설정
global:
  scrape_interval: 15s
scrape_configs:
  - job_name: boot
    metrics_path: '/actuator/prometheus'
    static_configs:
      - targets: ['ip:port']

ip : docker 내부에서 로컬 application 으로 접속 해야 하므로 localhost로는 접속이 안됨. ip 로 설정

port : application port

 

  • docker run
docker run -d -p 9090:9090 -v /파일경로/prometheus.yml:/etc/prometheus/prometheus.yml prom/prometheus

 

  • prometheus 콘솔 접속
    • http://localhost:9090/

grafana docker 설치

docker run --name grafana -d -p 80:3000 grafana/grafana
  • 접속 : http://localhost/
  • prometheus 데이터 소스 설정
    • http://localhost/connections/datasources 접속 하여 add new data source 버튼 클릭
    • prometheus 선택 
    • Name 입력
    • Prometheus Server URL 입력 : http://host.docker.internal:9090
      • localhost 로 설정 시 아래와 같은 에러로 접속 테스트 실패 
      • Post "http://localhost:9090/api/v1/query": dial tcp 127.0.0.1:9090: connect: connection refused - There was an error returned querying the Prometheus API.
    • 화면 끝에 Save & Test 버튼 클릭
  • 대시 보드 구성

mysql_exporter docker 설치

  • mysql_exporter 용 mysql 사용자(test_exp) 생성
CREATE USER 'test_exp'@'localhost' IDENTIFIED BY 'test_exp' WITH MAX_USER_CONNECTIONS 3;
GRANT PROCESS, REPLICATION CLIENT, SELECT ON *.* TO 'test_exp'@'localhost';
flush privileges;
  • /etc/mysql/my.cnf 파일 생성 (원하는 위치에 생성)
[client]
host=ip
port=3306
socket=/home/mysql.sock
user=test_exp
password=test_exp

 

  • docker run
    • mysql 이 docker로 떠 있는 상태에서 mysql.sock을 이용해 mysql_exporter가 mysql에 접속 해야 함
    • 따라서 mysql docker의 mysql.sock 을 mysql_exporter가 참조 하도록 해야 함
    • mysql docker의 mysql.sock은 /var/lib/mysql/mysql.sock 에 있지만, 심볼릭 링크로 걸려 있음 
    • 링크 : /var/lib/mysql/mysql.sock -> /var/run/mysqld/mysqld.sock
    • 링크 : /var/run -> /run
    • 결국 이 위치 : /run/mysqld/mysqld.sock
    • 이 파일을 mysql_exporter와 공유해야 함 
    • mysql docker 실행 시 -v /mornitoring/mysql_exporter:/run/mysqld 옵션을 주어 로컬의 /mornitoring/mysql_exporter 디렉토리로 공유하고
    • 이 디렉토리의 mysqld.sock 을 mysql_exporter와 공유하도록 함
docker run -itd -p 9104:9104 -v /etc/mysql/my.cnf:/home/.my.cnf -v /mornitoring/mysql_exporter/mysqld.sock:/home/mysql.sock --name mysql_exporter prom/mysqld-exporter --config.my-cnf=/home/.my.cnf
  • prometheus.yml mysql_exporter 설정 추가 후 prometheus 재시작
global:
  scrape_interval: 15s
scrape_configs:
  - job_name: mysql_exporter
    static_configs:
      - targets: ['ip:9104']
  • grafana 대시보드 구성

Prometheus 확인

  • http://localhost:9090/targets?search= 접속

 

 

 

 

  • 모델링 과정을 매우 복잡한 도메인까지 확장하는 원리
  • 성공적인 모델 : 규모와는 상관없이 모순되거나 정의가 겹치지 않고 처음부터 끝까지 논리적인 일관성 유지
  • Bounded Context 
  • Distillation
  • 대규모 구조 : 전혀 공통점이 없는 부분들이 맞물려 돌아가도록 일관성을 가져다 줌
    • Responsibility Layer
    • Evolving Order(발전하는 질서)
모델의 무결성 유지
  • 각 모델이 적용되는 경계를 분명하게 합의하는 것에서 출발
  • 단일화(unification) : 각 용어가 모호하지 않고 모순되는 규칙이 없는 모델의 내적 일관성
  • 다른 여러 모델 간의 경계와 관계를 표시해줄 수단 필요
    • Context Map : 프로젝트 분야를 매핑, 각 컨텍스트 간의 관계의 전체적인 개관
    • Bounded Context : 각 모델의 적용가능성의 범위 정의
    • Continuous Integration : 프로세스를 토대로 모델의 단일화 유지
    • Shared Kernel
    • Separate Ways
Bounded Context
  • 규모가 큰 프로젝트에는 다수의 모델이 공존하며, 서로 다른 모델은 서로 다른 컨텍스트에 적용된다
  • 한 팀 내에서도 다수의 모델이 공존할 수 있다
  • 다수의 모델 탓에 발생하는 문제의 해결 방법
    • 소프트웨어 내의 제한된 부분으로 특정 모델의 범위를 명확하게 정의
    • 팀의 구성과 조화
    • 컨텍스트의 경계를 팀 조직, 애플리케이션의 특정 부분에서의 사용법, 코드 기반이나 데이터베이스스키마와 같은 물리적인 형태의 관점에서 명시적으로 설정
    • 경계 내에서는 모델을 엄격하게 일관된 상태로 유지하고 경계 바깥의 이슈 때문에 초점이 흐려지거나 혼란스러워져서는 안된다
    • 모델을 책임지는 팀에서는 영속화를 비롯한 각 객체의 전체 생명주기를 다룬다

Bounded Context안의 균열 인식

  • 코드로 작성된 인터페이스가 서로 맞지 않는 경우
  • 예상치 못한 행위
  • 언어를 혼동한 상태로 구사
  • 중복개념 : 실제로 같은 개념을 나타내는 두개의 모델 요소가 존재하는 것
  • 허위동족언어(false cognates) : 같은 용어를 사용하는 두 사람이 서로 같은 것을 이야기하고 있다고 생각하지만 실제로는 그렇지 않은 경우, 개념상의 충돌
  • 해결방안
    • Continuous Integration
    • Context Map
    • Shared Kernel
    • Customer/Supplier Development Team
    • Conformist
    • Anticorruption Layer
    • Separate Ways
    • Open Host Service
    • Published Language

Continuous Integration

  • 내부적으로 균열이 발생할 때 이를 빠르게 포착하고 정정할 수 있을 정도로 컨텍스트 내의 모든 작업을 빈번하게 병합해서 일관성 유지
  • 모델 개념의 통합
    • 팀 구성원 간의 부단한 의사소통을 토대로 통합
  • 구현 수준에서의 통합
    • 모델내의 균열을 조기에 드러내는 체계적인 병합/빌드/테스트 프로세스를 토대로 통합
  • 프로세스
    • 단계적이고 재생 가능한 병합/빌드 기법    
    • 자동화된 테스트 스위트
    • 수정사항이 통합되지 않은 상태로 존재할 수 있는 시간을 적당히 짧게 유지하는 규칙

Context Map

  • Bounded Context간에 코드를 재사용하는 것음 위험하므로 기능과 데이터는 번역 과정을 거쳐 통합해야 한다.
  • 서로다른 컨텍스트간의 관계를 정의하고 프로젝트상의 모든 모델 컨텍스트를 아우르는 전체적인 뷰를 만들면 혼란을 줄일 수 있다
  • Context Map은 프로젝트 관리와 소프트웨어 설계 영역 사이에 걸쳐있는 개념
  • 의사소통을 위해 컨텍스트 간의 번역에 대한 윤곽을 명확하게 표현
  • 컨텍스트 간에 공유해야 하는 정보를 강조
  • 모델과 모델이 만나는 경계 지점 서술
  • Bounded Context간의 관계 패턴
    • Shared Kernel
    • Customer/Supplier(현재 가장 많이 적용되어 있는 패턴)
    • Separate Ways
    • Open Host Service
    • Anticorruption Layer(wrapper, translator, facade, service)

디스틸레이션

  • 혼합된 요소를 분리해서 본질을 좀더 값지고 유용한 형태로 뽑아내는 과정
  • Generic subdomain - 모델에서 가장 특색이 없는 측면 제거
  • Coherent Mechanism - 용도가 다양하고, 의미전달이 용이하며, 유연한 설계를 통해 캡슐화
  • Segregated Core
  • Abstract Core - 가장 근본적인 개념과 관계를 순수한 형태로 표현
  • Core Domain
    • Domain Vision Statement - 최소한의 투자로 기본개념과 가치를 
    • Highlighted Core - 의사소통을 향상시키고 의사결정에 도움

Core Domain

  • 도메인 모델을 가치있는 자산으로 만들려면 모델의 핵심적인 측면을 다루기 수월해야 하고 애플리케이션의 기능성을 만들어내는 데 충분히 활용할 수 있어야 한다
  • Core Domain은 시스템에서 가장 큰 가치가 더해지는 곳이다
  • 시스템의 비전을 수행하기에 충분한 심층 모델을 찾고 유연한 설계를 개발할 수 있게 코어에 노력을 쏟아라

Generic Subdomain

  • 모델은 일반 원칙(누구나 알고 있는것)이나 세부사항(보조적인 역할을 수행하는 전문 분야에 속하는 것) 탓에 정체된다
  • 개발 시 선택사항
    • 기성 솔루션(상용 또는 오픈소스)
    • 공표된 설계나 모델
    • 외주 제작
    • 사내구현
  • 일반화가 재사용 가능하다는 의미는 아니다
  • 필요한 만큼만 투자
  • 쉽기 때문에 보조 시스템을 먼저 구축할 경우 위험관리의 목적을 헛되게 할 수 있음

Domain vision Statement

  • 도메인 모델의 본질과 해당 도메인 모델이 얼마나 기업에 가치 있는가에 초점을 맞춘다
  • 팀 내에서 방향성을 공유하게 해 줌
  • 작성방법
    • Core Domain을 짧게 기술하고 그것이 가져올 가치에 해당하는 가치제안을 작성
    • 이 도메인 모델과 다른것과 구별하는데 도움되지 않는 측면은 무시
    • 도메인 모델이 어떻게 다양한 관심사를 충족하고 균형을 이루는지 보여라
    • 한정된 범위에서 내용을 유지하라
    • 초기에 이 선언문을 작성하고 새로운 통찰력을 얻을 때마다 선언문을 개정하라


Highlighted Core

  • 어떤 모델에서 특별히 중요한 부분을 그것을 구체화한 구현과 함께 표시할 필요가 있는데, 이러한 표시는 모델에 대한 설명이지 반드시 모델 자체의 한 부분일 필요는 없다
  • 기법
    • 디스틸레이션 문서 : Core Domain과 Core의 구성요소 사이에서 일어나는 상호작용을 기술하는(3에서 7페이지 가량의) 매우 간결한 문서를 작성하라
    • 표시된 Core : 모델의 주요 저장소 안에 있는 Core Domain의 구성요소에 대해 그것의 역할을 설명하려하지 말고 표시하라. 개발자가 힘들이지 않고도 Core의 안과 밖을 알 수 있게 하라

Cohesive Mechanism

  • 의도를 드러내는 이름이 이정된 메서드에 복잡한 알고리즘을 숨기면 "무엇"이 "어떻게"와 분리된다
  • 문제해결을 위한 알고리즘을 제공하는 수많은 메서드가 문제를 표현하는 메서드를 분분명하게 만드는 경우
    • 개념적으로 Cohesive Mechanism을 별도의 경량 프레임워크로 분할
    • Intention-Revealing Interface로 프레임워크의 기능을 노출
    • 해법의 복잡성("어떻게")을 프레임워크에 위임해서 문제("무엇")을 표현하는데 집중    

Segregated Core

  • 설계자가 가장 중요한 관계를 분명하게 볼 수 없다면 취약한 설계로 이어지는 결과가 나타난다
  • 모든 일반적이거나 보조적인 역할을 하는 구성요소를 다른 객체로 추출해서 다른 패키지에 배치하라
Abstract Core
  • 모델의 가장 근본적인 개념을 식별해서 그것을 별도의 클래스나 추상 클래스, 또는 인터페이스로 추출하라
  • 이 추상 모델이 중요 컴포넌트 간에 발생하는 상호작용을 대부분 표현할 수 있게끔 설계하라
  • 특화되고 세부적인 구현클래스는 하위 도메인을 기준으로 정의된 자체적인 모듈에 남겨둔 상태에서 이 추상적이면서 전체적인 모델을 자체적인 모듈에 배치하라

리팩토링의 대상선택

  • 문제의 근원에 Core Domain이나, Core 와 지원요소와의 관계가 있는지 살핀다. 그렇다면 가장먼저 고쳐야 한다
  • 마음껏 리팩토링할 수 있는 상황이라면 제일먼저 Core Domain을 더 잘 분해하고 Core의 격리를 개선하며, 보조적인 하위 도메인이 Generic하게 만드는 데 집중한다.


리팩터링은 엔트로피와의 싸움이며 레거시 시스템이 퇴보하는 것을 막는 최전선에 놓여 있다.


암시적인 개념을 명확하게

  • 도메인 전문가가 사용하는 언어에 귀 기울여라
    • 어색한 부분을 조사하라
    • 설명하기 힘들만큼 복잡한 작업을 수행하는 프로시저와 관련된 부분이나 새로운 요구사항 탓에 복잡성이 증가하는 부분
    • 모순점에 대해 깊이 고민하라
  • 서적을 참고하라
  • 시도하고 또 시도하라
    • 모델러/설계자는 자신의 아이디어에 집착해서는 안된다
다소 불명확한 개념을 모델링하는 법
1. 명시적인 제약조건
  • 제약조건을 별도의 메서드로 분리하고 제약조건의 의미를 분명하고 명확하게 표현할 수 있게 메서드의 이름을 짓는다
  • 부여된 이름을 사용해서 제약조건에 관한 토의가 가능
  • 잘못된 제약조건 설계의 식별
    • 제약조건을 평가하려면 해당 객체의 정의에 적합하지 않은 데이터가 필요하다
    • 관련된 규칙이 여러 객체에 걸쳐 나타나며, 동일한 계층구조에 속하지 않는 객체 간에 중복 또는 상속 관계를 강요한다
    • 설계와 요구사항에 관한 다양한 논의는 제약조건에 초점을 맞춰 이뤄지지만 정작 구현단계에서는 절차적인 코드에 묻혀 명시적으로 표현되지 않는다.
2. 도메인 객체로서의 프로세스
  • 객체는 절차를 캡슐화해서 절차 대신 객체의 목표 의도에 관해 생각하게 해야 한다
  • 어떤 프로세스를 선택할 것인가는 곧 어떤 객체를 선택할 것인가가 되고, 각 객체는 각기 다른 Strategy를 표현한다

3. 명세(Specification)

  • 어떤 객체가 특정기준을 만족하는지 판단하는 술어다.
  • 다른객체에 대한 제약조건을 기술하며, 제약조건은 존재할 수도 존재하지 않을 수도 있다
  • 특별한 목적을 위해 술어와 유사한 명시적인 Value Object를 만들어라

유연한 설계

  • 너무 과도한 추상 계층과 간접 계층이 존재하면 오히려 유연성에 방해가 된다
  • 정교한 시스템을 목적으로 조립가능하고 이해하기 어렵지 않은 요소를 만들어내려면 적당한 수준의 엄밀한 설계형식과 접목하고자 노력해야 한다

1. 의도를 드러내는 인터페이스(Intention-Revealing Interface)

  • 캡슐화로 클라이언트 코드는 단순해지고 상위 수준의 개념 관점에서 코드를 이해할 수 있다
  • 인터페이스를 구성하는 각 요소(타입, 메서드, 인자 이름)의 이름을 토대로 설계 의도를 드러낸다
  • 결과와 목적만을 표현하도록 클래스와 연산의 이름을 부여(Ubiquitous Language 기반)
  • 행위에 대한 테스트를 먼저 작성
  • 방법이 아닌 의도를 표현하는 추상적인 인터페이스위로 모든 까다로운 메커니즘을 캡슐화
2. 부수효과가 없는 함수(Side-Effect-Free-Function)
  • 부수효과를 일으키지 않으면서 결과를 반환하는 연산을 함수라 한다
  • 함수는 여러번 호출해도 무방하며 매번 동일한 값을 반환한다
  • 명령과 질의를 엄격하게 분리 - 변경을 발생시키는 메서드는 도메인 데이터를 반환하지 않아야하고 가능한 단순하게 유지해야 한다
  • 명령과 질의를 분리하는 대신 연산의 결과를 표현하는 새로운 Value Object를 생성해서 반환
  • 상태변경을 수반하는 로직과 계산이 혼합된 연산은 리팩터링을 거쳐 두개의 연산으로 분리해야 한다
  • 복잡한 계산을 처리하는 책임을 Value Object로 옮긴다

3. 단언(Assertion)

  • 연산의 사후 조건과 클래스 및 Aggregate의 불변식을 명시하라
  • 개발자들이 의도된 단언을 추측할 수 있게 인도하고, 쉽게 배울수 있고 모순된 코드를 작성하는 위험을 줄이는 응집도 높은 개념이 포함된 모델을 만들려고 노력하라
4. 개념적 윤곽(Conceptuel Contour)
  • 이 개념이 현재 모델과 코드에 포함된 관계를 기준으로 했을때 적절한가, 또는 현재 기반을 이루는 도메인과 유사한 윤곽을 나타내는가?
  • 변경되는 부분과 변경되지 않는 부분을 나누는 중심 축을 식별하고, 변경을 분리하기 위한 패턴을 명확하게 표현하는 개념적 윤곽을 찾아라
  • 객체와 메서드를 와해시킬 정도로 광범위한 변경을 야기하는 요구사항이 나타났다는 것은 도메인에 관해 알고 있는 지식을 개선해야 한다는 메시지다
4. 독립형 클래스(Standalone Class)
  • Module과 Aggregate 모두 지나치게 얽히고 설키는 상호의존성을 방지하는 것이 목적이다
  • 명시적인 참조에 비해 암시적인 개념이 훨씬 더 많은 정신적 과부하를 초래한다.
  • 현재 상황과 무관한 모든 개념을 제거하라
  • 클래스가 완전히 독립적으로 바뀌고 단독으로 검토하고 이해할 수 있을 것이다
  • 모든 비본질적인 의존성을 제거하는 것이 목표다
  • 가장 복잡다단한 계산을 Standalone class로 도출하려고 노력하라
  • Standalone Class는 극단적으로 결합도를 낮춘 것이다

5. 연산의 닫힘(Closure of Operation)

  • 구현자(implementer)가 연산에 사용되는 상태를 포함하고 있다면 연산의 인자로 구현자를 사용하는 것이 효과적이므로 인자의 타입과 반환 타입을 구현자의 타입과 동일하게 정의한다. 이런 방식으로 정의된 연산은 해당 타입의 ㅇ니스턴스 집합에 닫혀 있다. 닫힌 연산은 부차적인 개념을 사용하지 않고도 고수준의 인터페이스를 제공한다.
  • 일반적으로 Entity는 어떤 계산의 수행결과를 표현하는 개념이 아니다. 따라서 대부분의 경우 Value Object에서 이 패턴을 적용할 기회를 찾을 수 있다.
6. 선언적 설계
  • 일반적으로 일종의 실행 가능한 명세(executable specification)로서 프로그램 전체 혹은 프로그램의 일부를 작성하는 방식
  • 특성(properties)을 매우 정확하게 기술함으로써 소프트웨어를 제어하는 것
  • 선언적인 프로그램의 이점을 누리려면 모든 개발자가 프레임워크의 규칙을 준수 해야 한다
  • 아주 좁은 범위로 한정된 프레임워크를 사용해서 영속성과 객체관계형 매핑과 같이 매우 지루하고 오류가 발생하기 쉬운 설계 측면을 자동화한 경우에 큰 가치를 얻었다
  • 규칙기반(rule base)
    • 원칙적으로는 선언적이지만 대부분의 시스템은 성능 최적화를 위해 제어술어(control predicate)를 포함
    • 이러한 제어코드는 부수효과를 수반하므로 더는 선언된 규칙만으로는 완전한 행위를 지시할 수 없다
7. 도메인 특화 언어
  • 특정 도메인을 위해 구축된 특정 모델에 맞게 조정된 프로그래밍 언어를 사용해 클라이언트 코드 작성
  • 프로그램의 표현력을 월등히 향상시킬 수 있음
  • Ubiquitous Language와도 가장 높은 일관성을 유지 할 수 있음
8. 하위 도메인으로 분할하라
  • 조금씩 뜯어내어 접근
  • 모델의 일부 영역이 전문적인 영역이거나, 상태변경에 제약을 가하는 복잡한 규칙을 적용한다면 끄집어내어 별도의 모델이나 규칙을 선언적으로 표현해주는 간단한 프레임워크 내부로 옮긴다
  • 하나의 영역에 집중

9. 가능하다면 정립된 정형화를 활용하라

  • 오랜 시간 동안 정립되어 온 개념적인 체계 이용
  • 수학적인 개념 - 도메인에 적절히 특화된 수학은 깔끔한 동시에 명확한 규칙과 결합할 수 있어서 사람들이 이해하기도 쉽다
모델과 디자인 패턴의 연결
  • 기술저인 문제에 대한 해법뿐 아니라 개념적인 도메인에 관한 해법도 제공해야 한다는 것이 디자인 패턴을 도메인 패턴으로 적용하기 위한 유일한 요구사항이다
1. Strategy(전략, 정책 패턴)
  • 여러프로세스의 선택이라는 문제를 해결
  • 프로세스의 규칙과 프로세스를 제어하는 행위를 서로 분리
  • 도메인 패턴으로 사용하는 관점에서는 프로세스 또는 정책적인 규칙과 같은 하나의 개념을 표현하는 능력에 중점

2. Composite

  • Composite내부에 포함된 모든 구성요소를 포괄하는 추상 타입을 정의
  • 계층구조상의 모든 수준에서 동일한 행위를 제공
  • 크고 작은 각 부분이 전체 구조를 투명하게 반영


Layered Architecture

  • SoC를 아키텍처 레벨에서 가능하게 해주는 패턴
  • 계층을 나누어 각 계층의 관심사에 집중할 수 있도록 한다.
  • 일반적으로 사용자 인터페이스, 응용, 도메인, 인프라스트럭쳐 계층이 있다.


도메인 모델 빌딩블럭

엔티티(Entity)

  • 어떤 객체를 일차적으로 해당 객체의 식별성으로 정의
  • 객체의 생명주기 내내 이어지는 추상적인 연속성
  • 엔티티의 속성 보다는 정체성에 초점


Value Object

  • 개념적 식별성을 갖지 않으면서 도메인의 서술적 측면을 나타내는 객체
  • 모델에 포함된 어떤 요소의 속성에만 관심
  • 불변적
  • 객체의 수가 많아질 수 있으므로 객체를 고유하여 최적화 가능


Service

  • 모델에서 독립적인 인터페이스로 제공되는 연산
  • 다른 객체와의 관계를 강조
  • 연산의 명칭은 Ubiquitous language에 도입되어야 함
  • 매개변수와 결과는 도메인 객체여야 함
  • 도메인 계층에서만 이용되는 것은 아님
  • 각 계층의 서비스와 도메인 서비스를 적절하게 나누는 것이 중요 함


Module

  • 패키지
  • 일련의 응집력있는 개념
  • 모듈의 이름은 도메인에 통찰력을 줄 수 있어야 함


도메인 객체의 생명주기


도메인객체의 관리 문제

  • 생명주기 동안의 무결성 유지하기
  • 생명주기 관리의 복잡성으로 모델이 난해해지는 것을 방지하기

해결방법

Aggregate

  • 소유권과 경계를 명확히 정의하여 객체 간의 연관관계가 복잡해지지 않도록 한다
  • 도메인 객체의 무결성 유지에 중요 함
  • 생명주기의 전 단계에서 불변식이 유지돼야 할 범위를 표시

Factory

  • 생명주기의 초기단계
  • 복잡한 객체와 Aggregate를 생성하고 재구성

Repository

  • 생명주기의 중간과 마지막
  • 영속 객체를 찾아 조회하는 수단
Aggregate
  • root와 boundary
  • boundary : 무엇이 포함되고 무엇이 포함되지 않는지 정의
  • root : 단 하나만 존재, 특정 엔티티
  • 경계 바깥의 객체는 해당 Aggregate의 구성요소 가운데 root 만 참조 가능
  • 불변식 : 데이터가 변경될 때마다 유지돼야 하는 일관성 규칙
  • 구현 규칙
    • 루트 엔티티는 전역 식별성을 지니며 불변식을 검사할 책임이 있다
    • 루트는 Value Object의 복사본을 단른 객체에 전달해 줄 수 있다
    • 삭제 연산은 경계안의 모든 요소를 한번에 제거해야 한다.
    • 변경시 전체 불변식은 반드시 지켜져야 한다




전역식별성

지역식별성

모델

  1. 중요한 사실이나 사상의 일부 측면을 나타냄 
  2. 대상을 단순화 한것
  3. 당면한 문제와 관련된 것을 추상화

도메인

  • 사용자가 프로그램을 사용하는 대상 영역
  • 컴퓨터와 거의 관련이 없음
도메인 모델
  • 특정한 다이어 그램이 아니라 전달하고자 하는 아이디어
  • 지식을 엄격하게 구성하고 선택적으로 추상화한 것
  • 모델과 핵심 설계는 서로 영향을 주며 구체화 된다
  • 모델은 모든 팀 구성원이 사용하는 언어의 중추
  • 모델은 지식의 정수만을 뽑아낸 것이다
Ubiquitous language
  • 클래스, 주요 연산등의 이름
  • 명시적으로 드러나는 규칙을 토론하기 위한 용어
  • 도메인 모델에 적용하는 패턴의 이름
  • 공통언어(도메인 전문가와 개발팀간)로서 모델과 코드에서 사용해야 한다.
  • 모델, 설계, 구현 전체를 연결하는 핵심 키

도메인 모델 작성시 가장 중요하다고 생각하는 것은 추상화이다. 특히 기존 코드를 분석하여 모델을 만드는 경우 코드의 세세한 부분까지 모델에 표현하려는 욕심은 자제해야 한다.



유틸리티 클래스를 설계하면서의 고민..
1. 네이밍
2. 구조
3. as-is와의 호환?

유틸리티 클래스에 대해서는 설계적인 고민을 전혀 하지 않았다. 어차피 static메소드 자체가 OO와는 거리가 있다는 생각에 그냥 필요한 메소드를 쭉 구현만하면 되지 않을까라는 생각을 가지고 있었다. 이런식의 생각은 매우 위험하다. 특히 어차피 라는 말.. 어차피가 들어가기 시작하면 설계고 구현이고 대충 하게 되도 어차피라는 말로 전부 커버가 되버린다. 비록 static 메소드만을 가지고 있는 유틸리티 클래스 일지라도 그 범위내에서 고민할 내용들은 무지 많아진다 특히 프레임웍 개발이나 공통 패키지의 작업일 경우는 더욱 그러하다.

네이밍이 엉망일 경우 나같아도 그기능을 사용하지 않는다. 네이밍을 보고 일단 무시 들어가 버린다. 근데 내가 그렇게 무시당하도록 아무생각없이 그냥 만들어 버렸다. 쪽팔린다.

같은 기능을 하는데 아규먼트의 종류나 수가 다를경우 어떻게 설계해야 하는가.. 이때는 두가지 방법이 가능할 것 같다. 메소드의 오버로딩이나 아니면 그냥 메소드를 분리하는거다. 난 후자를 택했다. 이유는 어차피 때문이다.. 어차피 스태틱 메소드인걸.. 그런생각에

나는 항상 어떤 한계를 가지고 뭔가를 생각하는 것 같다 as-is를 분석하면 그 sa-is를 마치 지켜야 하는 성역인것 처럼 생각하게 된다. as-is에서 잘못된 것을 바로 잡는것이 to-be의 목적중 하나일텐데.. 잘못된것은 과감하게 잘라버리자. as-is의 한계속에서 나아가 기술적인 한계속에서 생각하지 말자
Step을 확장하기 위해 가장 먼저 생각나는 확장포인트는 AbstractStep 일것이다. 하지만 이녀석을 확장한다면 스프링 배치의 스텝을 사용하지 않는것과 같다. 왜냐하면 스텝의 중요한 기능들은 ItemOrientedStep에서 구현하고 이녀석은 AbstractStep 을 확장한 것이기 때문이다.
두번째로는 AbstractStepFactoryBean이 있다. 이녀석을 확장하는것 역시 AbstractStep을 확장하는것과 별반 다르지 않다.

그렇다면 ItemOrientedStep을 확장해야 하는가? 그것또한 쉽지않다. ItemOrientedStep만으로는 skip정책과 retry 정책등과 같은 필수적인 기능들을 제공하지 않기 때문이다. 이러한 정책들은 AbstractStepFactoryBean을 상속한 SimpleStepFactoryBean과 또 이 클래스를 상속한 SkipLimitStepFactoryBean에서 세팅해준다.

이쯤에서 드는 생각은 ItemOrientedStep과 SkipLimitFactoryBean을 확장하면 될것같다는 생각이 들것이다. 하지만 이것또한 답은 아닌것 같다 왜냐하면  AbstractStepFactoryBean은 FactoryBean 인터페이스를 구현한다. 그런데  AbstractStepFactoryBean에서 구현한 getObject메소드를 final로 막아버렸다. 그래서 SkipLimitFactoryBean을 확장한다고 해도 ItemOrientedStep을 확장한 스텝을 생성할 수가 없다

이쯤에서 한번 생각해볼 문제가 왜 데이브는 getObject메소드를 오버라이딩 하지 못하게 막았을까하는 점이다. 분명 우리보다 뛰어난 사람일 텐데 그렇게 한 이유가 분명히 있을것이다

여기까지가 저번주 목, 금 이틀동안 삽질한 결과이다. 답은 전혀다른 곳에 있었다. 생각을 유연하게 하고 기술에 얽매이지 않으면 답이 보일것이다.
db2는 데이터를 수정, 삭제, 조회할때 스키마를 설정해줘야 한다.
예를 들면 select * from schema.teble_name 이런식이다.
조회 문에 스키마명을 설정해주지 않기 위해서는 jdbc url에  currentSchema를 설정해주거나 아니면 프로퍼티로 키값을 currentSchema로 해서 설정해 줄 수가 있다.


url에는 이런식으로 해주면 된다.
 <property name="jdbcUrl" value="${batch.jdbc.url}:currentSchema=${batch.schema};" />
<aop:aspect id="..." ref="...">
    <aop:... pointcut="execution(.....)"
</aop:aspect>

표현식의 기본구조는
   execution(수식어패턴? 리턴타입패턴 패키지패턴?이름패턴(파라미터패턴))
위와같다

패턴 * : 모든값
패턴 .. : 0개이상

and(&&) 와 or(||) 를 이용하여 표현식을 연결할 수 있다

+ Recent posts