반응형
아키텍처란?
- 시스템 아키텍처는 시스템의 동작 여부와 거의 관련이 없다. (아무런 역할을 하지 않는다는건 아니다.)
- 형편 없는 아키텍처를 갖춘 시스템도 그런대로 잘 동작한다.
- 이런 시스템은 운영보다는 배포, 유지보수, 계속되는 개발 과정에서 어려움을 겪는다.
- 유지보수는 모든 측면에서 봤을 때 비용이 가장 많이 발생한다.
- 유지보수 중 가장 큰 비용은 탐사와 이로 인한 위험부담에 있다.
- 탐사? 기존 소프트웨어를 파헤쳐서 어디를 고치는게 최선/최적일지 결정하는 비용
- 이러한 변경사항을 반영할 때 의도치 않은 결함이 발생할 가능성이 존재하며, 이로 인한 위험부담 비용이 추가됨
- 시스템을 컴포넌트로 분리하고, 안정된 인터페이스를 두어 서로 격리하면 비용을 줄일 수 있음
선택사항 열어 두기
- 소프트웨어는 두 가지 가치
행위적 가치
,구조적 가치
를 지닌다. 행위/아키텍처 - 소프트웨어를 부드럽게(soft)만드는 것은 바로
구조적 가치
- 소프트웨어를 부드럽게 유지하는 것은 선택사항을 가능한 한 많이 열어두는 것
- 열어 둬야 할 선택사항? 중요치 않은 세부사항(detail)
- 모든 소프트웨어 시스템은
정책(policy)
와세부사항
두 가지 구성요소로 분해 가능- 정책 : 모든 업무 규칙과 업무 절차를 구체화
- 세부사항 : 사람, 외부 시스템, 프로그래머가 정책과 소통할 때 필요한 요소. 정책이 가진 행위에 조금도 영향을 미치지 않는다.
- 입출력 장치, 데이터베이스, 웹 시스템, 서버, 프레임워크, 통신 프로토콜 등등
- 아키텍트의 목표는
시스템에서 정책을 가장 핵심적인 요소로 식별하고, 세부사항은 정책에 무관하게 만들 수 있는 형태의 시스템을 구축하는 것
- 세부사항을 결정하는 일은 미루거나 연기할 수 있게 됨
- 예)
- 개발 초기에 데이터베이스 시스템을 선택할 필요가 없음. 고수준의 정책은 어떤 종류의 데이터베이스를 사용하는지 신경 써서는 안됨
- 개발 초기에는 웹 서버를 선택할 필요가 없음. 고수준의 정책은 자신이 웹을 통해 전달된다는 사실을 알아서는 안됨
- 개발 초기에는 REST를 적용할 필요가 없음. 고수준의 정책은 외부 세계로의 인터페이스에 대해 독립적이어야 하기 때문
- 개발 초기에는 의존성 주입 프레임워크를 적용할 필요가 없음. 고수준의 정책은 의존성을 해석하는 방식에 대해 신경 써서는 안됨
- 세부사항의 결정을 미루면 더 많은 정보를 얻을 수 있고, 이를 기초로 제대로 된 결정을 내릴 수 있음
- 세부사항에 몰두하지 않은 채 고수준의 정책을 만들어야 함
- 또한 이를 통해 다양한 실험을 시도해볼 수 있는 선택지도 열어둘 수 있음
- 결정을 더 이상 연기할 수 없는 순간이 닥쳤을 때 이러한 실험과 시도 덕분에 더 많은 정보를 획득한 상태일 것
좋은 아키텍트는 결정되지 않은 사항의 수를 최대화 한다.
결론
- 좋은 아키텍트는 세부사항을 정책으로부터 가려내고, 정책이 세부사항과 결합되지 않도록 엄격히 분리
- 정책은 세부사항에 관한 어떤 지식도 갖지 못하게 되며 의존하지 않게 된다.
독립성
- 좋은 아키텍처는 다음을 지원해야 한다
- 시스템의 유스케이스
- 시스템의 운영
- 시스템의 개발
- 시스템의 배포
유스케이스
- 시스템의 의도를 지원해야 함
- 만약 장바구니 애플리케이션이라면, 아키텍처는 장바구니와 관련된 유스케이스를 지원해야 함
- 행위를 명확히 하고 외부로 드러내며, 이를 통해 시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있게 만드는 것
- 이들 행위는 시스템의 최상위 수준에서 알아볼 수 있으므로, 개발자가 일일이 찾아 헤매지 않아도 됨
운영
- 운영 지원 관점에서 아키텍처는 더 실질적이며 덜 피상적인 역할을 맡는다.
- 초당 100,000명의 고객을 처리해야 한다면, 이 요구와 관련된 유스케이스에 걸맞은 처리량과 응답시간을 보장해야 함
- 뛰어난 아키텍트라면 어떻게 시스템을 처리해야할 것인지 결정을 열어두어야 하는 선택 사항 중 하나다
- 일련의 작은 서비스들을로 배열하여 병렬로 실행
- 멀티 스레드 사용
- 등등
개발
시스템을 설계하는 조직이라면 어디든지 그 조직의 의사소통 구조와 동일한 구조의 설계를 만들어 낼 것
- 많은 팀이 시스템을 개발해야 한다면, 각 팀이 독립적으로 행동하기 편한 아키텍처를 확보하여 개발하는 동안 팀들이 서로 방해되지 않도록 한다.
- 잘 격리되어 독립적으로 개발 가능한 컴포넌트 단위로 시스템을 분할하여 작업
배포
- 아키텍처는 배포 용이성을 결정하는 중요한 열활을 하며 목표는
즉각적인 배포(immediate deployment)
이다. - 수십 개의 작은 설정 스크립트나 속성 파일을 약간씩 수정하는 방식을 사용하지 않음
- 꼭 필요한 디렉터리나 파일을 수작업으로 생성하게 내버려 두지 않음
선택사항 열어놓기
- 좋은 아키텍처는 컴포넌트 구조와 관련된 이 관심사들 사이에 균형을 맞추고, 각 관심사를 모두 만족시키는 것
- 현실에서는 균형 잡기가 매우 어려움
- 모든 유스케이스를 알 수 없으며, 운영하는 데 따르는 제약사항, 팀 구조, 배포 요구사항도 알지 못하기 때문
- 아키텍처 원칙을 지키면 제대로 격리된 컴포넌트 단위로 분할할 때 도움이 되며, 이를 통해 선택사항을 가능한 한 많이, 그리고 가능한한 오랫동안 열어 둘 수 있게 해준다.
- 좋은 아키텍처는 선택사항을 열어 둠으로써, 향후 시스템에 변경이 필요할 때 어떤 방향으로든 쉽게 변경할 수 있도록 한다.
결합 분리
- 수평 분할 - 서로 다른 이유로 변경되는 계층 분리
- (UI 계층) UI 일부
- (업무 로직 계층) 애플리케이션 특화 업무 규칙 - ex. 입력 필드 유효성 검사 (애플리케이션에 더 밀접)
- (업무 로직 계층) 애플리케이션 독립 업무 규칙 - ex. 계좌 이제 계산, 재고품 집계 (업무 도메인에 더 밀접)
- (데이터 베이스 계층) 데이터베이스 기능 일부
- 수직 분할 - 유스케이스 (유스케이스들이 겹치지 않게 한다.)
- 주문 추가 유스케이스 (주문 추가용 UI - 주문 추가용 업무 로직 - 주문 추가용 데이터베이스)
- 주문 삭제 유스케이스 (주문 삭제용 UI - 주문 삭제용 업무 로직 - 주문 삭제용 데이터베이스)
- 시스템에서 서로 다른 이유로 변경되는 요소들을 결합 분리하면 기존 요소에 지정을 주지 않고 새로운 유스케이스를 계속해서 추가할 수 있다.
- 수평분할이 되어 있으면, UI/데이터베이스는 업무 규칙과 다른 서버에서 실행될 수 있다. 높은 대역폭을 요구하는 유스케이스는 여러 서버로 복제하여 실행할 수 있음
중복
- 소프트웨어에서 중복은 일반적으로 나쁜 것
- 하지만 중복에도 여러 종류가 있으면 그중 하나는
진짜 중복
이다.- 이 경우 한 인스턴스가 변경되면, 동일한 변경을 그 인스턴스의 모든 복사본에 반드시 적용 해야 함
- 또 다른 중복은
거짓된 또는 우발적인 중복
- 두 코드 영역이 각자의 경로로 발전한다면, 서로 다른 속도와 다른 이유로 변경된다면 이 두 코드는
진짜 중복
이 아님 - 몇 년이 지나 다시 보면 두 코드는 매우 다르게 됨
- 두 코드 영역이 각자의 경로로 발전한다면, 서로 다른 속도와 다른 이유로 변경된다면 이 두 코드는
- 유스케이스 화면 구조가 매우 비슷 -> 이 구조에서 사용할 코드를 통합하고 싶은 강한 유혹
우발적 중복
일 가능성이 높음- 시간이 지나면 두 화면은 서로 다른 방햐응로 발전할 가능성이 많음
- 이러한 이유로 코드를 통합하지 않도록 유의
- 유스케이스를 수직으로 분리할 때 이러한 문제와 마주칠 테고, 이들 케이스를 통합하고 싶은 유혹을 받게 될 것
- 비슷한 화면구조, 알고리즘, 데이터베이스 쿼리와 스키마를 가지기 때문
- 자동반사적으로 중복을 제거해버리는 잘못을 저지르는 유혹을 떨쳐내자.
결합 분리 모드(다시)
- 계층과 유스케이스의 결합을 분리하는 방법은 다양
- 소스 수준 분리
- 소스 코드 모듈 사이 의존성 제어 가능
- 하나의 모듈이 변하더라도 다른 모듈을 변경하거나 재컴파일 하지 않도록할 수 있음
- 컴퓨터 메모리에는 하나의 실행 파일만이 로드. (모노리틱)
- 배포 수준 분리
- jar, ddl, 공유 라이브러리와 같이 배포 가능한 단위들 사이의 의존성을 제어
- 한 모듈의 소스 코드가 변하더라도 다른 모듈을 재빌드하거나 재배포하지 않도록 만들 수 있음
- 분리된 컴포넌트가 독립적으로 배포할 수 있는 단위로 분할
- 서비스 수준 분리
- 의존하는 수준을 데이터 구조 단위까지 낮출 수 있음
- 네트워크를 통해서만 통신 가능
- 완전히 독립적
- 시스템이 한 서버에서 실행되는 동안은 결합을 소스 수준에서 분리하는 것만으로도 충분. 나중에는 배포 가능한 단위, 심지어는 서비스 수준까지 분리해야 할 수도 있음
- 서비스 수준의 결합 분리
- 개발 시간 측면뿐 아니라 시스템 자원 측면에서 비용이 많이 듬
- 필요치 않은 서비스 경계 처리, 메모리, 등등
- 개발 시간 측면뿐 아니라 시스템 자원 측면에서 비용이 많이 듬
- 컴포넌트가 서비스화될 가능성이 있다면 컴포넌트 결합을 분리하되 서비스가 되기 직전에 멈추는 방식을 선호(서비스에 대한 선택권을 열어 둔다.)
- 초기에 컴포넌트가 소스 코드 수준에서 분리 -> 배포/개발에 문제가 생기면 일부 결합을 배포 수준까지 분리해 대응
- 개발/배포/운영적인 문제가 생기면 서비스 수준으로 전환할 배포 단위들을 신중하게 선택 후 시스템을 변경해 나간다.
- 좋은 아키텍처는 시스템이 모노리틱 구조로 태어나서 단일 파일로 배포되더라도, 독립적으로 배포 가능한 단위들의 집합으로 성장하고, 또 독립적인 서비스나 마이크로서비스 수준까지 성장할 수 있도록 만들어져야 함. (거꾸로 또한 가능해야함)
경계: 선 긋기
- 소프트웨어 아키텍처는 선을 긋는 기술
- 경계(boundary)는 소프트웨어 요소를 서로 분리하고, 경계 한편에 있는 요소가 반대편에 있는 요소를 알지 못하도록 막음
- 경계는 프로젝트 수명 중 아주 초기에, 심지어 코드 작성전에 그어지며 나중에 그어지기도 한다.
- 초기에 그어지는 선은 핵심적인 업무 로직을 오염시키지 못할 목적으로 그려진다. (가능한 한 오랫동안 결정을 연기시키기 위해)
- 아키텍트 목표 : 시스템을 만들고 유지하는 데 드는 인적 자원 최소화
- 인적 자원 효율을 떨어뜨리는 요인 ? 결합(coupling). 너무 일찍 내려진 결정에 따른 결합
- 어떤 종류의 결정이 이른 결정일까?
- 업무 요구사항(유스 케이스)과 아무런 관련이 없는 결정
- 프레임워크, 데이터베이스, 웹 서버, 유틸리티 라이브러리 ..
어떻게 선을 그을까? 그리고 언제 그을까?
- 관련이 있는 것과 없는 것 사이에 선을 긋는다.
- GUI는 업무 규칙과 관련이 없기 때문에, 이 둘 사이에는 선이 있어야 한다.
- DatabaseAccess의 두 화살표는 모두 바깥쪽으로 향한다.
- 즉, DatabaseAccess가 존재한다는 사실을 알고 있는 클래스가 없다는 뜻
- 조금 멀리서 보자면, 많은 업무 규칙이 포함되는 컴포넌트, 데이터베이스와 데이터베이스 접근 클래스를 포함하는 컴포넌트의 사이의 경계는 아래와 같다.
- Database는 BusinessRule을 알고 있지만, BusinessRule은 Database에 관해 알지 못한다. (언제든 DB를 변경할 수 있음)
- DatabaseInterface 클래스는 BusinessRule 컴포넌트에 속하며, DatabaseAccess 클래스는 Database 컴퍼넌트에 속한다.
- BusinessRule에게 Database는 문제가 되지 않지만, Database는 BusinessRules 없이는 존재할 수 없다.
- Database 컴포넌트는 BusinessRules가 만들어 낸 호출을 데이터베이스의 쿼리 언어로 변환하는 코드를 담고 있음
플러그인 아키텍처
- 소포트웨어 개발 기술의 역사는 플러그인을 쉽게 생성하여, 확장 가능하며 유지보수가 쉬운 시스템 아키텍처를 확립할 수 있게 만드는 방법에 대한 이야기
- 선택적이거나 수많은 다양한 형태로 구현될 수 있는 나머지 컴포넌트로부터
핵심적인 업무 규칙 분리
- 이 설계에서는 UI/데이터베이스가 플러그인 형태로 고려되었기에, 다른 형태로 연결할 수 있다.
- UI : 웹 기반, 클라/서버 기반, 콘솔 기반, 어떤 사용자 인터페이스 기술 ..
- DB : MySql, NoSql, 파일시스템 ..
결론
- 경계선을 그리려면 시스템을 컴포넌트 단위로 분할하는 것이 먼저다.
- 핵심 업무 규칙 컴포넌트 : 필수 기능 포함
- 나머지 컴포넌트 : 플러그인
- 핵심 업무를 향하도록 나머지 컴포넌트의 소스를 배치한다.
- 의존성 역전 원칙 + 안정된 추상화 원칙 응용
- 의존성 화살표는 저수준 세부사항에서 고수준의 추상화를 향하도록 배치한다.
반응형
정책과 수준
- 소프트웨어 시스템이란 정책을 기술한 것
- 좋은 아키텍처는 각 컴포넌트를 연결할 때 의존성의 방향이 컴포넌트 수준을 기반으로 연결되도록 만들어야 함
- 저수준 컴포넌트가 고수준 컴포넌트에 의존하도록 설계수준
- 수준(level)은 엄밀하게 정의하자면
입력과 출력까지의 거리
- 입/출력으로 멀리 위치할수록 정책의 수준은 높아짐. 입력과 출력을 다루는 정책이라면 최하위 수준에 위치
- 데이터 흐름 : 굽은 실선 화살표
- 소스 코드 의존성 : 점선
- 번역 컴포넌트는 이 시스템에서 최고 수준의 컴포넌트. 입력과 출력에서부터 가장 멀리 떨어져 있기 때문
- 소스 코드 의존성은 그 수준에 따라 결합되어야 하며, 데이터 흐름을 기준으로 결합되서는 안 된다.
fun encrypt() {
wirteChar(translate(readChar()))
}
- 위는 잘못된 아키텍처
- 고수준인 encrypt 함수가 저수준인 readChar, writeChar에 의존하기 때문
- 아래 클래스 다이어그램처럼 시스템의 아키텍처가 개선되어야 함.
- 고수준의 암호화 정책을 저수준의 입력/출력 정책으로부터 분리시켜야 한다.
- 암호화 정책을 더 넓은 맥락에서 사용할 수 있다.
- 저수준 정책 : 긴급성을 요하며 덜 중요
- 고수준 정책 : 중요
- 저수준 정책에 비해 덜 빈번하게 변경되고, 보다 중요한 이유로 변경되는 경향이 있음
- 정책을 컴포넌트로 묶는 기준은 정책이 변경되는 방식에 달려있다는 사실을 상기하자
- 단일 책임 원칙(SRP), 공통 폐쇄 원칙(CCP)에 따르면 동일한 시점에 변경되는 정책은 함꼐 묶임
업무 규칙
- 애플리케이션을 업무 규칙과 플러그인으로 구분히려면 업무 규칙이 실제 무엇인지 잘 이해해야만 한다.
- 업무 규칙?
- 사업적으로 수익을 얻거나 비용을 줄일 수 있는 규칙 또는 절차
- 핵심 업무 규칙(Critical Business Rule)
- 대출에 N % 이자를 부과한다.
- 핵심 업무 데이터(Criticial Business Data)
- 대출 잔액, 이자율, 지급 일정
엔티티
- 핵심 규칙 + 핵심 데이터
- 시스템 내부의 객체, 핵심 업무 데이터를 기반으로 동작하는 일련의 조그만 핵심 업무 규칙을 구체화
- 핵심 업무 데이터를 기반으로 동작하는 핵심 업무 규칙을 구현한 함수들로 구성된다.
- Loan 엔티티는 세 가지 핵심 업무 데이터를 포함하며, 데이터와 관련된 세 가지 핵심 업무 규칙을 인터페이스로 제공
- 이 클래스는 어떤 시스템에서도 업무를 수행할 수 있으며, 독립적으로 존재해야 한다.
- DB, UI, 서드파티 프레임워크로 부터 오염되어서는 안된다.
유스케이스
- 자동화된 시스템이 동작하는 방법을 정의하고 제약함으로써 수익을 얻거나 비용을 줄이는 업무 규칙도 존재
- 유스케이스는 자동화된 시스템이 사용되는 방법을 설명한다.
- 대출 담당자가 신청자의 신상정보를 수집하여 검증한 후, 신용도가 500보다 낮다면 대출 견적을 제공하지 않기로 결정
- 유스케이스는 사용자가 제공해야 하는 입력, 사용자에게 보여줄 출력, 그리고 해당 출력을 생성하기 위한 처리 단계를 기술
- 엔티티 내의 핵심 업무 규칙과는 반대로, 유스케이스는 애플리케이션에 특화된(application-specific)업무 규칙을 설명
- 유스케이스는 엔티티 내부의 핵심 업무 규칙을 어떻게, 그리고 언제 호출할지를 명시하는 규칙을 담는다. 엔티티가 어떻게 춤을 출지 유스케이스가 제어
- 애플리케이션에 특화된 규칙을 설명하며, 이를 통해 사용자와 엔티티 사이의 상호작용을 규정
- 시스템에서 데이터가 들어오고 나가는 방식은 유스케이스와 무관
- 유스케이스는 객체이며 애플리케이션에 특화된 업무 규칙을 구현하는 하나 이상의 함수를 제공
- 입력 데이터, 출력 데이터, 상호작용하는 엔티티에 대한 참조 데이터 등의 데이터 요소를 포함
- 엔티티는 자신을 제어하는 유스케이스에 대해 아무것도 알지 못하며 의존성 역전 원칙을 준수하는 의존성 방향임
- 고수준인 엔티티는 저수준의 유스케스에 대해 아무것도 알지 못하며, 반대로 저수준 유스케이스는 고수준 엔티티에 대해 알고 있음
- 유스케이스는 단일 애플리케이션에 특화되어 있어 해당 시스템의 입력/출력에 가깝게 위치하기 때문에 저수준
- 엔티티는 수많은 다양한 애플리케이션에 사용될 수 있도록 일반화된 것이므로, 입/출력과는 멀어서 고수준
요청 및 응답 모델
- 유스케이스는 입력 데이터를 받아 출력 데이터를 생성하며, 제대로 구성된 유스케이스라면 데이터를 사용자나 또 다른 컴포넌트와 주고 받는 방식에 대해서 눈치챌 수 없어야 한다.
- 유스케이스 클래스 코드가 HTML이나 SQL에 대해 알게 되는 일은 없어야 한다.
- 요청 및 응답 모델이 독립적이지 않다면, 그 모델에 의존하는 유스케이스도 결국 해당 모델이 수반하는 의존성에 간접적 결합이 된다.
- 엔티티와 요청/응답 모델은 많은 데이터를 공유하므로 엔티티 객체의 참조를 요청/응답 데이터 구조에 넣는 유혹을 받을 수 있지만 두 객체의 목적은 완전히 다르다.
- 두 객체는 완전히 다른 이유로 변경될 것이고, 두 객체를 묶는 행위는 공통 폐쇄 원칙(OCP)과 단일 책임 원칙(SRP)을 위배하게 된다.
- 수 많은 떠돌이 데이터(tramp data)가 만들어지고, 수많은 조건문이 추가되어 버림
결론
- 업무 규칙은 소프트웨어 시스템이 존재하는 이유(핵심적인 기능)
- 업무 규칙은 사용자 인터페이스나 데이터베이스와 같은 저수준의 관심사로 인해 오염되어서는 안되며, 그대로의 모습으로 남아 있어야 한다.
- 업무 규칙을 표현하는 코드는 시스템 심장부에 위치해야 하며, 덜 중요한 코드는 심장부의 플러그인되어야 한다.
- 업무 규칙은 시스템에서 가장 독립적이며 가장 많이 재사용할 수 있는 코드여야 한다.
소리치는 아키텍처
아키텍처의 목적
- 좋은 아키텍처는 유스케이스를 중심에 두기 때문에, 프레임워크나 도구, 환경에 구애받지 않고 유스케이스를 지원하는 구조를 아무런 문제 없이 기술할 수 있다.
- 좋은 소프트웨어 아키텍처는 프레임워크, 데이터베이스, 웹 서버 등등 개발 환경 문제나 도구의 결정을 미룰 수 있도록 만든다.
- 프로젝트 후반에 결정 하지 않아도 되도록 하며, 결정을 쉽게 번복할 수 있도록 한다.
- 유스케이스에 중점을 두며, 지엽적인 관심사에 대한 결합은 분리시킨다.
하지만 웹은?
- 웹은 아키텍처에 영향을 주지 않는다.
- 웹은 전달 메커니즘(입출력 장치)이며, 아키텍처에서도 그와 같이 다루어야 한다.
- 웹을 통해 전달된다는 사실은 세부사항임
- 웹으로 전달할지 여부는 미루어야 할 결정사항 중 하나
- 과도한 문제를 일으키거나 근본적인 아키텍처를 뜯어 고치지지 않더라도 시스템을 콘솔, 웹, 앱, 리치 클라이언트 앱 등등 으로도 전달할 수 있어야 한다.
테스트하기 쉬운 아키텍처
- 유스케이스를 최우선으로 한다면, 그리고 프레임워크와 적당한 거리를 둔다면, 프레임워크가 없더라도 필요한 유스케이스 전부에 대한 단위 테스트를 할 수 있어야 한다.
- 테스트를 돌리는데 웹 서버, 데이터베이스가 필요해서는 안된다.
- 엔티티 객체는 POJO여야 하며, 프레임워크/데이터베이스에 의존해서는 안 된다.
- 유스케이스 객체가 엔티티 객체를 조작해야 하며, 프레임워크로 인한 어려움을 겪지 않고도 이 모두를 있는 그대로 테스트할 수 있어야 한다.
결론
- 아키텍처는 시스템을 이야기해야 하며, 적용한 프레임워크에 대해 이야기해서는 안된다.
- 헬스 케어 시스템을 구축하고 있다면, 새로온 개발자는 repository를 봤을 때 첫 인상은 "오, 헬스 케어 시스템이군"이어야 한다.
- 새로 합류한 개발자는 시스템이 어떻게 전달될지 알지 못한 상태에서도 시스템의 모든 유스케이스를 이애할 수 있어야 한다.
클린 아키텍처
- 지난 수십 년간 시스템 아키텍처와 관련된 여러 가지 아이디어가 나왔다.
- 육각형 아키텍처(Hexagonal Architecture)
- DCI(Data, Context and Interaction)
- BCE(Boundary-Control-Entity)
- 모두 세부적인 면에서 차이가 있지만 그 내용은 상당히 비슷하다. 바로 관심사의 분리(separation of concerns)이다.
- 이들 모두 소프트웨어를 계층으로 분리함으로써 관심사의 분리라는 목표를 달성할 수 있었다.
- 다음과 같은 특징을 지님
- 프레임워크 독립성 : 프레임워크는 도구로 사용되며, 프레임워크가 지닌 제약사항안으로 시스템을 욱여 놓지 않는다.
- 테스트 용이성 : 업무 규칙은 외부 요소 없이도 테스트할 수 있다. (UI, 데이터 베이스, 웹 서버 ..)
- UI 독립성 : 시스템의 나머지 부분을 변경하지 않고도 UI를 쉽게 변경할 수 있다.
- 데이터베이스 독립성 : 업무 규칙은 데이터베이스와 결합되지 않는다. 오라클, MS SQL, 몽고 DB 등으로 교체 가능
- 모든 외부 에이전시에 대한 독립성 : 실제 업무 규칙은 외부 세계와의 인터페이스를 알지 못한다.
- 아래 다이어그램은 이들 아키텍처를 전부 실행 가능한 하나의 아이디어로 통합하려는 시도
의존성 규칙
- 각각의 동심원은 서로 다른 영역을 표현한다.
- 안으로 들어갈수록 고수준의 소프트웨어가 된다.
- 가장 중요한 규칙은 의존성 규칙(Depedency Rule)
소스 코드 의존성은 반드시 안쪽으로, 고수준의 정책을 향해야 한다.
- 내부의 원의 요소는 외부의 원에 속한 어떤 것도 알지 못한다.
- 외부 원에 위치한 어떤 것도 내부의 원에 영향을 주지 않기를 바란다.
엔티티
- 전사적인 핵심 업무 규칙을 캡슐화한다.
- 엔티티는 메서드를 가지는 객체이거나 일련의 데이터 구조와 함수의 집합일 수 있음
- 특정 애플리케이션에 무언가 변경이 필요하더라도 엔티티 계층에는 절대 영향을 주면 안된다.
유스케이스
- 시스템의 모든 유스케이스를 캡슐화하고 구현한다.
- 엔티티로 들어오고 나가는 데이터 흐름을 조정하며, 엔티티가 자신의 핵심 업무 규칙을 사용해서 유스케이스의 목적을 달성하도록 이끈다.
- 이 계층에서 발생한 변경이 엔티티에 영향을 주며 안되며, 다른 외부 요소에서 발생한 변경이 이 계층에 영향을 줘서도 안된다.
- 유스케이스 계층은 이러한 관심사로부터 격리되어 있다.
인터페이스 어댑터
- 프레젠터, 뷰, 컨트롤러는 모두 인터페이스 어댑터에 속한다.
- 모델은 그저 데이터 구조 정도에 지나지 않으며,
컨트롤러 -> 유스케이스
로 전달되며유스케이스 -> 프레젠터/뷰
로 되돌아 간다. - 데이터를 엔티티/유스케이스에게 가장 편리한 형식에서 영속성으로 사용 중인 임의의 프레임워크(데이터베이스)가 이용하기 가장 편리한 형식으로 변환한다.
- 이 원 안에 속한 코드는 데이터베이스에 대해 조금도 알아서는 안된다.
- SQL 기반의 데이터베이스를 사용한다면 모든 SQL은 이 계층을 벗어나서는 안 된다.(어댑터는 특정 기술에 종속)
- 외부 서비스와 같은 외부적인 형식에서 유스케이스나 엔티티에서 사용되는 내부적인 형식으로 변환하는 어댑터도 필요하다.
프레임워크와 드라이버
- 가장 바깥쪽 계층이며, 일반적으로 데이터베이스나 웹 프레임워크 같은 프레임워크나 도구들로 구성
- 안쪽원과 통신하기 위한 접합 코드 외에는 특별히 더 작성해야 할 코드가 그다지 많지 않다.
- 웹, 데이터베이스같은 세부사항. 외부에 위치시켜 피해를 최소화 한다.
원은 네 개여야만 하나?
- 예시이며, 더 많은 원이 필요할 수도 있음
- 어떤 경우에도 의존성 규칙은 적용된다.
- 소스 코드 의존성은 항상 안쪽으로 향한다.
- 안쪽으로 이동할수록 추상화와 정책의 수준은 높아진다.
- 바깥쪽 원은 저수준의 구체적인 세부사항으로 구성된다.
- 안쪽으로 이동할수록 소프트웨어는 점점 추상화되고 더 높은 수준의 정책들을 캡슐화한다.
- 가장 안쪽 원은 가장 범용적이며 높은 수준을 가진다.
경계 횡단하기
- 컨트롤러와 프레젠터가 유시크에스와 통신하는 모습을 볼 수 있음
- 제어흐름 : 컨트롤러 -> 유스케이스 -> 프레젠터
- 소스 코드 의존성 : 각 의존성은 유스케이스를 향해 안쪽을 가르킨다.
- 제어흐름과 의존성 방향이 반대여야하는 경우 의존성 역전 원칙을 사용하여 의존성 방향을 제어 흐름과 반대가 되게 만들 수 있다.
- 인터페이스와 상속 관계를 적절하게 배치
- 유스케이스에서 프레젠터를 호출해야 한다고 가정
- 직접 호출은 의존성 규칙을 위배 (내부 원에서 외부 원에 있는 어떤 이름도 언급해서는 안됨)
- 유스케이스가 내부 원의 인터페이스를 호출하도록 하고, 외부 원의 프레젠터가 그 인터페이스를 구현하도록 만든다.
경계를 횡단하는 데이터는 어떤 모습인가
- 경계를 가로지르는 데이터는 간단한 데이터 구조로 이루어져 있다. (DTO)
- 중요한 점은 격리되어 있는 간단한 데이터 구조가 경계를 가로질러 전달된다는 사실
- 엔티티 객체나 데이터베이스 행(row)을 전달하는 일을 원치 않음
- 데이터 구조가 의존성을 가져 의존성 규칙을 위배하게 되는 일은 바라지 않음
- 데이터베이스 프레임워크는 쿼리에 대한 응답으로 사용하기 편리한 데이터 포멧을 사용
- 이 행(row)구조가 경계를 넘어 내부로 그대로 전달되는 것을 원치 않음
- 이렇게 되면 의존성 규칙을 위배, 내부의 원에서 외부 원의 무언가를 알아야만 하기 때문
전형적인 시나리오
- 아래는 데이터베이스를 사용하는 웹 기반 자바 시스템의 전형적인 시나리오
- 웹 서버는 사용자로부터 입력 데이터를 모아
Controller
로 전달 Controller
는 데이터를 POJO로 묶은 후,InputBoundary
인터페이스를 통해UserCaseInteractor
로 전달UseCaseInteractor
는DataAccessInterface
를 사용하여Entities
가 어떻게 춤출지 제어하는데 사용UseCaseInteractor
는DataAccessInterface
를 사용하여Entities
가 사용할 데이터를 데이터베이스에서 불러와서 메모리로 로드UseCaseInteractor
는Entities
로 부터 POJO자바 객체인OutputData
구성OutputData
는OutputBoundary
인터페이스를 통하여Presenter
로 전달Presenter
가 맡은 역할은OutputData
를ViewModel
과 같이 화면에 출력할 수 있는 형식으로 재구성하는 일ViewModel
은 주로 문자열과 플래그로 구성되며,View
에서는 이 데이터를 화면에 출력함- OutputData에서는 Date 객체를 포함하지만 Presenter는 ViewModel을 로드할 때 Date 객체를 사용자가 보기 적절한 문자열로 변환한다.
- 모든 의존성은 경계선을 안쪽으로 가로지르며, 의존성 규칙을 준수한다.
Controller에서 요청하고, Presenter에서 받는다? 이 부분은 잘 이해가 되지 않는다. 하지만 읽으면서 이해가 안되는 부분들이 많으니 일단 스킵하도록 한다.
결론
- 간단한 규칙들을 준수하는 일은 어렵지 않으며, 향후에 겪을 수많은 고통거리를 덜어준다.
- 계층을 분리하고 의존성 규칙을 준수하면 본질적으로 테스트하기 쉬운 시스템이 만들어 진다.
- 그 에 따른 이점은 DB 혹은 웹 프레임워크와 같은 시스템 교체가 쉬워진다.
프레젠터와 험블 객체
험블 객체 패턴
- 디자인 패턴으로
테스트하기 어려운 행위
와 테스트하기 쉬운 행위
를 단위 테스트 작성자가분리
하기 쉽게 하는 방법으로 고안 - 행위들을 두 개의 모듈 또는 클래스로 나눈다. 이 모듈 중 하나가 험블(humble)
- 가장 기본적인 본질은 남긴다.
- 테스트하기 어려운 행위를 모두 험블 객체로 옮김
프레젠터와 뷰
- 뷰 : 험블 객체이며 테스트하기 어렵다.
- 가능한 간단하게 유지
- 데이터를 GUI로 이동시킬뿐 데이터를 직접 처리하지 않는다.
- 프레젠터 : 테스트하기 쉬운 객체이다.
- 프레젠터 역할은 애플리케이션으로부터 데이터를 받아 화면에 표현할 수 있는 포멧으로 만드는 것이다.
- 애플리케이션은 프레젠터에 Date 객체를 전달하고, 프레젠터는 적절한 문자열로 변환하여 뷰 모델에 전달하게 된다.
부분적 경계
- 아키텍처 경계를 만드는 데는 비용이 많이 든다.
- 쌍뱡항 다형적 Boundary 인터페이스, Input/Output을 위한 데이터 구조 필요
- 애자일 커뮤니티에서는 YAGNI(You Aren't Going to Need It)원칙을 위배하기 때문에 탐탁치 않게 여긴다.
- 하지만 문제를 검토하면서 "어쩌면 필요할지도"라는 생각이들 수 있는데, 만약 그렇다면 부분적 경계(partial boundary)를 구현해볼 수 있다.
방법1. 마지막 단계를 건너뛰기
- 독립적으로 컴파일하고 배포할 수 있는 컴포넌트를 만들 수 있는 환경을 만든 후, 단일 컴포넌트에 그대로 모아두는 것
- 모두를 단일 컴포넌트로 컴파일해서 배포하여 다수의 컴포넌트를 관리하는 작업은 하지 않아도 됨
방법2. 일차원 경계
- 양방향 Boundary 인터페이스를 유지하기 위해선 비용이 많이든다.
- 한방향만 경계를 인터페이스로 격리해서 사용한다.
방법3. 퍼사드
- 경계는 Facade 클래스로 간단히 정의
- Facade 클래스에는 모든 서비스 클래스를 메서드 형태로 정의하고, 서비스 호출이 발생하면 해당 서비스 클래스로 호출 전달
- 클라이언트는 이들 서비스 클래스에 직접 접근할 수 없음
- but, Client가 서비스 클래스에 추이 종속성을 갖게 됨
계층과 경계
- 아키텍처 경계는 어디에나 존재한다.
- 아키텍처 경계가 언제 필요한지를 신중하게 파악해내야 한다.
- (책에서는 콘 쉘에서 단 200줄이면 구현 가능한 프로그램으로 아키텍처 경계를 나누면서 예시를 듬)
- 경계를 제대로 구현하려면 비용이 많이 든다는 사실을 인지하자.
- 이러한 동시에 경계가 무시되었다 나중에 다시 추가하는 비용 또한 크다.
추상화가 필요하리라고 예측해서는 안됨
- YAGNI(You Are't Going to Need It)가 말하는 철학으로, 오버 엔지니어링이 언더 엔지니어링보다 나쁠 때가 많다.
- 아키텍트는 미래를 내다봐야 한다. 현명하게 추측하자.
- 비용을 산정하고, 어디에 경계를 둬야 할지, 완벽하게 구현할 경계는 무엇인지와 부분적으로 구현할 경계와 무시할 경계는 무엇인지 결정을 해야 한다.
- 이는 일회성 결정은 아니다. 프로젝트 초반에 어떤 경계일지 쉽게 결정할 수 없다. 시스템을 지켜보며 시스템이 발전함에 따라 주의를 기울여야 한다.
- 경계가 필요할 수 있는 부분에 주목하고, 경계가 존재하지 않아 생기는 마찰의 조짐을 신중하게 관찰해야 한다.
- 첫 조짐이 보이는 시점이 되면, 경계를 구현하는 비용과 무시할 때 감수할 비용을 가늠해본 후 경계의 구현 비용이 무시해서 생기는 비용보다 적어지는 변곡점에서 경계를 구현하자.
- 목표를 달성하려면 빈틈없이 지켜봐야 한다.
메인 컴포넌트
- 모든 시스템에는 최소 하나의 컴포넌트가 존재하고, 이 컴포넌트가 나머지 컴포넌트를 생성하고 조정, 관리 한다.
- 이 컴포넌트를 메인(Main)이라고 부른다.
궁극적인 세부사항
- 메인 컴포넌트는 궁극적인 세부사항으로, 가장 낮은 수준의 정책이다.
- 메인은 시스템의 초기 진입점이며 운영체제를 제외하면 어떤 것도 메인에 의존하지 않는다.
- 메인은 모든 팩토리와 전략 그리고 기반 설비를 생성한 후, 더 높은 수준을 담당하는 부분으로 제어권을 넘기는 역할을 맡는다.
- 메인은 클린 아키텍처에서 가장 바깥 원에 위치하는, 지저분한 저수준 모듈이다.
- 메인은 고수준의 시스템을 위한 모든 것을 로듷나 후, 제어권을 고수준의 시스템에 넘긴다.
결론
- 메인을 애플리케이션 플러그인이라고 생각하자.
- 메인은 초기 조건과 설정을 구성하고 외부 자원을 수집한 후 제어권을 애플리케이션의 고수준 정책으로 넘기는 플러그인이다.
'크고 작은 모든' 서비스들
서비스 지향 아키텍처
와마이크로서비스 아키텍처
가 최근에 큰 인기를 끌고 있다. 이유는 아래와 같다.- 서비스를 사용하면 상호 결합이 철저하게 분리되는 것처럼 보인다. (일부만 맞다)
- 서비스를 사용하면 개발과 배포 독립성을 지원하는 것처럼 보인다. (일부만 맞다)
서비스 아키텍처?
- 서비스를 사용한다는 것은 본질적으로 아키텍처에 해당하지 않는다.
- 시스템 아키텍처는 의존성 규칙을 준수하며 고수준의 정책을 저수준의 세부사항으로부터 분리하는 경계에 의해 정의된다.
단순히 애플리케이션의 행위를 분리할 뿐인 서비스라면 값비싼 함수 호출에 불과
하며, 아키텍처 관점에서 꼭 중요하다고 볼 수는 없다.- 서비스는 프로세스나 플랫폼 경계를 가로지르는 함수 호출에 지나지 않다. 아키텍처적으로 중요한 서비스도 있지만, 중요하지 않은 서비스도 존재한다.
서비스의 이점?
결합 분리의 오류
- 시스템을 서비스들로 분리하면 서비스 사이의 결합이 확실히 분리된다는 이점을 얻을 수 있음
- 어쨌든 각 서비스는 서로 다른 프로세스에서 실행될 것이며, 서비스는 다른 서비스의 변수에 직접 접근할 수 없다. (인터페이스가 잘 정리되어 있어야함)
- 어느 정도 일리가 있음. 하지만 프로세서 내의 또는
네트워크 상의 공유 자원 때문에 결합
될 가능성이 여전히 존재한다.- 서로 공유하는 데이터에 의해 이들 서비스는 강력하게 결합된다.
- ex. 서비스 사이를 오가는 데이터 레코드에 새로운 필드를 추가한다면, 이 필드를 사용해 동작하는 모든 서비스는 반드시 변경되어야 한다.
- 또한 이 서비스들은 필드에 담긴 데이터를 해석하는 방식을 사전에 완벽하게 조율해야 한다.
- 서비스들은 데이터 레코드에 강하게 결합되고, 서비스들 사이는 서로 간접적으로 결합되어 버린다.
개발 및 배포 독립성의 오류
- 또하나의 이점은 전담팀이 서비스를 소유하고 운영할 수 있음
- 데브옵스전략의 일환으로 전담팀에서 각 서비스를 작성하고, 유지보수하며, 운영하는 책임을 질 수 있다.
- 이러한 개발 및 배포 독립성은 확장 가능한(scalable)것으로 간주된다.
- 대규모 엔터프라이즈 시스템을 독립적으로 개발하고 배포 가능한 수십, 수백, 수천 개의 서비스들을 이용하여 만들 수 있다고 믿음
- 시스템의 개발, 유지보수, 운영 또한 비슷한 수의 독립적인 팀 단위로 분할할 수 있다고 여긴다.
- 어느 정도 일리가 있지만, 극히 일부분이다.
- 첫째, 대규모 엔터프라이즈 시스템은 서비스 기반 시스템 이외에도, 모놀리식 시스템이나 컴포넌트 기반 시스템으로도 구축할 수 있음
- 따라서 서비스는 확장 가능한 시스템을 구축하는 유일한 선택지가 아님
- 둘째, '결합 분리의 오류'에 따르면 서비스라고 해서 항상 독립적으로 개발하고, 배포하며, 운영할 수 있는 것은 아니다.
- 데이터나 행위에서 어느정도 결합되어 있다면
결합된 정도에 맞게 개발, 배포, 운영을 조정
해야만 한다.
야옹이 문제
- 택시 통합 시스템은 많은 택시 업체를 알고 있고, 고객은 승차 요청을 할 수 있음
- 고객은 승차 시간, 비용, 고급 택시 여부, 운전사 경력 등 다양한 기준에 따라 택시를 선택할 수 있다고 가정
- 수 많은 작은 마이크로서비스를 기반으로 구축하기로 결정
- 가상 아키텍트가 서비스를 배치한 모습
- TaxiUI : 고객을 담당하며 고객은 모바일 기기로 택시를 호출
- TaxiFinder : 여러 TaxiSupplier 현황을 검토하여 적합한 택시 후보들을 선별. 사용자에게 할당된 단기 데이터 레코드에 후보 택시들의 정보 저장
- TaxiSelector : 사용자가 지정한 비용, 시간, 고급 여부 등의 조건을 기초하여 적합한 택시를 선택. 해당 택시를 TaxiDispatcher 서비스로 전달
- TaxiDispatcher : 해당 택시에 배차 지시를 함
- 어느날, 마케팅 부서 마케터들이 도시에 야옹이를 배달하는 서비스를 제공하겠다는 계획을 발표
- 사용자는 자신의 집이나 사무실로 야옹이 배달 주문이 가능해짐
- 회사는 도시 전역에 야옹이를 태울 다수의 승차 지점을 설정
- 야옹이 배달 주문이 오면, 근처의 택시가 선택되고, 승차 지점 중 한 곳에서 야옹이를 태운 후, 올바른 주소로 야옹이를 배달
- 일부 택시 업체가 참여
- 어떤 운전자는 고양이 알러지가 있을 수 있으므로 해당 운전자는 서비스에서 제외되어야 함
- 배차를 신청한 고객이 알러지가 있다고 밝혀지면 지난 3일 사이에 야옹이를 배달했던 차량은 배차되지 않아야 함
- 서비스 다이어그램을 살펴보면 의심의 여지 없이 전부다 변경해야 한다.
- 야옹이 배달 기능을 추가하려면 개발과 배포 전략을 모두 신중하게 조정해야 함
- 서비스들은 모두 결합되어 있어서 독립적으로 개발하고, 배포하거나, 유지될 수 없음
- 이것이 횡단 관심사(cross-cutting concern)가 지닌 문제
- 모든 소프트웨어 시스템은 서비스 지향이든 아니든 이 문제에 직면하게 마련이다.
- 위의 서비스 다이어그램에 묘사된 것과 같은 종류의 기능적 분해는 새로운 기능이 기능적 행위를 횡단하는 상황에 매우 취약하다.
객체가 구출하다.
- 컴포넌트 기반의 아키텍처에서는 다형적으로 확장할 수 있는 클래스 집합을 생성해 새로운 기능을 처리하도록 함 (SOLID)다이어그램의 클래스들은 위에서 보여준 서비스들과 거의 일치한다.
- 경계와 의존성들이 의존성 규칙을 준수한다는 점을 주목하자.
- 원래 서비스의 로직 중 대다수가 이 객체 모델의 기반 클래스들 내부로 녹아들었음
- 배차에 특화된 로직 부분은 Rides 컴포넌트로 추출, 야옹이에 대한 신규 기능은 Kittens 컴포넌트로 추출
- 이 두 컴포넌트는 기존 컴포넌트들에 있는 추상 기반 클래스를 템플릿 메서드나 전략 패턴 등으로 오버라이드 함
- 신규 컴포넌트인 Rides와 Kittens는 의존성 규칙을 준수함 또한 이 기능들을 구현하는 클래스들은 UI의 제어하에 팩토리(Factories)가 생성함
- 이 전략을 따르더라도 야옹이 기능을 구현하려면 TaxiUI는 어쩔 수 없이 변경해야하지만 그 외의 것들은 변경할 필요가 없다.
- 대신 야옹이 기능을 구현한 새로운 jar 파일을 시스템에 추가하여, 런타임에 동적 로드하면 됨
- 야옹이 기능은 결합이 분리되며, 독립적으로 개발하여 배포할 수 있음
컴포넌트 기반 서비스
- 그렇다면 "서비스에도 이렇게 할 수 있을까?" 가능하다. 서비스가 반드시 소규모 단일체(monolith)여야할 이유는 없다.
- 서비스는 SOLID 원칙대로 설계할 수 있으며 컴포넌트 구조를 갖출 수도 있다. 이를 통해 서비스 내의 기존 컴포넌트들을 변경하지 않고 새로운 컴포넌트를 추가할 수 있다.
- 자바의 경우, 서비스를 하나 이상의 jar 파일에 포함하는 추상 클래스들의 집합이라고 생각
- 새로운 기능 추가 혹은 기능 확장은 새로운 jar 파일로 만든다.
- 새로운 jar 파일을 구성하는 클래스들은 기존 jar 파일에 정의된 추상 클래스들을 확장해서 만들어진다.
- 새로운 jar 파일을 추가 (기능을 추가하는 행위가 개발 폐쇄 원칙을 준수하게 된다.)
- 서비스들의 존재는 이전과 달라진게 없지만, 각 서비스의 내부는 자신만의 컴포넌트로 설계되어 있어 파생클래스를 만드는 방식으로 신규 기능 추가가 가능
횡단 관심사
- 아키텍처 경계는 서비스 사이에 있지 않다.
- 오히려 서비스를 관통하며, 서비스를 컴포넌트 단위로 분할
- 모든 주요 시스템이 직면하는 횡단 관심사를 처리하려면 아래의 다이어그램 처럼 서비스 내부는 의존성 규칙도 준수하는 컴포넌트 아키텍처로 설계해야 한다.
- 서비스들은 시스템의 아키텍처 경계를 정의하지 않는다. 경계를 정의하는 것은 서비스 내에 위치한 컴포넌트
테스트 경계
- 테스트는 시스템의 일부이며, 아키텍처에도 관여
시스템 컴포넌트인 테스트
- 아키텍처 관점에서 모든 테스트는 동일하다. (유닛 테스트, 통합 테스트 등등)
- 테스트는 태생적으로
의존성 규칙
을 따르며, 테스트는 세부적이며 구체적인 것으로 의존성은 항상 테스트 대상이 되는 코드를 향한다. - 테스트는 아키텍처에서 가장 바깥쪽 원에 위치하며, 내부의 어떤 것도 테스트에 의존하지 않는다. (테스트는 시스템 컴포넌트를 향해 항상 안쪽으로 의존한다.)
- 테스트는 독립적으로 배포 가능하며, 컴포넌트 중에서 가장 고립되어 있다.
- 어떤 사용자도 테스트에 의존하지 않으며, 테스트의 역할은 운영이 아니라 개발 지원이다.
테스트를 고려한 설계
- 테스트가 시스템 설계와 잘 통합되지 않으면, 테스트는 깨지기 쉬워지고, 시스템은 뻣뻣해져서 변경하기 어려워진다.
- 시스템에 강하게 결합된 테스트라면 시스템이 변경될 때 함께 변경되어야만 한다.
- 시스템 컴포넌트에 생긴 사소한 변경도, 이와 결합된 수많은 테스트를 망가뜨릴 수 있다.
- 시스템의 공통 컴포넌트가 변경되면 수백, 심지어 수천 개의 테스트가 망가진다. (Fragile Test Problem)
- 깨지기 쉬운 테스트는 시스템을 뻣뻣하게 만든다는 부작용을 낳을 때가 많다.
- 시스템에 가한 간단한 변경이 대향의 테스트 실패로 이어진다는 사실을 알게 되면, 개발자는 그러한 변경을 하지 않으려 든다.
- 마케팅팀에서 페이지 탐색 구조를 손봐달라고 요청했는데, 이로 인해 망가지는 테스트가 1,000개라면..? 두 팀 사이의 대화는 ㅜ_ㅜ
- 하여
테스트를 고려해서 설계해야 한다.
- 변동성이 있는 것에 의존하지 말라.
- GUI같은 변동성이 큰 시스템이 아닌 업무 규칙을 테스트할 수 있게 해야 한다.
앱-티튜드 테스트
켄트 벡이 말하는 소프트웨어 구축하는 세가지 활동
- "먼저 동작하게 만들어라"
- 소프트웨어가 동작하지 않는다면 사업은 망한다.
- "그리고 올바르게 만들어라"
- 코드를 리팩터링해서 당신을 포함한 나머지 사람들이 이해할 수 있게 만들고, 요구가 변경되거나 요구를 더 잘 이해하게 되었을 때 코드를 개선할 수 있게 만들어라.
- "그리고 빠르게 만들어라"
- 코드를 리팩터링해서 '요구되는'성능을 만족시켜라
반응형
'프로그래밍 노트 > 아키텍처' 카테고리의 다른 글
응집도(Cohesion)과 결합도(Coupling) (0) | 2024.10.07 |
---|---|
[클린 아키텍처] 4장. 컴포넌트 원칙 (4) | 2023.11.09 |
[클린 아키텍처] 3장. 설계 원칙 - 좋은 아키텍처를 정의하는 원칙(SOLID) (1) | 2023.10.17 |
[클린 아키텍처] 객체 지향 프로그래밍 (0) | 2023.09.04 |
[클린 아키텍처] 행위, 아키텍처 두가지 가치에 대하여 (0) | 2023.08.28 |