소프트웨어 아키텍처는 선을 긋는 기술이다. 이 선은 소프트웨어를 분리하고 이 선을 사이에 두고 한 쪽은 다른 쪽을 알지 못한다(반대는 알 수 있다). 결합을 줄이고 일찍 결정하지 않아도 되는 요소들을 나중으로 미루는 것이 선 긋기의 목표다. 어떻게 선을 그을까? 그리고 언제 그을까? 관련이 있는 것과 없는 것 사이에 선을 긋는다. GUI 와 비즈니스 로직은 관련이 없기 때문에 GUI 와 비즈니스 로직 사이에는 선이 그어져야 한다. 그리고 비즈니스 로직과 DB 사이에도 선이 그어져야 한다. 어떤 DB 를 사용하더라도 비즈니스 규칙이 그에 따라 변화되어서는 안된다. 이렇게 정하고 보면 인터페이스가 어떤 계층에 있어야 하는지 알게 된다. 기본적으로 스프링 컨트롤러 - 서비스 - 레포지토리 구조에서 DB 인터페..
클린아키텍처
좋은 아키텍처가 지원하는 것들 유스케이스 운영 개발 배포 유스케이스 시스템 아키텍처는 반드시 시스템의 요구사항을 지원해야 한다. 명확한 이름을 짓는 것, 유스케이스 단위로 분리하는 것이 유스케이스를 지원하는 좋은 방법이 될 수 있다. 운영 운영시에 어떠한 환경이 필요한지에 대해 고민하다 보면 시스템마다 다른 아키텍처를 가져가게 된다. 어떤 시스템은 실시간 처리를 해야할 수도 있고, 어떤 시스템은 대용량의 데이터를 다루어야 할 수도 있다. 그리고 또 어떤 시스템은 트래픽이 많지 않아 단순한 요청만 처리하는 시스템일 수 있다. 각각의 특징마다 어떤 시스템은 마이크로 서비스로, 어떤 시스템은 모노리틱 구조로 구현하면 된다. 만약 모노리틱 구조로 시스템 아키텍처를 구현하더라도 소스 코드 단계에서 컴포넌트로 잘 ..
아키텍트는 프로그래밍 작업을 놓아서는 안된다. 아키텍트는 프로그래밍 작업을 통해 발생하는 문제를 경험하고 프로그래머를 지원하는 역할을 해야 한다. 시스템 아키텍처의 목표는 시스템을 쉽게 개발하고 배포하고 운영하며 유지보수 되도록 만드는 것이다. 시스템 아키텍처는 시스템이 제대로 동작하기를 최우선 목표로 삼는 것은 당연하나 아키텍처가 엉망이더라도 시스템은 제대로 동작할 가능성이 크다. 사실상 아키텍처는 운영보다 배포, 유지보수, 개발을 쉽게 하는데에 초점이 맞춰져야 한다. 아키텍처의 주된 목적은 시스템의 생명주기를 지원하는 것이다. 좋은 아키텍처는 시스템을 쉽게 이해하고, 쉽게 개발하며, 쉽게 유지보수하고, 또 쉽게 배포하게 해준다. 아키텍처의 궁극적인 목표는 시스템의 수명과 관련된 비용은 최소화하고, 프..
ADP: 의존성 비순환 원칙 간단히 말하면 컴포넌트 의존성 그래프에 순환이 있으면 안된다는 원칙이다. 최신 스프링 부트 버전에서는 컴포넌트는 아니더라도 빈의 의존성이 순환을 하게 되면 에러를 발생시킨다. 순환 의존성 제거 개발 환경을 릴리스 가능한 컴포넌트 단위로 분리한 후 컴포넌트별로 담당자 또는 담당팀을 지정한다. 컴포넌트가 동작 가능하도록 만들고 릴리스 번호를 부여해 다른 팀에서 사용할 수 있도록 만들면 다른 팀은 해당 컴포넌트를 사용하기만 하면 된다. 만약 새로운 릴리스 버전이 만들어졌다고 하면, 이것을 사용할지 이전 릴리스 버전을 사용할지는 전적으로 사용하는 사람의 몫이다. 따라서 사용하는 팀과 만드는 팀의 결합이 느슨해졌다. 이러한 구조를 잘 사용하기 위해서는 컴포넌트 의존성이 순환하지 않아야..
REP: 재사용/릴리즈 등가 원칙 재사용 단위는 릴리스 단위와 같다. REP는 당연한 것이다. 한 번에 같이 릴리즈 되어야 하는 클래스들이 한 곳에 뭉쳐야 하고, 이를 지키는 것은 너무나 당연해서 지키지 않았을 때 너무 많이 티가 난다. CCP: 공통 폐쇄 원칙 동일한 시점에 변경되는 클래스를 같은 컴포넌트로 묶어야 한다. 다른 말로 하면 다른 시점에 다른 이유로 변경되는 클래스는 다른 컴포넌트로 분리해야 한다. CCP는 SRP 단일 책임 원칙을 컴포넌트 단위에서 작성한 것이다. CRP: 공통 재사용 원칙 CRP는 함께 재사용되는 경우가 많은 클래스와 모듈은 한 컴포넌트로 묶어야 한다고 말한다. 개별 클래스가 단독으로 사용되는 경우는 거의 없고 다른 클래스와 상호작용 하는 경우가 많다. CRP를 지키려면..
컴포넌트는 자바의 jar 파일과 같은 배포 단위이다. 여러 컴포넌트를 하나로 묶어서 아카이브로 만들 수도 있고, 각각의 컴포넌트를 독립적으로 배포할 수도 있다. 잘 설계된 컴포넌트는 반드시 독립적으로 배포 및 개발이 가능해야 한다. 프로그램이 커질수록 프로그램을 컴파일하고 실행하는데까지 점점 더 많은 시간이 소요되었다. 그러나 그것보다 더 빠르게 메모리의 속도가 빨라지고 가격이 저렴해지면서 이제는 링크 시간이 초 단위 수준이 되었다. 이후 액티브 X 와 공유 라이브러리 시대가 열렸고 이제는 jar 파일이나 DLL, 공유 라이브러리를 기존 애플리케이션에 플러그인 형태로 배포하는 것이 일상적인 일이 되었다. 현재 이 아키텍처가 컴포넌트 플러그인 아키텍처다.
의존 관계에서 구체적인 모듈을 참조해서는 안되지만 이는 사실상 불가능하다. 자바의 String 클래스가 대표적인 예인데, String 클래스는 구체 클래스이나 대부분의 모듈에서 이에 의존한다. 따라서 우리가 의존하는 것을 피해야 할 것은 변동성이 큰 구체적인 요소이다. 안정된 추상화 인터페이스를 최대한 변경하지 않고 새로운 기능을 추가할 수 있는 방법을 가장 먼저 찾아라. 변동성이 큰 구현체에 의존하는 것을 최대한 피하고 안정된 추상 인터페이스를 의존해야 한다. 변동성이 큰 구체 클래스를 참조하지 말라 추상 팩토리 사용을 고려하라 변동성이 큰 구체 클래스로부터 파생하지 말라 상속은 가장 강력한 결합이다 구체 함수를 오버라이드 하지 말라 구체적이며 변동성이 크다면 절대로 그 이름을 언급하지 말라 팩토리 소..
ISP와 언어 정적 타입 언어는 import, use, include 등과 같은 선언문을 사용해야 한다. 이로 인해 소스 코드 의존성이 발생하는데, 루비나 파이썬과 같은 동적 언어는 이러한 의존성이 아예 없어서 정적 타입 언어를 사용할 때보다 결합도가 낮은 시스템을 만들 수 있다. ISP와 아케틱처 시스템을 만들 때 특정 프레임워크를 사용하기로 강제로 결정을 내리고, 해당 프레임워크는 특정 데이터베이스를 강제로 사용해야 한다면, 시스템은 결국 데이터베이스까지 의존성을 가지게 된다. 이는 데이터베이스에 변경사항이 발생하면 프레임워크도 재컴파일 및 재배포를 해야 하고, 그렇게 되면 시스템도 재컴파일 및 재배포를 해야 한다. 또한 시스템이 전혀 사용하지 않는 데이터베이스의 특정 기능이 문제를 일으키면 시스템에..
책에서 리스코프 치환 원칙을 위반하는 좋은 예제를 정사각형/직사각형 문제로 보여준다. 사각형이라는 인터페이스가 있고 사각형 인터페이스는 가로 설정, 세로 설정 메서드를 제공한다. 그리고 이를 구현한 구현체는 직사각형 구현체와 정사각형 구현체가 있다. 사용자가 사각형 인터페이스를 사용할 때, 가로 설정과 세로 설정을 모두 사용한다고 가정하면 정사각형의 구현체를 사용한 사람은 해당 메서드가 원하는대로 동작하지 않을 것이다. 사용하는 입장에서 사용하는 것의 정확한 구현체를 알아야 한다면 이는 리스코프 치환 원칙에 위배된다. LSP는 아키텍처 수준까지 확장할 수 있고, 반드시 확장해야만 한다. 치환 가능성을 조금이라도 위배하면 시스템 아키텍처가 오염되어 상당량의 별도 메커니즘을 추가해야 할 수 있기 때문이다.
소프트웨어 개체는 확장에는 열려 있어야 하고, 변경에는 닫혀 있어야 한다. 의존성 방향이 향하는 것은 화살표가 시작하는 곳의 변경사항을 화살표가 향하는 곳에서 지키기 위해서다. 만약 A -> B 로 화살표가 향한다면, A의 변경사항이 B에 영향을 주지 않게 하기 위함이다. 따라서 가장 중요한 비즈니스가 담긴 로직은 화살표를 받기만 하는 것이 좋고, 세부 사항으로 내려갈 수록 다른 것들을 의존해야 한다. 이런 화살표를 제어할 수 있는 가장 좋은 방법이 Interface 를 사용하는 것이다. OCP는 시스템 아키텍처를 떠받치는 원동력 중 하나다. OCP의 목표는 시스템을 확장하기 쉬운 동시에 변경으로 인해 시스템이 너무 많은 영향을 받지 않도록 하는 데 있다. 이러한 목표를 달성하려면 시스템을 컴포넌트 단위..