Photo by Hayden Scott on Unsplash

What is gRPC?

Intro

grpc를 이용하여 몇 개의 프로젝트를 했음에도 불구하고 아직 gRPC가 어떤 것인지 명확하게 머리 속에 자리잡지 않은 것 같아서 gRPC 개념에 대해서 정리해보려고한다.

gRPC의 개념에 대해서 한 문장으로 정리하자면 HTTP/2 기반의 RPC 프로토콜이라고 할 수 있다. 그래서 이 개념에 대해서 이해하기 위해서는 RPCHTTP/2 프로토콜 대해서 먼저 알아야한다.

RPC

RPC는 Remote Procedure Call의 약자로 의미 그대로 원격 컴퓨터나 프로세스에 존재하는 함수를 호출하는 프로토콜 이름이다. 기존 로컬 주소 공간에 존재하는 함수(procedure)를 호출하는 것을 확장하여 다른 주소 공간에 존재하는 함수(procedure)를 호출할 수 있는 프로토콜이다. 여기서 다른 주소 공간이라함은 같은 머신 내의 다른 프로세스를 말할 수도 있고 네트워크가 연결되어 있는 다른 머신의 프로세스를 말할 수도 있다. 그렇기 때문에 RPC로 클라이언트-서버 구조의 어플리케이션을 만들 수 있다

RPC는 일반적으로 request parameter과 response parameter를 알아야한다. 그렇기 때문에 미리 서버와 클라이언트 양쪽의 인터페이스를 맞춰야 하는데 Interface Definition Language라고 부르는 IDL로 함수명, 인자, 반환 값에 대해서 정의한 뒤 rpcgen 컴파일러를 이용하여 stub 코드를 자동 생성한다. gRPC는 자신만의 rpcgen 컴파일러가 존재하는데 이는 뒤에서 살펴보겠다.

이 때 생성되는 stub 코드는 개발자들에게 친숙한 go, java와 같은 원시소스코드 형태로 만들어지고 클라이언트, 서버 프로그램에 포함하여 빌드한다.

mechanism

gRPC mechanism

RPC 프로토콜은 다음과 같은 과정을 거쳐서 이뤄진다.

1. 클라이언트는 client stub procedure를 호출하는데 호출하는 입장에서는 평소 로컬 함수를 호출하듯이 함수 인자를 함수에 전달한다. client stub은 클라이언트 주소 공간에 존재한다.

2. client stub은 전달받은 인자들을 marshal(pack)하여 메세지를 만든다. marshal하는 과정에서 데이터형을 XDR(eXternal Data Representation) 형식으로 변환하여 바꾼 인자들을 메세지에 포함시킨다. 이렇게 XDR로 변환하는 이유는 integer, float과 같은 기본 데이터 타입에 대해서 머신마다 메모리 저장 방식(i.e. little endian, big endian)이 CPU 아키텍처 별로 다르며, 네트워크 전송 과정에서 바이트 전송 순서를 보장하기 위해서이다.

3. 이렇게 marshaling을 거친 메세지들은 transport layer에 전달하는데 transport layer은 리모트 서버 머신에 메세지를 전달한다.

4. 서버에서는 전달받은 메세지를 server stub에 전달하고 unmarshal(unpack) 과정을 거처서 클라이언트가 전송한 인자들을 얻어내고 클라이언트가 호출하려고하는 함수를 서버에서 찾아내어 서버의 함수로 실행시킨다. 실행 결과는 다시 server stub에 전달되고 클라이언트가 서버에 함수 인자를 전달하는 방식과 마찬가지로 결과 값이 클라이언트에 반환된다.

RPC라는 개념이 존재하기 전부터 소켓 프로그래밍을 통해서 네트워크에 연결되어있는 다른 서비스를 호출할 수는 있다. 그렇지만 네트워크를 타는 통신은 서버가 응답하지 않는 등의 장애가 발생할 수 있다. 이때 소켓 프로그래밍으로 구현했다면 이와같은 장애에 대해서 개발자가 직접 핸들링해야 한다.

RPC는 이러한 번거로움을 덜어준다. 네트워크 통신과 같은 번거로운 로직들을 감추고 추상화된 통신 인터페이스를 제공해준다. 또한 HTTP 기반의 REST가 유행하면서 RPC는 많이 사라졌는데, REST의 경우 request parameter와 response 값이 명시적이지 않기 때문에 오류가 생길 여지가 있고 JSON을 HTTP를 통해서 쏘기 때문에 속도가 비교적 떨어진다는 단점을 가지고 있다.

Protocol Buffers

gRPC는 protobuf를 IDL로 사용할 수 있다. Protobuf는 구글에서 만들고 현재 사용되고 있는 데이터 직렬화 라이브러리다. protobuf 파일의 예시는 아래와 같다.

IDL로 정의된 메세지를 기반으로 데이터 접근을 위한 코드를 생성하기 위해서 protoc 컴파일러를 사용한다. 간단한 명령어로 개발자가 원하는 프로그래밍 언어로 stub 코드를 생성할 수 있고 getter/setter 등 서버, 클라이언트 인터페이스가 자동 생성된다.

HTTP/2

HTTP/1은 기본적으로 클라이언트가 서버에 요청을 보내고, 서버가 요청에 대한 응답을 다시 보내는 구조이다. 그렇기 때문에 클라이언트가 서버에 요청을 할 때마다 커넥션이 맺어지고 서버를 왕복해야한다. 또한 헤더의 크기는 불필요하게 큰 편이다. 이와 같은 이유로 HTTP/1은 느리다.

구글에서는 이러한 문제점을 해결하기 위해서 SPDY를 만들었고 이를 기반으로 HTTP/2 표준이 만들어졌다.

SPDY

과거에 비해 오늘날에는 훨씬 더 많은 리소스를 주고 받고 있으며 보안이 훨씬 중요한 이슈가 되고 있다. 이렇게 바뀐 환경을 고려하여 구글에서는 느린 HTTP를 보완한 SPDY 프로토콜을 발표했다. SPDY 프로토콜은 latency 문제를 주로 해결하고자 하였다.

SPDY 프로토콜의 특징은 다음과 같은 것들이 있다.

항상 TLS(Transport Layer Security) 위에서 동작

따라서 HTTPS로 작성된 웹에서만 적용 가능하다.

Header Compression

HTTP 통신을 할 때 메세지에 포함되는 HTTP 헤더에는 요청마다 반복되는 내용들이 매우 많다. 그렇기 때문에 헤더 압축만으로도 리소스를 엄청 절약할 수 있다. 구글 발표 자료에 따르면 HTTP 헤더 압축을 할 경우, 최초 요청 시에는 압축을 안했을 때에 비해 10~30%를 줄일 수 있고 long-lived connection과 같이 여러 번 요청을 주고 받을 경우에는 80~97%까지 헤더 크기를 감소시킬 수 있다.

Binary protocol

HTTP/1.1에 존재하지 않았던 프레임이라는 새로운 단위가 생겼는데 이는 통신상의 제일 작은 정보 단위를 말한다. SPDY 프로토콜은 프레임을 텍스트가 아닌 바이너리로 구성하여 파싱이 더 빠르고 오류 발생 가능성이 낮다.

Multiplexing

HTTP에서는 하나의 커넥션에서 한 번에 하나의 요청을 처리하며 요청에 대한 응답이 순차적으로 이뤄졌다. 이와 달리 SPDY에서는 하나의 커넥션 안에서 여러 개의 독립적인 스트림을 동시에 처리한다. 그렇기 때문에 적은 수의 커넥션으로 다수의 요청, 응답을 동시에 처리할 수 있다. 또한 HTTP에서는 요청들이 FIFO로 처리되기 때문에 하나의 요청에 대한 응답이 지연되면 나머지 응답도 모두 늦어지는 HTTP pipelining과는 달리 SPDY에서는 각각의 요청, 응답이 모두 독립적으로 처리된다.

Full-duplex interleaving, stream priority

스트림이 진행 중일 때 다른 스트림이 끼어드는 것을 허용하고 스트림에 대해서 우선 순위 설정도 가능하기 때문에 우선 순위가 낮은 데이터를 전송 중일 때 우선 순위가 높은 데이터가 끼어들어서 더 빨리 전달할 수 있다.

Server push

Long-polling과는 다르게 클라이언트의 요청이 없어도 서버에서 컨텐츠를 직접 push할 수 있으며 리소스 캐싱이 가능하다. 하지만 server push를 구현하기 위해서는 어플리케이션 로직에 추가 구현이 필요하다.

Server push와 같은 경우를 제외하면 SPDY를 적용하기 위해서 추가적으로 작업해야할 필요는 없다. 한 가지, 브라우저와 서버가 SPDY를 지원해야한다. 그리고 spdy:// 와 같이 프로토콜 scheme이 없고 브라우저에서도 SPDY 프로토콜 사용 여부에 대해서 어떤 표시도 하지 않기 때문에 사용자는 굳이 SPDY 사용여부를 알 필요 없이 투명하게 적용 가능하다.

HTTP/2는 SPDY를 기반으로 만들어졌기 때문에 위에서 언급했던 SPDY의 특징을 가지고 있다.

gRPC

gRPC는 HTTP/2 특징을 기반으로 하는 RPC 프로토콜이다. 그렇기 때문에 양방향 스트리밍이 가능하고 HTTP보다 통신 속도가 빠르다. 그리고 RPC의 특징처럼 네트워크 통신에 대한 추상화를 제공해주어서 사용자가 네트워크 프로그래밍에 대해 신경을 쓰지 않게 되고 비즈니스 로직 개발에 집중할 수 있도록 도와준다.

그리고 과거에 gRPC의 단점 중 하나였던 gRPC에 대해 api-gateway 도입이 어렵다는 것이 있었는데 현재는 grpc-gateway 프로젝트를 통해 이는 극복한 것 같다.

필요한 코드의 양이 적고 여러 프로그래밍 언어를 지원한다는 장점이 있지만 프로토콜의 한계상 데이터 포맷 변환이 런타임에 자유롭지 못하다는 단점이 있다.

Reference

  • https://medium.com/@goinhacker/microservices-with-grpc-d504133d191d
  • https://www.tutorialspoint.com/remote-procedure-call-rpc
  • https://www.geeksforgeeks.org/operating-system-remote-procedure-call-rpc/
  • https://leejonggun.tistory.com/9
  • https://d2.naver.com/helloworld/140351
  • https://www.chromium.org/spdy/spdy-whitepaper/
  • https://grpc.io/docs/guides/
  • https://grpc.io/docs/guides/concepts/

Hyperledger Fabric: Transaction Flow

이번 포스팅에서는 Hyperledger Fabric의 transaction이 어떻게 생성되고 ledger에 최종적으로 commit되는지 이해가 잘 되도록 예시를 들어 설명되어 있는 글을 Hyperledger Fabric doc에서 발견해서 번역해보았다. 원본 링크: https://hyperledger-fabric.readthedocs.io/en/release-1.2/txflow.html

Scenario

상품 거래를 하는 시나리오를 가지고 어떻게 transaction이 발생하고 ledger에 commit되는지 그 과정에 대해서 설명하려고 한다. 예시로 사용할 시나리오에서는 A, B라는 두 명의 client가 있고 각각은 당근을 사고 팔려고 한다. client A, B 각각 자신의 네트워크에 피어를 가지고 있고 그 네트워크를 통해 각각의 transaction을 보내고 ledger에 기록한다.

Assumptions

이 시나리오에서는 channel이 세팅되어있다고 가정한다. 그리고 어플리케이션의 user들은 각 organization의 CA에 register & enroll하고 cryptomaterial materal을 생성했다. 그리고 user들은 이것을 이용해서 network authentication에 사용한다.

각 피어들에게 chaincode (당근 시장의 초기 상태를 나타내는 key & value pair들이 포함되어있다.)가 installed되었고 channel에 instantiated되었다. chaincode에는 transaction instructions set이 정의되어있다. Endorsement policy 또한 이 chaincode에 세팅되어 있고 이 때 policy는 모든 transaction에 대해서 피어 A, B 모두 endorse해야한다고 정의된다.

1. Client A initiates a transaction

먼저 구매자인 client A가 당근을 구매하겠다는 request를 보낸다. 이 때 이 request는 peerApeerB를 타겟으로 삼는다. 왜나하면 channel이 만들어질 때 정의된 endorsement policy에 따르면 모든 피어들이 transaction에 대해서 endorse 해야하기 때문이다.

그 다음, transaction proposal이 생성된다. SDK를 사용하고있는 어플리케이션에서는 SDK API 함수를 이용해서 transaction proposal을 생성한다. 이 proposal은 chaincode를 invoke 해달라는 요청인데, 결과적으로 이 proposal을 이용해서 ledger를 읽거나 쓸 수 있다. 여기서 사용하는 SDK는 transaction proposal을 gRPC를 통해 전송될 수 있는 포맷으로 패키징하는 역할을 해준다. 그리고 이 패키징에 user의 cryptographic credential을 이용해서 이 transaction proposal의 유일한 signature를 만들게 된다.

2. Endorsing peers verify signature & execute the transaction

그 다음 단계로 endorsing peer들은 이전 단계에서 만들어진 transaction proposal을 받게 되는데 다음과 같은 사항들을 확인한다.: (1)첫 번째로 이전 단계에서 만들어진 transaction proposal들이 잘 만들어졌는지 확인하고 그리고 (2)똑같은 transaction proposal이 이전에 요청되었는지도 확인한다. (replay-attack protection) (3) 세 번째로 signature가 유효한지 MSP를 이용해서 확인하고 (4) 마지막으로 요청을 전송한 submitter(현재 시나리오에서는 Client A이다.)가 현재 channel에 proposed operation을 할 수 있는 권한을 가지고 있는지 확인한다. 다시 말하면 submitter가 현재 channel의 Writers policy를 충족하는지 확인한다.

endorsing peer들은 transaction에서 invoke 되고 있는 chaincode의 함수 인자를 가지고 다시 chaincode를 현재 데이터베이스 상태에 대해서 실행시킨다. 그리고 그것들을 실행시킨 결과로 response value, read set 그리고 write set을 만들게 된다. 그런데 중요한 점은 chaincode의 함수를 실행시켜본 것이지 그 결과를 이용해서 ledger를 업데이트 시킨 것은 아니다. 아까 말한 세 가지 결과와 endorsing peer의 signature를 합쳐서 proposal response를 만들고 이것을 다시 SDK에 전송하게 된다. 어플리케이션에서는 payload를 파싱해서 사용하면 된다.

MSP

MSP는 client로부터 전달되는 transaction request들을 피어들로 하여금 검증할 수 있게 해주고 transaction를 검증한 결과에 대해서 sign 할 수 있게 해주는(endorsement) peer component이다. 이 때 writing policy는 channel이 생성될 때 정의되고 어떤 user들이 channel에 transaction을 제출할 수 있는지 명시한다.

3. Proposal responses are inspected

어플리케이션에서는 endorsing peer의 signature들을 검증하고 proposal response들을 가지고 서로 같은지 비교한다. 이 때 chaincode가 ledger를 조회하는 작업만 했다면 어플리케이션에서는 조회 결과만 확인하고 transaction을 Ordering Service에 제출하지 않는다.

만약 client 어플리케이션이 transaction을 Ordering Service에 ledger를 업데이트 시키기 위해 제출하려면 endorsement policy가 충족되었는지(peerApeerB 모두 endorse해야한다.)를 먼저 확인해야한다. 만약 어플리케이션 단에서 response를 확인하지 않고 unendorsed한 transaction을 보내거나 혹은 endorsement policy를 충족시키지 않는 transaction을 제출하게되면 commit validation 단계에서 reject 하게 된다.

4. Client assembles endorsements into a transaction

어플리케이션은 transaction proposal과 “transaction message”의 response를 Ordering Service에 보내게 된다. 그리고 transaction은 read/write set, endorsing peer의 signature들과 channel ID를 포함하고 있다. 이 때 Ordering Service는 transaction 전체를 살펴볼 필요가 없다. Ordering Service에서는 단순히 네트워크 상의 모든 channel에서 transaction을 받고 channel에 따라서 정렬한 다음 channel 별로 block을 생성하면 된다.

5. Transaction is validated and committed

위에서 생성된 block은 해당 channel의 모든 피어들에게 전송된다. 그리고 block에 있는 transaction들은 endorsement policy를 충족시키는지 검증하게되고 read set들에 대해서는 ledger state에 변화가 생기지 않았는지 확인한다. 이 검증 결과에 따라서 block의 transaction들은 valid나 invalid라는 태그를 달게된다.

6. Ledger updated

마지막으로 모든 피어들은 channel의 chain에 block을 연결시키게 되고 valid한 transaction의 write set들은 database에 commit된다. 그리고 client application에 transaction이 chain에 붙었다는 event가 전파되고 그 transaction이 검증된 것인지 아닌지도 알리게 된다.

Sequence Diagram

마지막으로 모든 step들을 sequence diagram으로 표현하면 다음과 같다.

https://hyperledger-fabric.readthedocs.io/en/release-1.2/txflow.html

Domain-Driven Design Key Concepts

현재 오픈소스로 활동하고 있는 it-chain-Engine에서 적용 중인 아키텍쳐, 디자인에 대해서 조금 더 자세하게 알아보고 개념에 대해서 포스팅해보려고 한다. 그 중에 첫 번째가 DDD(Domain-Driven-Design)이다.

본 글은 원작자의 허가를 받고 번역한 글입니다. 의역과 오역이 있을 수 있습니다.

원본 링크: https://herbertograca.com/2017/09/07/domain-driven-design/

Domain-Driven Design과 관련해서 엄청나게 많은 중요한 컨셉들이 있지만 여기서 그것들에 대해서 모두 다루는 것은 아니고 중요하다고 생각하는 개념들에 대해서 나열하고 그것들에 대해서 설명해보려고 한다.

이번 포스팅에는 다음과 같은 개념들에 대해서 적어보려고 한다.

  • Ubiquitous language
  • Layers
  • Bounded contexts
  • Anti-Corruption Layer
  • Shared Kernel
  • Generic Subdomain

Ubiquitous language

소프트웨어 개발에서 계속 생기는 문제 중 하나는 코드를 보면서 이것이 무엇을 하는 것이고, 어떻게 동작하는지에 대해서 이해하는 것이 어렵다는 것이다. 만약 코드를 만든 사람이 어떤 코드에 대해서 ‘A’라고 말하는데 실제로 코드는 ‘B’와 관련된 것이라면, 그 코드를 보는 사람들은 더더욱 혼란스러워질 것이다. 하지만 이러한 문제들은 class와 method에 더욱 적절한 네이밍을 해주면 어느정도 사라지게 될 것이다. 여기서 ‘적절한 네이밍’이란 도메인 컨텍스트에 대해서 어떤 object가 어떤 일을 하고 어떤 method가 어떤 일을 하는지 더욱 명확하게 표현하는 것을 말한다.

Ubiquitous Language의 메인 아이디어는 앞으로 구현하게 될 application 및 technology와 요구사항에 해당하는 business을 매칭시키는 것이다. 매칭시킨다는 의미는 둘 사이간에 의미를 공유할 수 있는 공용어(common language)를 두는 것이다. 그리고 이렇게 만들어진 공용어를 이용해 코드를 작성하게 된다. 요구사항에 해당하는 business에서 용어의 컨셉을 가져온다 그 다음 그것을 구현할 technology 쪽에서 그것을 다듬고 확정하게 된다. (business에서 가져온 컨셉으로는 항상 좋은 네이밍을 가져가기 힘들다.) 이렇게 둘 사이의 입장을 반영한 용어를 만듦으로써 우리는 business와 technology에서 사용할 수 있고, 앞으로 우리가 작성할 코드에서도 모호함 없이 사용할 수 있다. 이것이 Ubiquitous Language라고 할 수 있다. 코드에서 사용될 class, methods, properties, modules의 네이밍은 Ubiquitous Language와 매칭돼야 한다.

Layers

다른 디자인에서도 layer라는 컨셉은 사용되지만, DDD에서 특징적인 layer는 다음과 같다.

  • User Interface

    User Interface에서는 사용자들이 상호작용할 수 있는 screen을 만들고 사용자들의 input을 application의 명령들(commands)로 변환한다. 여기서 중요한 점은 사용자(user)들은 사람이 될 수도 있지만 어떤 application이 다른 application의 api를 사용한다면 그 application도 사용자가 될 수 있다.

  • Application Layer

    사용자들이 요구하는 tasks들을 수행하기위해 domain object들을 사용한다. Application Layer는 busniess logic을 가지고 있지 않고 Application Service가 포함된다. Application Service는 domain object에 해당하는 repository, domain service, entity, value object을 가지고 그것들을 조합함으로써 필요한 목표를 달성한다.

  • Domain Layer

    Domain Layer에서 Domain Services, Entities, Events와 같은 domain object들은 필요한 모든 business logic들을 담고 있다. 그렇기 때문에 Domain layer가 전체 시스템에서 핵심이라고 할 수 있다. Domain Services는 Entity에 딱 맞지 않는 로직이 들어가거나 혹은 몇몇 Entity들을 이용해서 business logic을 처리한다.

  • Infrastructure

    persistence나 messaging과 같이 상위 계층에서 layers들을 서포트해주는 것들이 포함된다.

Domain-Driven Design layer diagram
Eric Evans, 2003

Bounded Contexts

기업용 application에서는 model이 커질 수 있고 그에 따라 같은 코드베이스에서 작업을 하는 개발자의 수가 늘어날 수 있다. 그런데 그에 따라서 두 가지 문제점이 발생할 수 있다.

  1. 한 코드 베이스에서 작업하는 개발자의 수가 늘어날수록 한 사람이 알아야하는 코드의 양이 많아지고, 여러명이 작업하기 때문에 코드를 이해하기도 어려워진다. 그렇기 때문에 버그나 에러가 생길 가능성이 커지게된다.
  2. 같은 코드 베이스에서 작업하는 개발자가 많아질수록 작업을 조율하기가 힘들어지고 같이 쓰는 domain 코드들이나 기술적인 부분이 많아지게 된다.

위와 같은 상황을 해결할 수 있는 방법 중 하나는 많은 개발자들이 공동으로 작업해야하는 코드베이스들을 쪼개는 것이다. 그리고 이렇게 쪼갤 수 있게 도와주는 것이 “bounded context”이다.

Bounded context는 model들이 분리되어 적용될 수 있는 context를 말한다. 이 ‘분리’는 기술적인 분리나 코드의 분리, 데이터베이스 schema의 분리 그리고 팀의 분리 등을 말한다. 이러한 분리의 여러 단계 중에서 어디까지 실제 프로젝트에서 적용할지는 그때 그때 상황과 필요에 맞춰서 이뤄진다.

그런데 이러한 bounded context를 이용한 분리라는 개념은 완전히 새로운 것은 아니다. Ivar Jacobson이라는 사람이 1992년에 그의 에 ‘subsystems’이라는 비슷한 개념에 대해서 서술한 적이 있다.

Subsystems description diagram
Ivar Jacobson, 1992

그 때 당시 그가 서술했을때 이미 다음과 같은 구체적인 아이디어를 가지고 있었다:

  • 전체 system은 여러 개의 subsystem들로 이루어져있다. 그리고 그 각각의 subsystem들은 또다시 subsystem을 가질 수 있다. (중략…) Subsystem들로 전체 system을 구성하는 것은 그렇기 때문에 전체 system을 개발하고 유지보수할 수 있는 방법이다.
  • Subsystem의 과제는 객체들을 패키징하는 것이다. 그렇게 함으로써 전체 시스템의 복잡도가 줄어들게 된다.
  • 특정 기능과 관련된 모든 객체들은 같은 subsystem에 두어야한다.
  • 이렇게 특정 기능과 관련된 객체들을 묶는 것은 같은 subsystem에서는 strong function coupling을 가지고 다른 subsystem과는 weak coupling을 가지게하기 위해서다. (오늘날에는 low coupling, high cohesion으로 알려지고 있는 개념이다.)
  • 한 subsystem에서는 한 명의 actor와만 coupled 되어야한다. 왜냐하면 system의 변화는 주로 actor에 의해서 일어나기 때문이다.
  • (중략…) control object들을 subsystem에 두기 시작하였고 그것들과 강한 결합을 가지는 entity object들과 interface들을 같은 subsystem 내에 두었다.
  • object 사이에서 기능적으로 강한 결합을 가진 것들은 같은 subsystem 내에 두어야한다. (중략…)
    • 어떤 한 object의 변화가 다른 object에도 영향을 미치는가?(지금 이 개념은 현재 Robert C. Martin이 1996년도에 “Granularity”라는 논문에 서술한 The Common Closure Principle ― 같이 변하는 class들은 같은 package로 묶여야한다.―로 알려져있다.)
    • 같은 actor에 대해서 커뮤니케이션하는가?
    • *어떤 두 object가 interface나 entity와 같은 다른 제 3의 object에 의존하는가? *
    • 어떤 object가 다른 object들에게 영향을 주고 있는가?(지금 이 개념은 현재 Robert C. Martin이 1996년도에 “Granularity”라는 논문에 서술한 The Common Reuse Principle ― 같이 사용되는 class들은 같은 package로 묶여야한다.―로 알려져있다.)
  • 또다른 subsystem 분리 기준은 그들 사이에서 최소한의 커뮤니케이션만 일어나야한다는 것이다.(low coupling)
  • 사이즈가 큰 프로젝트에서는 그렇기 때문에 subsystem을 나누는 다른 기준이 있을 수 있다. 예를 들어:
    • 다른 두 개발 팀은 다른 능력과 자원을 보유하고있고, 그에 따라 적절하게 작업량을 분배해야할 것이다.(두 팀이 지리적으로 떨어져있을 수도 있다.)
    • 분산 환경에서는, 각각의 subsystem은 각각의 logical node에 의해 사용될 수 있다.(SOA, web services and micro services)
    • 만약 어떤 product가 현 시스템에서 사용되려면, 그것은 또다른 subsystem으로 여겨져야할 것이다.(시스템이 ORM과 같은 library에 의존하는 경우)

Anti-Corruption Layer

Anti-corruption layer는 기본적으로 두 subsystem 사이에서 middleware역할을 한다. 그리고 나눠진 subsystem들은 서로 직접적으로 의존하는 대신 anti-corruption layer에 의존하게된다. 이렇게 하게되면 한 subsystem을 완전히 다른 것으로 바꾸더라도 우리가 수정해야할 부분은 anti-corruption layer밖에 없다. 다른 subsystem들은 그대로 두어도 된다.

Anti-corruption layer는 legacy 시스템과 새로운 시스템을 합쳐야할 때 유용하다. legacy 구조가 새롭게 디자인한 구조에 영향을 주지 않으려면 anti-corruption layer를 두어서 새로운 시스템에 꼭 필요한 legacy system의 API만 가져오면 된다.

이것은 세 가지 역할을 가지고 있다:

  1. Client subsystem이 필요한 API를 또다른 subsystem에서 가져오는 것
  2. Subsystem 사이에서 data와 command를 전달하는 것
  3. 단방향이든 양방향이든 필요함에 따라 서로 다른 subsystem들 사이에 communication을 형성해주는 것

Roles of anti-corruption layer in diagram
Eric Evans, 2003

이것은 여러 subsystem들을 직접 제어하고 싶지 않을 때 더욱 유용하다. 그런데 이것은 모든 subsystem들을 컨트롤하고 싶을 때에도 유용할 수 있다. 그리고 아주 성격이 다른 model들이 있을 때와 한 model을 바꿨을 때 나머지 시스템에 줄 수 있는 영향을 줄이고 싶을 때에도 좋다.

Shared Kernel

우리가 아무리 컴포넌트를 분리시키고 decouple 하려고해도 어떤 domain 코드들은 여러 컴포넌트에서 공유하는게 더 좋을 때도 있다.

이렇게 공유가 필요한 코드들을 여러 컴포넌트에 공유하는 부분이 Shared Kernel이고, Shared Kernel을 두게 되면 shared kernel과는 강하게 결합이 생기지만 나머지 컴포넌트끼리는 여전히 분리시킬 수 있다.

예를 들어, 어떤 컴포넌트가 event를 전파시키고 다른 컴포넌트들이 그 event를 listen하는 경우라면 event가 shared kernel 부분에 들어갈 수 있다. 물론 service interface나 entity들도 shared kernel에 포함될 수 있다.

그렇지만 shared kernel은 가능한 작게 두는 것이 좋다. 그리고 shared kernel 코드는 다른 컴포넌트들에서 사용될 가능성이 높기 때문에 이 부분을 수정할 때 어떤 다른 부분에 영향을 주는지 확인하는 것이 좋다. 그렇기 때문에 shared kernel 코드들을 수정할 땐 전체 팀원과 충분한 상의와 합의 후에 작업하는 것이 좋다.

Generic Subdomain

Subdomain은 완전히 분리된 domain을 말한다. 그리고 generic subdomain은 현재 우리가 작업 중인 application 뿐만 아니라 비슷한 성격의 다른 application에서도 충분히 쓰일 수 있는 subdomain을 말한다.

만약 우리가 금융과 관련된 부분이 우리 프로젝트에 있다면, 다른 곳에서 만들어진 금융 관련 library를 사용할 수도 있을 것이다. 그 library를 가져와서 쓰든 안쓰든, 만약 그 금융 부분이 generic subdomain이라고 한다면 우리 application의 꼭 필요한 부분이지만 핵심 business는 아니다. 그렇기 때문에 팀이 작업할 때 그 부분은 엄청 신경써야할 부분이 아니고 또 결정하기에 따라서 dependency management tool을 이용해서 아예 소스코드 밖에 두어도 된다.

Conclusion

여기서 소개된 DDD와 관련된 개념들은 대부분 single responsibility, low coupling, high cohesion, isolating logic과 관련된 것들이다. 그래서 이러한 것들은 application을 더욱 견고하고 요구사항에 따른 변화에 잘 적응할 수 있게 만들어준다.

Sources

1992 – Ivar Jacobson – Object-Oriented Software Engineering: A use case driven approach

1996 – Robert C. Martin – Granularity

2003 – Eric Evans – Domain-Driven Design: Tackling Complexity in the Heart of Software

2014 – Eric Evans – Domain-Driven Design Reference

본 글은 원작자의 허가를 받고 번역한 글입니다. 의역과 오역이 있을 수 있습니다.

원본 링크: https://herbertograca.com/2017/09/07/domain-driven-design/

Tips for OpenSource Project – Git

Recently I’m working on a opensource project: it-chain/engine. I’ve participate in this project as collaborator early in the year. For about 6 month, there’re lots of hard situation to work with other developers. And also learned about how can I work with others not making awkward situation. In addition I want to share the tips to work on opensource project and how to use git, not basic tutorial but more about practices which fit into opensource project.

Work on Smaller Feature

I think this is coincide with the principle of Seperation of Concerns(SoC) Principle in software design. And this is what I utmost want to emphasize in this post. Let’s assume that you are working on food delivery service project with others. And one developer on your team issued on github that “Now I’m going to fix whole delivery system” and let’s assume that these delivery system code affects on all other sub-servcies code, like UserManagement, StockManagement. Then whenever other developers are trying to fix those sub-services, the codes will broken because the developer who works on whole delivery system will mass up whole of your codes. And I think this is one of most irritating situation. Works on smaller feature first which do not affact on others parts, then integrate with others.

Issue on github which feature I’m going to working on

Two different developers may work on same part. And at the point of pull request, the other developer who is not pull requested may panic. So this is basic but important manner to working with other in opensource project. Issue what’s I’m going to working on and tag the other developers to notify.

But code should be large enough to express functional unit

For example, someone pull requested with this single struct

We don’t know what is this command struct is. And where this struct are going to used. It might be better to pull request with ConfirmBlockHandler function which actually use this struct. And other can see that where this struct are going to used in the view of whole system.

Tips on Using Git

git pull -r

Usually in personal project we just pull the code. But just pull the code actually dirties your git tree if that project grow.

And there’s another advantage by using -r option. For easily rebase, pull request should have least number of commit. This convention helps each pull request have 1 commit and this also helps each pull request have smaller feature which helps other feedback your codes and also each team member can work well by not interfered with the codes that pull requested.

git commit --amend

Our team makes conventions one of which is not to make unnecessary commit and this can be the cause of reject of pull request.
To remove unnecessary commits, git commit --amend is the way. Let’s assume that you made a commit with message “add command handler”, then you find out that you forgot to add some pieces of codes, of course by mistake. But this fixes are too small to make another commit. Then you can use --amend options. You just need to type git commit --amend then the codes that you commited and the codes that you fixes are now in one commit with message “add command handler”.

git reset --soft HEAD~<NUM>

You got a feedback from pull request that you may need to combine two commits into one that you pull requested. Because other can think the second commit is unnecessary. Then you can use git reset in the local.

Let’s this is the commits you made. In this situation, you need to combine Unnecessary commit into Some commit. Then you can use git reset --soft HEAD~1, with this you reset the recent one commit and those codes you made in Unnecessary commit are unstaged. Your team feedbacks you combine two commits so now you git add . and git commit --amend. Then your commits looks like this.