iOS/Architecture

클린아키텍쳐 5부 정리 (15장~21장)

HaningYa 2022. 4. 24. 11:37
728x90

15장 아키텍처란

소프트웨어 아키텍트도 프로그래머이다.
아키텍트도 실제 프로그래밍 작업에 지속적으로 참여해야한다.
소프트웨어 아키텍처: 시스템을 구축했던 사람들이 만들어낸 시스템의 형태

시스템이 쉽게 개발, 배포, 운영 유지보수 되도록 하기위해서는 가능한 한 많은 선택지를, 가능한 한 오래 남겨두는 전략을 따라야 한다.

좋은 아키텍처의 조건과 저자의 경험들

개발

팀 구조가 다르면 아키텍처 관련 결정에도 차이가 난다.

저자의 경험 썰

  • 배포
  • 운영
  • 유지보수
  • 선택사항 열어두기
  • 장치 독립성
  • 광고 우편
  • 물리적 주소 할당

결론

세부사항에 대한 결정은 가능한 한 오랫동안 미룰 수 있는 방향으로 설계해야 한다.


16장 독립성

좋은 아키텍처는 다음을 지원한다.

  • 시스템의 유즈케이스
  • 시스템의 운영
  • 시스템의 개발
  • 시스템의 배포

유즈케이스

아키텍처는 시스템의 의도를 지원해야 한다.
시스템이 지닌 의도를 아키텍처 수준에서 알아볼 수 있어야 한다.

운영

스펙에 관련된 각 유스케이스에 걸맞는 처리량과 응답시간을 보장해야 한다.
모노리틱, msa 등의 형태

개발

아키텍처는 개발환경을 지원하는데 있어 핵심적인 역할을 수행한다.
콘웨이 법칙: 시스템을 설계하는 조직이라면 어디든지 그 조직의 의사소통 구조와 동일한 구조의 설계를 만들어 낼 것 이다.
잘 격리되어 독립적으로 개발 가능한 컴포넌트 단위로 시스템을 분할 할 수 있어야 한다.

배포

좋은 아키텍처는 시스템이 빌드된 후 즉각 배포할 수 있도록 지원해야 한다.
그러기 위해 시스템을 컴포넌트 단위로 적절하게 분할하고 격리시켜야 한다.

선택사항 열어놓기

좋은 아키텍처는 컴포넌트 구조와 관련된 관심사들 사이 균형을 맞춘다,
근데 실질적으로 변화하는 현실 앞에 불가능하다. 그래서
좋은 아키텍처는 선택사항을 열어 둠으로써 향수 시스템에 변경이 필요할때 쉽게 변경할 수 있도록 한다.

계층 결합 분리

SRP와 OCP를 적용하여 의도와 맥락에 따라서 다른 이유로 변경되는 것들은 분리하고 동일한 이유로 변경되는 것들은 묶는다.

다른 이유로 변경되는 예시

  • 유스케이스에서 UI부분과 업무 규칙을 분리
  • 이들 규칙은 서로 다른 속도, 이유로 변경된까 분리하고 독립적으로 변경될 수 있도록 해야함
    • 필드 유효성 검사 -> 앱 자체에 밀접한 업무규칙
    • 계좌 이자계산 -> 업무 도메인에 밀접한 규칙
  • 디비 쿼리 스키마도 업무 규칙이나 UI와 관련없음
  • 이러한 부분을 시스템의 나머지 부분으로 분리하여 독립적으로 변경 가능하도록 해야함

수평계층으로 분리
-> UI , 앱 특화 업무 규칙, 앱 독립 업무 규칙, 데이터 베이스 등등

유스케이스 결합 분리

유스케이스 자체도 서로 다른 속도와 이유로 변경된다.
각 유스케이스는 UI, 앱 규칙, 도메인 규칙, 디비 각각 일부씩 사용한다.
-> 수평 계층으로 분할하며 동시에 얇은 수직 유스케이스로 시스템을 분할 한다.

저번에 레이어를 나눠도 피처를 칠때 모든 레이어를 조금식 변경해야 할까에 대한 고민이 있었는데 유스케이스로도 시스템을 분할한다는 맥락인것 같다.

중요한건 유스케이스들이 계층의 수직으로 내려가면서 각 계층에서 서로 겹치치 않게 한다.

결합 분리 모드

유스케이스 결합을 분리하면 유스케이스를 위해 수행하는 작업은 운영에도 도움이 된다.
결합을 분리할때 적절한 모드를 선택해야 한다.
여튼 또 좋은 아키텍처는 선택권을 열어두고 결합 분리 모드 또한 하나의 선택지 이다.

개발 독립성

업무 규칙이 UI를 알지 못하면 UI에 중점을 둔 팀은 업무 규칙에 중점을 둔 팀에 그다지 영향을 줄 수 없다.

계층과 유스케이스의 결합이 분리되는 한 시스템의 아키텍처는 그 팀 구조를 뒷받침해줄 것이다.

마이크로팀이 아닌 단일팀으로서의 아키텍처.. 굳이 모듈화 불필요..?

배포 독립성

운영중인 시스템에서도 계층과 유스케이스를 교체할 수 있다.

중복

일반적으로 중복은 나쁘지만 우발적인 중복도 있다.
무조건 중복이 나쁜게 아니라 각자의 경로로 발전 가능성이 있는 코드는 중복되도 된다.

결합 분리 모드(다시)

계층과 유즈케이스 불니하는 방법은 다양하다.

  • 소스 코드 수준에서 분리
    • 소스코드 모듈 사이 의존성을 제어할 수 있다.
    • 하나의 모듈이 변하더라도 다른 모듈을 변경하거나 재컴파일 하지 않아도 된다.
    • 예시) 타겟?
  • 바이너리 코드(배포 수준)에서 분리
    • 배포 가능한 단위들 사이의 의존성을 제어할 수 있다.
    • 한 모듈의 소스코드가 변하더라도 재빌드, 배포가 필요없다.
    • 동일 프로세서의 다른 프로세스에 상주하며 IPC, 소켓, 공유 메모리를 통해 통신한다.
    • 결합이 분리된 컴포넌트가 jar파일, Gem파일, DLL과 같이 독립적으로 배포할 수 있는 단위로 분할되어 있다.
    • 예시) 코코아팟?
  • 실행 단위 수준에서도 분리
    • 의존하는 수준을 데이터 구조 단위까지 낮추고 순수 네트워크 패킷을 통해서만 통신하도록
    • 모든 실행 가능한 단위는 소스와 바이너리 변경에 대해 서로 완전히 독립적
    • 예시) 서비스 또는 마이크로 서비스

좋은 아키텍처는 모노리틱 구조 -> 독립 배포 가능한 단위의 집합 -> 독립적인 서비스, 마이크로 서비스 수준까지 성장 또는 상황이 바뀔 경우 반대로
이러한 변경으로부터 소스코드 보호

결론

이렇게 하기 어려운거 안다.
근데 뛰어난 아키텍트라면 이러한 변경을 예측해서 쉽게 반영할 수 있어야 한다.


17장 경계: 선 긋기

소프트웨어 아키텍처는 선을 긋는 기술
이른 결정으로 인해 결합이 생기고 인적 자원의 효율이 떨어짐

이른 결정이란?
-> 업무 요구사항, 유스케이스와 아무런 관련 없는 결정(프레임워크, 디비, 웹서버 등)

두 가지 슬픈 이야기

개발 초기 구현에 대한 구체적인 결정을 하여 세부사항들의 결합도 때문에 낭패를 본 이야기

FitNesse

개발 초기 세부 사항은 결정하지 않고 여러가지 가능성을 열어두고 개발해서 득본 이야기

어떻게 선을 그을까? 그리고 언제 그을까?

GUI | 업뮤 규칙 | 데이터베이스 처럼 관련 없는것 끼리 선이 있어야 한다.
데이터 베이스와 업무규칙이 관련있다고 생각할 수도 있는데 아니다!
업무 규칙은 디비 인터페이스를 통해 디비에 접근해야 한다.
이 경우 경계선은 디비와 디비 인터페이스 사이에 그려진다.
선의 방향: 디비는 업무 규칙을 알지만 업무 규칙은 디비를 몰라야 한다.

입력과 출력은?

GUI 는 중요하지 않다.

플러그인 아키텍쳐

디비와 GUI관련해서 내린 두가지 결정을 보면 플러그인 패턴이 나온다.

플러그인에 대한 논의

시스템을 플러그인 아키텍처로 배치함으로써 변경이 전파될 수 없는 방화벽을 만들 수 있다.
경계는 변경의 축이 있는 지점에 그어진다.
업무 규칙과 의존성 주입 프레임워크도 경계가 필요하다
SRP에 해당한다. SRP는 어디에 경계를 그어야 할지 알려준다.

결론

경계를 그리기 위해서는 시스템을 컴포넌트 단위로 분할해야 한다.
일부 컴포넌트는 핵심규칙 다른 컴포넌트는 플러그인으로 구성된다.
컴포넌트 사이의 화살표가 핵심 업무를 향하도록 소스를 배치한다.
DIP, SRP 원칙을 응용한 것 이다.


18장 경계 해부학

아키텍처는 컴포넌트들을 분리하는 경계에 의해 정의된다.
경계는 다양한 형태로 나타난다.

경계 횡단하기

런타임에 경계를 횡단: 경계 한쪽에 있는 기능에서 반대편 기능을 호출하여 데이터를 전달
적절한 위치에서 경계를 횡단하는 비결은 소스코드 의존성 관리!
경계는 변경이 전파되는 것을 막는 방화벽을 구축하고 관리하는 수단이다.

두려운 단일체

경계 중에서 가장 흔한 형태: 물리적으로 엄격하게 구분되지 않는 형태

  • 함수 및 데이터가 단일 프로세서에서 같은 주소공간을 공유
  • 그저 나름의 규칙에 따라 분리되어 있음
  • 이를 이전장에서는 "소스 수준 분리 모드"라고 부름
  • 배포관점에서는 그냥 단일 실행 파일

위와 같이 배포관점에서는 단일체는 경계가 없음
이러한 아키텍처는 동적 다형성에 의존

가장 단순한 형태의 경계횡단: 저수준 클라이언트에서 고수준 서비스로 향하는 함수 호출
런타임, 컴파일 타임 의존성 모두 저->고수준 컴포넌트로 향함

고수준 클라이언트가 저수준 서비스를 호출해야 한다면 "동적 다형성" 사용하여 의존성 역전

이러한 방식으로 모노리틱 구조의 실행파일도 규칙적인 방식으로 구조분리하면 도움이 된다.

배포형 컴포넌트

아키텍처의 경계가 물리적으로 드러나는 경우: 동적 라이브러리
컴포넌트를 동적 라이브러리로 배포하면 따로 컴파일 하지 않고 바로 사용가능

배포과정만 차이가 있고 배포 수준은 단일체와 동일하다.
같은 프로세서의 주소공간에 위치하는 것도 단일체와 동일하다.

스레드

단일체와 배포형 컴포넌트는 모두 스레드를 활용할 수 있다.
모든 스레드가 단 하나의 컴포넌트에 포함될 수도 있고 많은 컴포넌트에 분산될 수도 있다.

로컬 프로세스

강한 물리적 형태를 띠는 아키텍처의 경계: 로컬 프로세스
주로 명령행이나 시스템 호출을 통해 생성된다.
각각이 독립된 주소 공간에서 실행된다.

각 로컬 프로세스는 정적으로 링크된 단일체거나 동적으로 링크된 여러개의 컴포넌트이다.

로컬 프로세스를 일종의 최상위 컴포넌트라 생각하면 로컬 프로세스는 컴포넌트 간 의존성을 동적 다형성을 통해 관리하는 저수준 컴포넌트로 구성된다...?

무슨 말이지 왜 아키텍처 얘기에 스레드 프로세스가 나오지..?
프로세스도 일종의 경계라는 건지는 알겠는데 한 컴포넌트들이 어떤 같은/다른 프로세스를 사용하는것도 일종의 경계인것 같긴 한데 이렇게 까지..?

서비스

물리적인 형태를 띠는 가장 강력한 경계: 서비스
서비스는 프로세스로 일반적인 명령 또는 시스템 호출을 통해 구동
물리적 위치에 구애받지 않음
서비스 경계를 지나는 통신은 함수 호출에 비해 매우 느림
통신 지연에 따른 문제를 고수준에서 처리할 수 있어야함

결국 저수준의 서비스던 컴포넌트던 뭐던은 고수준에 플러그인 되어야함

결론

단일체를 제외한 다른 시스템들은 한가지 이상의 경계 전략을 사용
대체로 한 시스템 안에서 통신이 빈번한 로컬 경계와 지연을 중요하게 고려해야 하는 경계가 혼합


19장 정책과 수준

소프트웨어 시스템이란 정책을 기술한 것
프로그램의 핵심은 정책이다.
정책은 각 입력을 출력으로 변환하는 정책을 상세하게 기술한 설명서

정책은 여러개의 정책들로 쪼갤 수 있다.
이러한 정책을 신중하게 분리하고 변경되는 양상에 따라 재편성 해야한다.

아키텍처 개발에는 재편성된 컴포넌트들이 비순환 방향 그래프로 구성하는 기술을 포함한다.
이러한 의존성은 컴파일 타임의 의존성이다. (자바 import)

좋은 아키텍처는 의존성 방향이 컴포넌트 수준을 기반으로 연결(저수준이 고수준에 의존하도록)

수준

수준: 입력과 출력까지의 거리
시스템의 입력과 출력으로부터 멀리 위치할 수록 정책의 수준은 높아진다.

입출력에 관계없는 로직일 수록 고수준 정책 이해쏙쏙
접근성도 하나의 정책으로서 입출력에 관계없으니까 고수준 정책이니 저수준 정책과 소통시 인터페이스

정책을 컴포넌트로 묶는 기준은 변경되는 방식에 따라 달라진다.
고수준은 변경될 가능성이 적고 저수준은 빈번하게 변경 될 수 있음

결국 또 저수준 컴포넌트가 고수준 컴포넌트에 플러그인 되어야 한다는것

결론

정책에 대한 논의는 SRP, OCP, 공통폐쇄원칙 DIP, 안정된 의존성 원칙, 안정된 추상화 원칙 모두 포함


20장 업무 규칙

업무 규칙이 무엇인가?

  • 사업적으로 수익을 얻거나 비용을 줄일 수 있는 규칙 또는 절차
  • 컴퓨터상 구현했는지와 상관없이 규칙을 통해 사업적으로 수익을 얻거나 비용을 줄일 수 있어야 한다. (사람이 이자를 계산하던 컴퓨터가 계산하던 상관없는 것 처럼)

핵심 업무규칙과 핵심 데이터

  • 핵심업무 규칙: 규칙을 자동화 하는 시스템 없더라도 사업 자체에 핵심적인 규칙
  • 핵심 데이터: 핵심업무 규칙에 필요한 데이터들
  • 핵심 규칙과 핵심 데이터는 결합되어 있어 객체로 만들기 좋은 후보가 된다.

핵심 규칙과 핵심 데이터가 결합된 객체를 엔티티라고 한다.

엔티티

역할

  • 시스템 내부 객체로서 핵심 업무 데이터를 기반으로 동작하는 핵심 업무 규칙을 구체화
  • 핵심 업무 데이터를 직접 포함하거나 매우 쉽게 접근
  • 엔티티의 인터페이스는 핵심 업무 규칙을 구현한 함수들로 구성

핵심 업무 데이터와 핵심 업무 규칙을 하나로 묶어서 별도의 소프트웨어 모듈로 만들어야함 -> OOP에서는 그게 클래스

유스케이스

'자동화'된 시스템이 동작하는 방법을 정의하고 제약하는 업무 규칙도 존재한다.
(수동 환경에서는 사용될 수 없는)
예를들어 신용도가 500보다 낮으면 대출 견적을 제공하지 않는다.

정의

  • 자동화된 시스템이 사용되는 방법
  • 사용자가 제공해야 하는 입력, 사용자에게 보여줄 출력, 해당 출력을 생성하기 위한 처리 단계
  • 핵심 업무 규칙과는 반대로 application-specific한 업무 규칙을 설명한다.

역할

  • 엔티티 내부의 핵심 업무 규칙을 어떻게 언제 호출할지 명시한다.
  • 엔티티가 어떻게 동작할 지 유즈케이스가 제어하는 느낌

특징

  • 유스케이스는 사용자 인터페이스를 기술하지 않는다.
  • 규칙을 통해 사용자와 엔티티 사이 상호작용만을 규정한다.
  • I/O는 유스케이스와 무관하다.
  • 유스케이스는 객체다.
  • 유스케이스는 상호작용하는 엔티티에 대한 참조 데이터 등의 데이터 요소를 포함한다.
  • 엔티티는 자신을 제어하는 유스케이스에 대해 모른다. (DIP의 예시)
  • 엔티티(고수준 개념) <- 유스케이스(저수준 개념)
  • 왜 엔티티는 고수준 유케는 저수준?
    유케는 단일 애플리케이션에 특화되어 입출력에 보다 가깝게 위치하기 떄문

요청 및 응답 모델

유스케이스는 입력을 받아 출력을 생성한다.
이 데이터 구조는 어떤 것 에도 의존하지 않는다.
엔티티 객체를 가리키는 참조를 요청 및 응답 데이터 구조에 포함하려 할 수도 있지만 안된다!
(두개의 목적은 전혀 다르다)

결론

업뮤 규칙= 소프트웨어 시스템이 존재하는 이유, 핵심 기능, 수익 내고 비용 줄이는 코드
아무도 업무 규칙을 건들여선 안된다. 덜 중요한 코드는 플러그인되어야 한다.


21장 소리치는 아키텍처

아키텍처의 테마

아키텍쳐도 애플리케이션의 유즈케이스에 대해 소리쳐야 한다.
아키텍쳐는 프레임워크에 대한 것이 아니다.

도메인별로 디렉토리를 나누는걸 유즈케이스 중심으로 아키텍쳐를 설계하는거고
ViewController, ViewModel, Model 으로 디렉토리를 나누는걸 프레임워크 중심으로 만드는 거라는 생각이 든다.

아키텍처의 목적

프레임워크나 도구환경 상관없이 유스케이스를 지원하는 구조를 기술 할 수 있다.
좋은 소프트웨어 아키텍쳐는 프레임워크, 디비, 웹 서버 등등 도구에 대한 결정은 미룰 수 있도록 한다.

하지만 웹은?

웹은 전달 매커니즘이다. 웹을 통해 전달되는건 세부사항이고 시스템 구조를 지배해서는 안된다.
앱, 웹, 콘솔 앱, 리치 클라이언트 앱, 웹서비스 앱으로도 근본적인 아키텍쳐를 유지한체 전달 할 수 있어야 한다.

흠.. 근본적인 아키텍쳐를 유지한체 전달이라는건 순전히 비즈니스 로직만을 포함하는 영역을 근본적인 아키텍쳐라고 할 수 있는걸까

프레임워크는 도구일 뿐, 삶의 방식은 아니다.

프레임워크가 아키텍처의 중심을 차지하는 일을 막을 수 있는 전략을 개발하라.

예를들어 sheet library를 언제든지 제거 할 수 있게 구조를 짜라는 것 인가

테스트 하기 쉬운 아키텍처

유즈케이스가 최우선이고 프레임워크와는 거리를 둔다면 따로 테스트 프레임워크 없이도 필요한 유즈케이스 전부에 대해 단위 테스트를 할 수 있어야 한다.
(테스트를 돌리는데 웹서버가 필요하면 안된다.)

쉽게 mock stub을 구성할 수 있는 아키텍처?

결론

아키텍처는 시스템의 프레임워크에 대한 얘기 대신 시스템을 얘기해야 한다.


728x90