diff --git "a/17\354\236\245/\353\247\210\354\235\264\355\201\254\353\241\234\354\204\234\353\271\204\354\212\244 \354\225\204\355\202\244\355\205\215\355\212\270 \354\212\244\355\203\200\354\235\274.md" "b/17\354\236\245/\353\247\210\354\235\264\355\201\254\353\241\234\354\204\234\353\271\204\354\212\244 \354\225\204\355\202\244\355\205\215\355\212\270 \354\212\244\355\203\200\354\235\274.md" new file mode 100644 index 0000000..81896ef --- /dev/null +++ "b/17\354\236\245/\353\247\210\354\235\264\355\201\254\353\241\234\354\204\234\353\271\204\354\212\244 \354\225\204\355\202\244\355\205\215\355\212\270 \354\212\244\355\203\200\354\235\274.md" @@ -0,0 +1,170 @@ +# 17 마이크로서비스 아키텍트 스타일 + +마크로소바스는 소프트웨어 프로젝트의 논리적 설계 프로세스를 강조한 도메인 주도 설계 사상의 영향을 많이 받았습니다. 특히 디커플링 스타일을 나타내는 바운디드 컨텍스트 개념은 마이크로서비스에 결정적인 영향을 미쳤습니다. + +전통적인 모노리식 아키텍처에서는 수많은 개념을 공유하고 재사용 가능한 클래스를 만들어 데이터베이스에 접속했습니다. 바운디드 컨텍스트 내부에서 코드, 데이터 스키마 같은 내무 요소들이 함께 연결되어 동작하지만, 바운디드 컨텍스트 외부 있는 것들은 전혀 커플링되지 않습니다. 이로써 각 콘텍스트는 다른 구성원을 수용하지 않고 자신이 필요한 것들만 정의할 수 있습니다. + +그러나 고도의 디커플링이 아키첵트의 목표라면 재사용보다 중복을 우선할 것이빈다. 마이크로서비스의 주요 목표는 바운디드 컨텍스트의 논리적인 개념을 물리적으로 모델링하는 고도의 디커플링입니다. + +## 토폴로지 + +![](https://raw.githubusercontent.com/cheese10yun/TIL/master/assets/101-17-11.jpg) + +마이크로서비스는 단일 목적만 가지기 때문에 오케스트레이션 기반의 서비스 지향 아키첵터와 같은 다른 분산 아키첵터 보다 서비스 규모가 훨씬 작습니다. 실제로 각 서비스에는 데이터베이스 및 기타 종속적인 컴포넌트 등 서비스가 독립적으로 작동되는 데 필요한 모든 것들이 준비되어 있습니다. + +> 코멘트 +> +> 오케스트레이션 기반의 서비스 지향 아키첵터에서는 디비는 같은데 서비스 단위로 나누는 것으라고 하긴 하는데 어차피 자신의 책임과 역활에 대해서만 알고 외부에 대한 상세합은 모르는 것이 핵심이기 때문에 크게 다르지 않다고 본다. +> +> 2, 3 서비스에서는 서로 큐 기반으로 데이터 파이프 라인을 구축하는 것으로 보이는데 좋은 설명인거 같다. + +## 분산 +마이크로서비스는 분산 아키첵터를 형성합니다. 서비스는 자체 프로세스로 실행되며, 원래 물리적인 컴퓨터를 의미하겠지만 이제는 가상 머신과 컨테이너로 빠르게 진화했습니다. + +다수의 애플리케이션을 관리할 때에도 네트워크 대여폭, 메모리, 디스크 공간 등 여러 가지 혜택을 운영 측면에서 재사용할 수 있습니다. 그러나 지원해야할 애플리케이 계속 늘어나면 결국 일부 리소스는 공유 인프라의 제약을 받을 수 밖에 없습니다. + +클라우드 리소스와 컨테이너 기술을 활용해 도메인 레벨, 운영 레벨 모두 디커플링의 이점을 누릴 수 있게 됐습니다. + +> 코멘트 +> +> 마이크로 서비스는 k8s같은 컨테이너 오케스트레이션 환경에 적합하다고 하는거 같다. 동의하지만 서비스의 크기가 그렇게 크지 않고 k8s 인프라에 대한 구축 노하우 및 운영 노하우가 없다면 그냥 물리 서버에서 동작하는 환경을 구축 해도 무방하다고 생각한다. +> +> 결국 증요한 것은 역활과 책임의 분리이며, 그 것을 유연하게 연결하는 인터페이스의 조합이라고 생각한다. + + +## 바운디드 컨텍스트 + +마이크로서비스의 근본 척할은 바운디드 컨텍스트 개념입니다. 서비스마다 도메인이나 워크플로우를 모델링하는 개념이이지요. 예를 들어 모노리스 사고 방식은 Address 같은 공통 클래스를 다른 애플리케이션 파트의 개발자가 공유하는 것이 당연하지만, 마이크로서비스는 커플링을 가급적 삼가하므로 이 아키첵터 스타일을 구축하는 아키첵트는 커플링보다는 차라리 중복이 낫다고 생각합니다. + +> 코멘트 +> +> 그렇게 적절한 예로 보이지 않긴 한다. 바운디드 컨텍스트라는 것은 문맥(서비스의 문맥)에 맞게 해석되고 관리되는 것으로 생각한다. +> +> 주문이라는 컨텍스트가 있다. 일반적으로 회원이 주문을 하기 때문에 주문자를 member 라고 생각할 수 있지만, 비회원 주문 등등 주문이라는 컨텍스트에서는 orderer(주문자)라고 해석하는 것이 바람직하다. +> +> 그렇다면 Member 객체를 중복으로 구현하는게 아니라. 본인의 컨텍스트 즉 바운디드 컨텍스트에 맞게 해석 하여 구현하는 것이 더 올바른 해석으로 보인다. 물론 그 개념이 너무 간단해서 중복 코드 처럼 나오는 경우도 있지만 단순히 중복 이라고만 해석하는 건 좋은 해석은 아니라고 생각한다. + +### 세분도 + +아키첵트는 마이크로소비스의 알맞는 세분도를 잘게 위헤 고심하다가 종종 서비스를 너무 잘게 나누는 실수를 저지르곤 합니다. + +### 목적 +각 마이크로서비스가 기능적으로 매우 응집되어 있고 전체 애플리케이션을 대표하여 하나의 핵심 기능을 제공하는 것이 가장 이상적인 모습입니다. + +### 트랜잭션 +여러 엔티티가 함께 개입하여 작동되는 트랜잭션을 아키텍트에서 좋은 서비스 경계 후보입니다. 분산 아키텍처에서 트랜잭션은 문제가 될 소지가 있으므로 그런 문제를 방지할 수 있도록 설계하는 것이 바람직합니다. + +### 데이터 격리 + +아키텍트 전체에 데이터를 분산시킬 수 있을 지 결정해야 합니다. 아니면 데이터베이스 복제나 캐시 기술로 정보를 분산시키든지, 뭔가 구체적인 방인이 필요하다. + +> 코멘트 +> +> 사실 해당 마이크로 서비스를 개발하는 개발자들이 그 데이터 격리 대해서 고민하기 때문에 이 부분은 크게 공감이 가질 않는다. +> 책 초반에서 나왔던 아키텍트가 모든 것을 설계하는 사상이 여기에서도 나오는거 같은데 마이크로 서비스 아키텍트로 가면 결국 그 서비스에 대한 오너십은 해당 개발자가 되는것이고 그 개발자가 아키텍트가 되는 그림이다. + +## API 레이어 + +모든 비지니스 로직은 바운디드 컨텍스트 내부에서 일어나야 합니다. + +## 운영 재사용 + +마이크로서비스가 커플링보다 복제를 선호한다고 했습니다. 그러면 모니터링, 로깅, 회로 차단기 등의 운영 관심사와 같이 실제로 커플링이 더 유리한 아키텍트 부분은 어떻게 처리해야할까요? + +마이크로서비스 아키텍트는 이 두 가지 관심사를 분리하고자 합니다. + +![](https://raw.githubusercontent.com/cheese10yun/TIL/master/assets/101-17-10.jpg) + +각 서비스마다 별도의 컴포넌트에 두고, 해당 팀이나 공유 인프라팀이 소유할 수 있도록 합니다. 사이드카 컴포넌트는 팀이 서로 커플링되면 더 유리한 모든 운영 관심사를 도 맡아 처리합니다. 따라서 모니터링 도구를 업그레이드할 때가 되면 공유 인프라팀이 사이드카를 업데이트 하는 방식으로 각 마이크로서비스는 신기능을 받아 사용할 수 있습니다. + +![](https://raw.githubusercontent.com/cheese10yun/TIL/master/assets/101-17-09.jpg) + +> 코멘트 +> +> 이것도 동의하기가 힘들다. 로깅, 모니터링(APM)등 인프라 도구는 애플리케이션 코드에 비침투적인 방향으로 발전 해왔고, 지금도 그렇다. +> 애플리케이션 코드 레벨에 침투하는 것은 올바른 방향은 아니라고 생각하며 만약 모니터링 도구가 변경되면서 시스템이 변경되는 경우에도 그 여파가 적게 설계하는 것이 일반적이다. +> +> 예를 들어 인프라 레벨에서 로그 수집 도구를 변경한다고 하더라도 기존에 서로 약속된 경로, 패턴 등에 파일 기반으로 내려주는 방식이 였다면 크게 변경될 여지가 없다. +> +> 무튼 하고 싶은 말의 핵심은 애플리케이션 코드에 침투하는 모니터링 쪽은 올바르지 않다고 생각하며, 그런 부분이 없을 수는 부분도 있다 이런 부분은 위에서 이야기 했듯이 어느 플랫폼을 쓰더라도 그 영향이 적은 일반적인 시스템을 이용해야 한다. + + +## 프론트엔드 + +마이크로서비스 아키텍처의 유저 인터페이스는 보통 두 가지 스타일로 나타납니다. + +![](https://raw.githubusercontent.com/cheese10yun/TIL/master/assets/101-17-08.jpg) + +요청을 처리하기 위한 단일 유저 인터페이스가 API 레이어를 통해 호출하는 모놀로식 프론트엔드 입니다. + +![](https://raw.githubusercontent.com/cheese10yun/TIL/master/assets/101-17-07.jpg) + +백엔드 서비스로 활용하여 유저 인터페이스를 동기적인 수준으로 세분화하고 격리합니다. + +> 코멘트 +> +> 이런 부분에 필요성을 느껴본적은 없어 딱히 코멘트할게 없습니다. +> 모놀로식 유저 인터페이스에서 여러 서비스를 호출하는 서비스 구현도 사실 어려운 부분이 있는데 마이크로 프론트엔드라?...음 + + +## 통신 +동기로 할지, 비동기로 할지 통신 방식을 결정해야합니다. + +프로토콜 인지 +서비스는 다른 서비스를 호출할 때 어떤 프로토콜을 사용할지 알아야 합니다. 아키텍트는 여러 서비스가 상대방을 호출하는 방식을 표준화 합니다. + +이종 +마이크로서비스는 분산 아키텍처라서 각 서비스마다 구현 기술 스택이 상이할 수 있습니다. 이종이란, 서비스 마다 사용되는 플랫폼이 저마다 다른 폴리그랏 환경을 완벽하게 지원한다는 뜻입니다. + +상호 운용성 +여러 서비스가 서로 호출한다는 뜻입니다. 마이크로 서비스에서 트랜잭션 메서드 호출을 권장하지 않지만, 어쩃거나 서비스는 네트워크를 통해 다른 서비스를 호출하여 정보를 주고 받으면서 협력해야 합니다. + +### 코레오그래피와 오케스트레이션 + +코레오그래피는 브로커 이벤트 기반의 아키텍처와 통신 스타일이 동일합니다. 즉 이 아키첵터는 중앙 중재자가 따로 없고 바운디드 컨텍스트 철학에 충실합니다. + +![](https://raw.githubusercontent.com/cheese10yun/TIL/master/assets/101-17-09.jpg) + +필요한 정보가 외부 서비스에 있다면 그 서비스를 호출하여 필요한 정보를 가져옵니다. + + +![](https://raw.githubusercontent.com/cheese10yun/TIL/master/assets/101-17-05.jpg) + +해당 일을 담당하는 서비스를 만들어 중재자가 다른 서비스를 호출하여 데이터를 생성합니다. 어느 쪽이든 완벽한 정답은 없고 각각 일당일단이 있습니다. + +> 코멘트 +> +> 오케스트레이션 방식은 상다히 많은 분석과 통찰이 이루어진 이후에 만들어져야 한다고 생각한다. 현재는 중간 서비스 -> 원천 서비스 2 depth를 가지지만 depth가 늘어 날수 있고, 또 다른 서비스를 호출 해야하는 경우가 생길수 있습니다. 아주 명확하지 않다면 코레오그래피 방식으로 개발하고 필요한 경우가 생기면 오케스트레이션으로 넘어가는 것이 좋다고 생각한다. + + + +![](https://raw.githubusercontent.com/cheese10yun/TIL/master/assets/101-17-04.jpg) + +![](https://raw.githubusercontent.com/cheese10yun/TIL/master/assets/101-17-03.jpg) + + +### 트랜잭션과 사가 + +분산 애플리케이션에서는 데이터베이스 역시 동일한 수준의 디커플링이 필요하므로 모놀로식 애플리케이션에서 별 문제가 아니였던 원자성 문제가 발생합니다. + +![](https://raw.githubusercontent.com/cheese10yun/TIL/master/assets/101-17-02.jpg) + +트랜잭션을 조정 합니다. 중재자는 트랜잭션을 구성하는 파트를 하나씩 호출하여 성공/실패 여부를 기록하고 그 결과에 따라 흐름을 조정합니다. 어느 한 파트라도 실패하면 중재자는 전체 트랜잭션 파트가 모두 성공하지 못하게 해야 합니다. + + +![](https://raw.githubusercontent.com/cheese10yun/TIL/master/assets/101-17-01.jpg) + + +중재자는 지금까지 성공한 모든 트랜잭션 파트에게 과거에 처리했던 내용을 언두라는 요청을 보냅니다. 이런 종류의 트랜잭션을 조정을 보장 트랜잭션 프레잌워크라고 합니다. + +하지만 여기에 비동기 요청이 끼어들고, 특히 보류된 트랜잭션 상태에 따라 새로운 요청이 등장하면서 설계가 무척복잡해 집니다. 또 네트워크 레벨에서도 조정 트래픽이 꽤 많이 발생합니다. + +트랜잭션 작업마다 두/언두 로직 개발을 하는 식으로 보상 트랜잭션 프레임워크를 구현할 수 있습니다. + +> 코멘트 +> +> 매번 보상 트랜잭션까지 API를 개발해야 하는 것이 작업양에 많은 영향을 미치기 때문에 좋은 대안이라고는 생각들지 않지만. 이 방법외에는 딱히 적절한 방법이 없긴 합니다. +> +> 또 read timeout, connection timeout이 발생하는 경우 요청한 쪽에서는 실패로 간주하고 보상 트랜잭션을 호출 할 수 밖에 없고, 그 요청이 실제로는 성공 했을 경우도 염두해야 하는 등등에 대한 코드도 필요합니다. +> +> 개인적으로는 API마다 원자성을 보장하는 방법 보다는 주기적으로 두 시스템의 데이터가 같은지 확인하는 방법이 더 효율적이고 현실적인 방법이라고 생각합니다.