세부사항과 경계
- 6.1 서비스는 아키텍처가 아니다
- 6.2 서비스의 이점이라는 환상
- 6.3 야옹이 문제와 횡단 관심사
- 6.4 테스트는 시스템 컴포넌트다
- 6.5 깨지기 쉬운 테스트 문제
- 6.6 테스트 API
- 6.7 클린 임베디드 아키텍처
- 6.8 HAL과 OSAL
- 6.9 데이터베이스는 세부사항이다
- 6.10 웹은 세부사항이다
- 6.11 프레임워크와 비대칭적 결혼
서비스를 쪼갠다고 자동으로 좋은 아키텍처가 되는 건 아니야. 데이터베이스도 웹도 프레임워크도 전부 세부사항이지. 진짜 아키텍처는 의존성 규칙을 따르는 경계에 의해 정의돼.
마이크로서비스가 대세인 시대에 저자가 찬물을 끼얹어. 서비스를 사용한다고 해서 아키텍처가 되는 건 아니야. 함수 호출이든 서비스 호출이든, 아키텍처적으로 중요한 건 의존성 방향이지. 마이크로서비스를 옹호하는 사람들이 흔히 주장하는 이점들을 까보면 -- "결합 분리"라고 하지만 공유 데이터 구조가 있으면 서비스들은 강하게 결합돼. 데이터 필드 하나 바꾸면 관련 서비스를 모두 수정해야 하잖아. "독립적 개발과 배포"라고 하지만 단일체에서도 컴포넌트 분리를 잘 하면 같은 수준의 독립성을 얻을 수 있어.
저자가 택시 배차 시스템 예시를 들어. 잘 돌아가는 마이크로서비스 시스템인데, 야옹이 기능(kitten delivery) -- 고양이 배달 서비스를 추가해 달라는 요구사항이 들어와. 이게 기존의 거의 모든 서비스를 횡단해. 배차 서비스, 요금 계산 서비스, 지도 서비스 등 전부 수정해야 하지. 서비스로 분리했다고 해서 이런 **횡단적 변경(cross-cutting concern)**에서 자유로운 게 아니야. SOLID 원칙을 적용한 객체 지향 설계가 이 문제를 해결해 -- 다형성으로 새 기능을 기존 코드 수정 없이 추가할 수 있거든. 서비스 안에도 SOLID 원칙에 따라 컴포넌트 구조를 만들 수 있어. 서비스는 아키텍처 경계를 정의하는 유일한 방법이 아니며, 가장 좋은 방법도 아닐 수 있지.
테스트도 시스템의 일부야. 테스트는 항상 가장 바깥쪽 원에 있어 -- 시스템의 어떤 것도 테스트에 의존하지 않지만, 테스트는 시스템 안쪽의 코드에 의존하지. 잘 설계되지 않은 테스트는 시스템을 뻣뻣하게 만들 수 있어. 깨지기 쉬운 테스트 문제(Fragile Tests Problem) -- 시스템에 변경을 가하면 수백 개의 테스트가 깨지는 상황. GUI를 통해서 업무 규칙을 테스트하면 GUI가 조금만 바뀌어도 수천 개의 테스트가 깨져. 해결책은 변동성이 있는 것에 의존하지 않는 것이야. 업무 규칙을 직접 테스트할 수 있는 테스트 API를 만들자. 보안 제약, UI, 데이터베이스를 무시하고 업무 규칙에 직접 접근하는 별도의 API야.
임베디드 소프트웨어 이야기도 원칙은 같아. 많은 임베디드 소프트웨어가 "일단 동작하게 만들기"에만 집중하고 깨끗하게 만들기는 신경 쓰지 않아. 소프트웨어가 하드웨어에 너무 강하게 결합되면 하드웨어와 함께 쓸모없어지지. 타깃-하드웨어 병목현상 -- 코드가 특정 마이크로컨트롤러에 직접 의존하면 그 하드웨어 없이는 테스트가 불가능해. 해결은 **HAL(하드웨어 추상화 계층)**이야. 소프트웨어와 펌웨어 사이에 HAL을 두면, 소프트웨어는 HAL의 인터페이스에만 의존해. 하드웨어가 바뀌면 HAL 구현만 바꾸면 되지. 운영체제도 세부사항이라서 **OSAL(운영체제 추상화 계층)**을 통해 분리할 수 있어.
데이터베이스는 세부사항이야. 관계형 데이터베이스는 우아한 기술이지만, 기술일 뿐이야. 데이터베이스 시스템이 이토록 널리 퍼진 건 디스크 때문이야 -- 디스크에서 데이터를 효율적으로 가져오려면 인덱스, 캐시, 쿼리 최적화가 필요하니까. 디스크가 RAM으로 대체되고 있는 지금, 데이터베이스는 본질적으로 디스크 표면과 RAM 사이에 데이터를 옮기는 기술일 뿐이야. 데이터는 중요하지만 데이터베이스는 중요하지 않아. 성능도 캡슐화해서 업무 규칙과 분리할 수 있는 저수준의 관심사야.
웹도 세부사항이야. 소프트웨어 산업은 진자처럼 왔다 갔다 하거든 -- 중앙집중과 분산, 서버 렌더링과 클라이언트 렌더링. 더미 터미널에서 데스크탑 앱으로, 웹 브라우저로, Ajax로, 다시 서버 사이드로. 이게 계속 반복돼. 웹은 이 진자의 한 지점일 뿐이고, 진자는 또 움직일 거야. UI는 세부사항이야. 업무 규칙을 UI에서 분리해야 해.
프레임워크도 세부사항이야. 프레임워크 저자는 네 문제를 모르거든. 자기 문제를 해결하려고 프레임워크를 만든 거지. 프레임워크와 네 관계는 비대칭적이야 -- 프레임워크는 엄청난 헌신을 요구하지만(클래스 상속, 어노테이션, 규칙 준수), 프레임워크 저자는 너를 알지도 못해. 초기에는 편리하지만 프로젝트가 성장하면서 프레임워크가 제약이 될 수 있고, 더 좋은 프레임워크로 바꾸고 싶어도 너무 깊이 얽혀 있어서 못 바꿔.
해결책은 프레임워크와 결혼하지 않는 거야. 프레임워크를 아키텍처의 바깥쪽 원에 두고, 안쪽 원으로 침투하지 못하게 막아. Spring은 좋은 도구지만, 업무 규칙의 엔티티와 유스케이스에 @Autowired 같은 어노테이션을 붙이는 순간 업무 규칙이 Spring에 결합돼. 물론 가끔은 프레임워크와 결혼해야 할 때도 있어 -- C++에서 STL을 안 쓸 수는 없잖아. 하지만 이건 의식적인 결정이어야 해.
정리
6장 읽고 기억할 거 세 가지:
- 서비스는 아키텍처가 아니야. 서비스로 쪼갠다고 자동으로 경계가 생기는 게 아니고, 서비스 안에도 SOLID 원칙에 따른 컴포넌트 구조가 필요해. 횡단 관심사 앞에서 마이크로서비스의 독립성은 환상일 수 있지.
- 데이터베이스, 웹, 프레임워크는 전부 세부사항이야. 업무 규칙과 경계를 긋고, 이것들이 플러그인으로 교체 가능하게 만들어. 프레임워크와 결혼하지 마.
- 테스트도 아키텍처의 일부야. 깨지기 쉬운 테스트는 시스템을 뻣뻣하게 만들어. 테스트 API를 통해 업무 규칙을 직접 테스트하고, UI 같은 변동성 높은 것에 의존하지 마.