Release 의 모든 것 (16장)
Release 의 모든 것 (16장) 의 내용 중, 인상적이었던 부분을 발췌 및 요약합니다.
블록 곡선 수익률
소프트웨어의 모든 부분이 매일같이 수정되어야 하는 것은 아니다. 그럼에도 변화에 적응하고, 빠르게 실패하고 배우는 과정을 통해서 생존을 도모할 수 있다.
절차와 조직
변경을 가하려면 의사 결정이 필요할 것이고, 회사의 규모에 따라서 변화의 주기는 다를 것이다. 주기를 더 빨리 돌수록 경쟁력이 높아진다.
스타트업은 빠른 실패를 통해서 경쟁 우위를 가진다. 주기 속도를 알기 위해서는 일감 목록에 티켓이 등록될 때가 아니라, 티켓으로 발행하기까지의 ‘결정’에 달려있다.
피드백과 개발 사이에 리듬을 일정하게 유지할 수 있어야 한다.
플랫폼 팀
이제 운영과, 개발의 경계는 이전과 달리 분리되어 있지 않다. 가상화와 클라우드 컴포팅의 등장으로 프로그래밍 가능한 인프라가 만들어졌다.
플랫폼 팀에게 중요한 것은 자신의 일이 다른 팀에 실제 플랫폼 요소를 제공하는 구조를 구현하는 것임을 기억하는 것이다. 즉, 구체적인 모니터링 규칙을 구현하는 것이 아니라 api 를 제공해야 한다.
플랫폼 팀에게 애플리케이션 가용성의 책임을 물을 것이 아니라, 플랫폼 자체의 가용성으로 평가받아야 한다. 플랫폼 팀은 고객 중심을 지향하며, 이때의 고객은 애플리케이션 개발자를 의미한다.
개발자가 플랫폼을 사용하는 이유가 규정 때문이 아니라, 개발자의 선호에 의해서 사용할 수 있어야 한다.
데브옵스는 개발과 운영이 하나가 되도록 하는 것이다. 두 팀의 인터페이스를 부드럽게 만드는 것이고, 배치 자동화 이상의 작업이다. 개발에서 운영을 고려하지 않고 결과물을 던지고, 운영은 티켓을 발행해서 처리하는 문화에서 운영 가용성과 응답성에 대한 가치를 고려하며 개발하는 문화로 전환하는 것이다.
고통 없는 출시
출시는 이발하는 정도의 행사여야 한다. 린 개발, 애자일 방법론, 등은 모두 빈번한 출시를 지지하며 운영 관점에서도 장점이 있다. 본질적인 해결 방법은 출시 과정을 자동화하고 표준화하는 것 뿐이다.
카나리 배치 패턴, 블루/그린 배치 패턴 등 여러 패턴을 이용해서 지속적인 배포에서도 위험을 줄일 수 있다. 이 패턴들은 위험한 작동의 속도를 제한하는 조속기 역할을 하며, 버그가 보여질 시간이나 새 코드에 접근하는 사람의 수를 제약하여 버그에 노출될 가능성이 있는 고객의 수를 제한한다.
서비스 멸종
진화는 점진적인 변화를 통해 진행된다. 진화적 아키텍쳐는 점진적인 변화의 적응력을 기업 안에 적용하는 것이다.
역설적으로, 진화적 아키텍쳐의 유용성은 실패를 통해서 달성할 수 있다. 거대한 참나무보다 작은 분재 향나무를 가지치기하기 쉬운 것처럼 분산된 복잡성을 더 쉽게 관리하고 정리할 수 있을 것이다. 여러 시험적 서비스 중에 실패한 서비스를 발견하면 다음과 같은 행동을 할 수 있을 것이다.
- 두 서비스를 계속 운영하면서 그에 따라 개발과 운영 비용을 감수한다.
- 성공적인 서비스에서 자금을 빼서 성과가 안 좋은 서비스를 더 좋게 만든다.
- 성과가 안 좋은 서비스를 더 나은 서비스와 직접 경쟁하지 않는 다른 영역으로 변경한다.
- 성과가 안 좋은 서비스를 제거해서 개발자를 다른 일에 투입시킨다.
- 포기한다.
기업은 보통 1,2 번 혹은 3번을 드물게 선택할 것이다. 그렇지만 4번도 선택지가 될 수 있다. 가성비가 안나오는 부분을 과감히 제거해라.
팀 규모 자율성
피자 두 판이라는 개념이 익숙할 것이다. 이 개념은 제프 베조스의 규칙으로, 모든 팀은 피자 두 판을 먹을 수 있는 규모 이상으로 커지면 안된다는 것이다. 사람들이 잘못 이해하는 부분은, 팀의 적정 인원에 관한 것이 아니라 소통 효율에 대한 이야기이다.
자기 완결적 피자 두 판 팀은 각 팀 구성원이 여러 직무를 담당하며, 외부 의존성이 낮아야 한다. 소수의 사람이 운영에 올리기까지 모든 과정을 자기 완결적으로 처리할 수 있도록 해야 한다.
효율성 주의
효율성은 종종 자기 작업에 대해서 시간을 100% 로 활용할 수 있는 것으로 이해된다. 그러나 계몽적인 시각에서는 업무 수행 절차를 작업자가 아닌 작업의 관점에서 바라본다. 효율적인 가치 흐름은 짧은 주기로 막힘 없이 많은 작업을 처리한다. 효율성은 유연성을 대가로 얻어진다. 컨테이너선은 항구에만 정박할 수 있지만, 많은 짐을 한번에 옮길 수 있다. 2인용 요트는 많은 짐을 옮길 수 없지만 어디에도 정박할 수 있다.
소프트웨어 산업에도 마찮가지이다. 자동화를 구축하고 인프라나 플랫폼에 결합시킬 때를 생각해보자. 쉘 스크립트는 조잡하지만 어디에서나 작동한다. 쿠버네티스와 같은 완전 자동화된 빌드 파이프라인은 일을 빠르게 진행시켜주지만 상당한 작업량이 요구된다.
시스템 아키텍쳐
기능이 형태를 결정하는 것이 아니라, 실패가 형태를 결정한다. 새로운 시도는 결합을 바로 잡으려는 시도이며, 이 과정에서 새로운 발명이 탄생한다.
진화적 아키텍쳐
진화적 아키텍쳐를 여러 차원에 걸쳐 점진적이고 유도되는 변화를 제 1원칙으로 지원하는 아키텍처라고 정의한다. 그러나 기본 아키텍쳐는 이런 변화를 지원하지 않는다. 일반적인 계층 아키텍쳐를 생각해보자.
인터페이스 계층, 세션 계층, 도메인 계층, 영속 계층으로 이루어져 있다. 계층의 격리는 가능하지만 수평 방향으로 계층 내에서는 단단한 결합이 되어 있다.
예를 들어, 도메인 클래스를 교체한다고 가정해보자. 조금만 수정하더라도 코드 기반을 뒤흔들 것이다. 이를 해결하기 위해 마이크로서비스와 같은 아키텍쳐가 등장했다. 수직적이고, 기능적인 컴포넌트 단위로 서비스를 분리한 것이다. 팀 단위 자율성을 강조한다.
느슨한 클러스터
시스템의 클러스터는 느슨해야 한다. 개별 인스턴스가 없어지는 일은 매우 하찮은 일이어야 한다. 즉, 서버 각각이 고유한 역할을 부여 받는 것이 아니라 역할이 둘 이상의 인스턴스에 부여되어야 한다.
만약 고유한 역할이 필요하다면, 리더 선출 방법이 사용되서, 서비스가 리더를 잃어도 자동으로 선출되어야 한다. 클러스터끼리 각각에 의존해서도 안된다. 서비스 전체를 대표하는 가상 IP 주소나, DNS 이름에 의존해야만 한다.
명시적 맥락
1
{"item": "0231232"}
1
{"itemID": "https://example.com/product/0231232"}
위의 json 조각과 아래의 json 조각을 비교해보자. 1번의 경우에 우리는 이 조각으로 무엇을 해야 할지 알 수 없다. 다른 서비스를 호출해서 저 ID 를 가진 객체를 가져와야 하는데, 어떤 서비스를 호출해야할지도 모호하다. 반면 2번의 경우에는 추가 정보를 어디에서 얻을 수 있는지 바로 알 수 있다. 이는 통신의 맥락을 암시적이 아니라, 명시적으로 만들어야 한다는 의미이다.
전역 상태는 가장 교묘한 형태의 암시적 맥락이다. 전역 상태의 구성 매개변수는 숨겨져 있다.
선택 가능성
오페라 하우스의 매끄러운 벽은 확장을 어렵게 한다. 조립식 주택의 평평한 벽은 확장이 용이하다. 모듈화된 시스템은 단일체보다 더 많은 선택지를 가진다.
분할
분할은 여러 모듈로 낭누어 설계하거나 한 모듈을 여러 서브 모듈로 나누는 것을 의미한다. 분할을 잘 하려면 기능을 분해하는 통찰이 필요하다.
대체
모듈식 설계에서 대체란 한 모듈을 다른 모듈로 교체하는 것이다. 당연히 두 모듈은 공통의 인터페이스를 공유해야 한다. 부모 시스템에서 필요로 하는 인터페이스 부분이 동일하기만 하면 된다.
강화와 배제
강화는 시스템에 모듈을 추가하는 것을 말하고, 배제는 제거하는 것을 말ㄹ한다. 시스템을 설계하면서, 강화와 배제를 최우선으로 둔다면 설계 결과가 전혀 달라질 것이다.
역전
역전은 분산된 여러 모듈의 기능을 모아서 시스템의 윗단계로 끌어올리는 식이다.
이식
이식은 다른 시스템의 모듈을 다른 목적으로 사용할 수 있도록 하는 것이다. 다른 프로젝트나 시스템에서 만든 서비스를 사용함으로서, 해당 서비스를 우리 시스템으로 이식할 수 있다.
정보 아키텍쳐
정보 아키텍쳐는 데이터를 구조화하는 방식이다. 현실의 일부 측면에 대해서 어떤 측면을 모델화할 것인지 선택해야 한다. 관계형 db 는 객체 E 의 속성 값을 알기는 쉽다. 그러나 E 의 속성 변화 이력을 추적하기는 어렵다. 관계형 DB 의 테이블은 사실을 표현하기 위한 것이고, 주장을 표현하기 위한 것이 아니다. 각 DB 마다 세상을 모델링하는 방법은 다를 것이다. 시스템을 구축할 때 우리는 시스템에 필요한 측면이 무엇인지, 어떻게 표현할 것인지를 알아야 한다.
매시지, 이벤트, 명령
마틴 파울러는 event driven 이라는 용어에 우려를 표했다. 이벤트는 4가지 방법으로 사용될 수 있다.
- 통지 : 발행 후에 잊는 단방향
- 상태 전송 : 다른 시스템이 작업하는 데 필요한 개체나 개체의 일부를 복제함
- 소싱 : 모든 변경이 변경을 서술하는 이벤트 형태로 기록되는 경우
- 명령-질의 분리 : 다른 구조에서 읽기와 쓰기를 수행함
카프카 덕분에 소싱 방식이 인기를 끌고 있다. 카프카는 메시지 대기열과 분산 로그의 특성을 혼합했다. 이벤트는 저장 공간이 허용하는 한 로그에 영구히 보존된다.
그러나 어떤 값을 알려면 모든 이벤트를 살펴봐야 한다. 메시지를 사용하면 복잡한 구조가 필요하고, 비동기식으로 비즈니스 요구 사항을 처리하기 위해서는 창의적인 사고가 필요하다.
URL 이원론
URL 은 어떤 값의 표상에 대한 참조이다. 포인터로 값을 얻듯이, URL 을 처리해서 그것이 가리키는 표상으로 바꿀 수 있다. 포인터와 같이 URL 을 식별 번호로 여기저기 주고 받을 수 있다. 이런 이원론을 적절하게 활용하면 의존성을 분리할 수 있다.
온라인 상거래에서 품목 정보를 가져오는 일반적인 방법은 다음과 같다. 품목 ID 로 요청을 보내면, DB 에서 ID 를 통해 조회한 결과를 보내고, 표시하는 것이다. 이 방법은 잘 작동하지만, 다른 브랜드의 DB 를 결합해야 하는 경우를 생각하면 문제가 발생한다.
해당 브랜드의 아이템을 모두 우리의 DB 에 등록하거나, 품목 ID 로 구분해서 어떤 DB 를 조회할지 결정해야 한다. 브랜드가 추가될 때마다 상황은 복잡해질 것이다.
URL 이원론을 사용하면, URL 을 품목 식별 번호와 처리 가능한 자원으로 활용해서 다수의 DB 를 지원할 수 있다.
1
{ "itemID": "http://www.item.com/old/12312" }
호출마다 서비스의 URL 을 바탕으로 유연하게 상품을 조회할 수 있을 것이다. 이것이 명시적 맥락의 적용이 될 수 있을 것이다.
개념 누수 방지
현실과 똑같은 데이터 모델은 존재하지 않는다. 개체, 관계, 시간에 따른 변화를 표현할 방법에 대한 선택만 있을 뿐이다. 내부 개념이 다른 시스템에 노출되는 것에 주의해야 한다.
실체를 있는 그대로 담는 것이 아니라, 일부 측면만을 모델로 만든다. 모든 데이터 모델링 패러다임은 어떤 것은 쉽게 만들지만 어떤 것은 불가능하게 만든다.
우리는 새로운 상태를 기록할지, 상태를 유발한 변화를 기록해야 할지 고민해야 한다. 요즘에는 디스크 공간이 충분하기에 변화를 기록하는 방법도 손쉽게 할 수 있다. 식별 번호의 사용과 남용으로 시스템 간의 결속이 발생한다. URL 의 본질의 이점을 취해서 URL 자체를 토큰이나, 개체를 얻는 주소처럼 작동하게 할 수 있다.