행위 (Behavior)
요구사항에 맞춰 코드를 짜고 버그를 수정하는 일
긴급하지만 중요하지 않다.
아키텍처
기계의 행위를 쉽게 변경할 수 있도록 시스템을 구축하는 것
긴급하진 않지만 중요하다.
행위를 긴급하고 중요한 일로 만들지 말아야 한다.
구조적 프로그래밍
제어 흐름의 직접적인 전환에 대해 규칙을 부과한다.
객체 지향 프로그래밍
제어 흐름의 간접적인 전환에 대해 규칙을 부과한다.
함수형 프로그래밍
할당문에 대해 규칙을 부과한다.
이 세 가지 패러다임은 프로그래머를 편리하게 하는 것이 아니라,
할 수 없는 것들(규칙)을 제시하여 프로그래머의 권한을 뺏어간다.
세 가지 패러다임과, 아키텍처의 세 가지 관심사는 서로 연관된다.
(함수, 컴포넌트 분리, 데이터 관리)
goto
문으로 인해 입증이 불가능한 프로그램을
if-then-else
, do-while-until
등으로, 증명 가능한 세부 기능 집합으로 분해한다.
이 집합들이 완벽하고, 항상 참이라고는 말할 수 없지만
거짓임을 증명하려는 테스트들이 모두 실패하는 것으로 목표한 만큼 참이라고 여길 수 있다.
모듈, 컴포넌트, 서비스를 쉽게 테스트할 수 있도록 노력해야 하고,
이를 위해 구조적 프로그래밍과 유사한 규칙들을 받아들여 활용해야 한다.
객체 지향이 무엇인가? 에 대한 답은 여러 가지가 있지만,
아키텍트 관점에서의 정답은 다형성을 이용하여 의존성에 대한 절대적인 권한을 획득하는 것이다.
각각의 모듈을 분리할 수 있고, 의존성 역전을 가능하게 하여
수준 별 모듈에 대해 독립성을 보장하여 개별 배포가 가능하게 한다.
클로저(Clojure)와 같은 함수형 언어에서는 변수가 변경되지 않는다.
이러한 불변성이 있다면 race condition, deadlock, concurrent update
등의 문제가 발생하지 않는다.
따라서 현명한 아키텍처는 가능한 한 많은 코드를 불변 컴포넌트로 옮기고,
가변 컴포넌트에서 최대한 코드를 빼내야 한다.
하나의 클래스는 하나의 책임만을 져야 한다.
우발적 중복
서로 성격이 많이 다른 액터들이 같은 모듈을 공유한다면,
한 액터가 모듈을 변경했을 때 다른 액터가 영향을 받는다.
병합
하나의 기능에 대해 서로 다른 액터들이 코드를 변경했을 때,
원하는 기능이 각자 다르므로 병합 충돌이 발생한다.
이를 해결하는 방법은 메서드를 각기 다른 클래스로 이동하는 방법 뿐이다.
소프트웨어 개체는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다.
상위 클래스를 사용하는 코드가 이를 하위 클래스로 변경해도 정상적으로 동작해야 한다.
클라이언트는 자신이 이용하지 않는 메서드에 의존하지 않아야 한다.
상위 모듈은 하위 모듈에 의존하지 않고, 하위 모듈이 상위 모듈의 추상화된 인터페이스에 의존해야 한다.
.ipa
)컴포넌트 응집도의 세 가지 원칙
컴포넌트 사이의 관계를 설명하는 세 가지 원칙
안정성 (Stability)
어떠한 컴포넌트 안쪽으로 들어오는 의존성이 많다면 사소한 변경이 발생하더라도
그 컴포넌트는 모든 의존성을 신경쓰면서 변경해야 하므로 변경이 쉽지 않다.
이럴 때 컴포넌트가 안정적이라고 할 수 있다.
아키텍처의 주된 목적은 시스템이 잘 작동하도록 하는 것이 아니다.
주된 목적은 시스템의 생명주기를 지원하는 것이다.
시스템을 쉽게 이해하고, 개발하며, 유지보수하고, 배포하게 해주는 것이 좋은 아키텍처이다.
아키텍처는 개발 팀들이 시스템을 쉽게 개발할 수 있도록 뒷받침되어야 한다.
개발 팀들의 규모, 방식에 따라 적절한 아키텍처를 결정해야 한다.
개발 초기에는 배포 전략을 거의 고려하지 않는다.
개발하기에만 편한 아키텍처로 개발한다면 배포가 몹시 어려워지고,
배포가 어려우면 시스템의 유용성이 떨어지게 된다.
시스템 아키텍처는 필수 행위를 일급 엔티티로 격상하고,
이들이 개발자에게 주된 목표로 인식되도록 해야 한다.
신중하게 아키텍처를 만들면, 유지보수에 필요한 탐사 비용을 크게 줄일 수 있다.
컴포넌트로 분리하고, 안정된 인터페이스를 두어 서로를 격리한다면
추가될 기능에 대한 길을 밝히고 장애가 발생할 위험을 줄일 수 있다.
세부사항을 정책으로부터 신중하게 가려내고,
정책이 세부 사항과 결합되지 않도록 엄격하게 분리한다.
이를 통해 정책은 세부 사항에 관한 어떠한 것도 알지 못하게 되고,
세부 사항에 의존할 수 없게 된다.
좋은 아키텍트는 결정되지 않은 사항의 수를 최대화한다.
좋은 아키텍처는 다음을 지원해야 한다.
시스템의 아키텍처는 시스템의 의도를 지원해야 한다.
좋은 아키텍처가 행위를 지원하기 위해 할 수 있는 일 중 가장 중요한 사항은
행위를 명확히 하고 외부로 드러내며,
이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있게 만드는 것이다.
아키텍처는 요구와 관련된 유스케이스에 걸맞은 처리량과 응답 시간을 보장해야 한다.
컴포넌트를 격리하고, 통신 방식을 특정 형태로 제한하지 않는다면
운영에 필요한 요구사항이 바뀌더라도 기술 스펙트럼 사이를 전환하는 일이 훨씬 쉬워진다.
아키텍처는 개발 환경을 지원하는 데에 핵심적인 역할을 수행한다.
콘웨이의 법칙
시스템을 설계하는 조직이라면 어디든지 그 조직의 의사소통 구조와 동일한 구조의 설계를 만들어 낼 것이다.
각 팀이 독립적으로 개발할 수 있는 아키텍처를 확보하여
개발하는 동안 각 팀들이 서로를 방해하지 않도록 해야 한다.
좋은 아키텍처는 시스템이 빌드된 후 즉각 배포될 수 있도록 해야 한다.
이를 위해 시스템을 컴포넌트 단위로 격리시켜야 한다.
마스터 컴포넌트는 시스템 전체를 하나로 묶고,
각 컴포넌트를 올바르게 구동하고 통합하고 관리해야 한다.
목표는 뚜렷하지 않을 뿐 아니라, 시시각각 변한다.
이러한 변화 속에서도 좋은 아키텍처는 컴포넌트 구조와 관련된 관심사들 사이에서
균형을 맞추고, 모든 컴포넌트를 만족시킬 수 있다.
좋은 아키텍처는 선택사항을 열어 둠으로써, 변경이 필요할 때
어느 방향으로든 쉽게 변경할 수 있도록 한다.
단일 책임 원칙과 공통 폐쇄 원칙을 적용하여 시스템을 적절히 분리하고 묶어야 한다.
시스템을 결합되지 않은 수평적인 계층으로 분리해야 한다.
계층으로는 UI / 애플리케이션에 특화된 업무 규칙 / 애플리케이션과는 독립적인 업무 규칙 / 데이터베이스
등이 있다.
유스케이스는 서로 다른 이유로 변경될 수 있다. (주문 추가용 유스케이스, 주문 삭제용 유스케이스)
따라서 시스템을 수평적인 계층으로 분리하면서, 수직적인 유스케이스로 분할할 수 있다.
서로 다른 이유로 변경되는 요소들을 격리해 놓으면,
기존 요소에 지장을 주지 않고 새로운 유스케이스를 계속 추가할 수 있게 된다.
컴포넌트가 완전히 분리되면 팀 사이의 간섭이 줄어든다.
계층과 유스케이스의 결합이 분리되는 한 시스템의 아키텍처는
팀 구조를 뒷받침해 줄 것이다.
결합을 제대로 분리했다면 운영 중인 시스템에서도 계층과 유스케이스를 hot-swap할 수 있다.
진짜 중복
한 인스턴스가 변경되면, 동일한 변경을 그 인스턴스의 모든 복사본에 적용해야 한다.
우발적 중복
서로 같아보이는 코드가 서로 다른 속도와 이유로 변경된다면 그 코드들은 중복이 아니다.
진짜 중복과 우발적 중복을 신중하게 구분해야 하고,
함부로 중복을 통합하지 말아야 한다.
관련이 있는 것과 없는 것 사이에 선을 긋는다.
컴포넌트 단위로 시스템을 분리하고, 사이의 화살표가 핵심 업무를 향하도록
컴포넌트의 소스를 배치한다.
이는 의존성 역전 원칙, DIP과 안정된 추상화 원칙, SAP을 응용한 것이다.