클린 아키텍쳐
건물을 만들기 전 제대로된 청사진을 만든 후 그것을 보며 만들게 되면 더 튼튼한 건물을 만들 수 있습니다. 소프트웨어도 마찬가지입니다. 클린 아키텍쳐를 설계하면 소프트웨어의 지속적인 관리, 유지보수를 하는데 엄청난 효과를 볼 수 있습니다. 초반에 한 회사에 대한 이야기가 나옵니다. 회사는 성공적으로 애플리케이션을 만듭니다. 그리고 애플리케이션을 유지보수하고 새로운 기능을 추가합니다. 그 과정에서 직원 수는 계속해서 늘어나지만 새로운 기능의 개발속도는 점점 줄어듭니다. 막바지에는 거의 개발을 하지 못하는 지경에 이르게 됩니다. 원인은 바로 클린 아키텍쳐 설계를 잘못했기 때문입니다. 빈약한 아키텍쳐는 지저분한 코드를 만들게 되고 이는 새로운 코드를 추가하거나 기존의 코드를 수정할 때 다양한 문제를 일으킵니다.
저자는 말합니다. 소프트웨어의 기본인 기능과 구조 중에서 구조가 더 중요하다고. 불완전한 기능과 완전한 구조는 얼마든지 수정할 수 있지만 완전한 기능과 불완전한 구조는 이후 소프트웨어에 새로운 기능을 추가하거나 유지보수를 할 때 손대기가 힘듭니다. 때문에 우리는 클린한 아키텍쳐를 만드는데 많은 시간과 공을 들여야 합니다.
SOLID 규칙
어쩌면 이 책은 클린 코드를 읽은 다음에 읽는 것이 더 좋으리라 생각합니다. 왜냐하면 클린 코드에서 보았던 SOLID규칙이 여기서도 등장합니다. 그런데 의미 자체는 조금 다릅니다. 결국 클린한 코드가 모인 메소드와 더 나아간 클래스, 그리고 같은 기능의 클래스가 모여 컴포넌트가 만들어집니다. 이 과정에서 핵심적인 규칙은 결국 일맥상통합니다. 아키텍쳐 관점에서 본 SOLID규칙의 몇가지 사례가 있습니다.
SRP : 서로 다른 액터가 의존하는 코드를 서로 분리한다.(A팀에서 calculatePay기능을 사용하고 B팀에서 reportHours기능을 사용 중일때 개발자가 A팀의 기능을 수정하기 위해서 regularHours를 건들면 B팀의 기능까지 영향을 미치게 된다.)
LSP : 직사각형(Rectangle)을 상속받은 정사각형(Square). 사각형이라는 특성 상 가능할 듯 보이지만 두 변이 다른 직사각형을 모두 같은 정사각형이 상속받는 것은 옳지 않다. 클라이언트가 명확히 필요로 하는 인터페이스가 있다면(정사각형) 범용 인터페이스를 제공하는 것 보다 필요로 하는 것을 제공해주는 것이 맞다는 의미입니다. 이는 하나의 큰 인터페이스 대신 여러개의 작은 인터페이스로 분리하여 사용하지 않는 메소드는 의존하지 않는 것을 말합니다.
ISP : 서로 다른 사용자가 한 클래스를 사용한다고 가정한다. 그러나 사용자들은 그 클래스의 한 메소드만을 각각 사용중이다. 이 경우 사용자들은 사용하지 않는 메소드도 의존하게 된다. 그래서 해당 클래스의 한 메소드를 수정했지만 그 메소드를 사용하지 않는 메소드들도 배포해야 한다.(이 경우 각각 사용자에 인터페이스를 만들어 해당 인터페이스에 의존하도록 한다.)
레이어드 아키텍쳐의 한계
레이어드 아키텍쳐는 그다지 추천하는 방법이 아니라고 했습니다. 물론 뷰와 컨트롤러, 모델 한방향 순서대로 흐르는 의존성은 저수준에서 고수준 정책으로 향하기 때문에 안전하며 기능 기준으로 패키지로 관리되기 때문에 구분이 명확합니다. 개발자는 프로젝트를 처음에 확인했을때 이 코드가 무엇에 관한 코드인지 한번에 알아볼 수 있습니다. 하지만 이 구조가 완전히 안전하다고는 볼 수 없습니다. 개발자 임의로 컨트롤러에서 비즈니스 로직을 거치지 않고 엔티티 수준의 정책을 참조하는 것이 가능합니다. 물론 가능성은 크지 않지만 이는 잠재적인 위협이 될 수 있습니다.
안정성 지표
컴포넌트의 안정성을 측정하는 지표가 있습니다. 들어오고 나가는 의존성의 개수를 세어보는 방법입니다.
- Fan-in : 컴포넌트 내부의 클래스에 의존하는 컴포넌트 외부의 클래스 개수.
- Fan-out : 컴포넌트 외부의 클래스에 의존하는 컴포넌트 내부의 클래스 개수.
불안정성을 I라고 할때, I = Fan-out / (Fan-in + Fan-out) 다. I가 0이면 최고로 안정된 컴포넌트이며 1이면 최고로 불안정한 컴포넌트입니다.
해석하면 내가 의존하는 컴포넌트는 최소화하면서 다른 컴포넌트가 나를 최대한 많이 의존할수록 안정된 컴포넌트라고 할 수 있습니다.
그렇다고 모든 컴포넌트가 안정적이어야 하는 것은 아닙니다.
고수준의 정책일수록 안정된 상태인 것이 좋습니다. 고수준의 정책은 변경이 거의 없습니다. 유틸 클래스를 예로 들수 있습니다. 날짜를 계산하는 유틸리티 클래스를 만든다고 가정할때 날짜가 13월이 생기거나 이런 일은 없다고 봐도 됩니다. 이 클래스는 안정적인 상태가 되어야 합니다. 의존적이지 않아야 합니다.
반면 저수준의 정책은 불안정한 상태여야 합니다. 저수준의 정책은 변경이 잦습니다. 나를 의존하는 컴포넌트가 많아지면 내가 변경될때 그 컴포넌트들 또한 변경해야해서 유지보수성이 떨어집니다. 이경우 불안정한 상태로 컴포넌트를 유지합니다. 추상적이어야 합니다.
이 안정성을 적절히 활용하기 위해 의존성 방향이 중요하고 의존성 역전 원칙(DIP)을 통해 알맞은 추상화 상태를 유지합니다.
비즈니스 로직 분리
좋은 아키텍쳐를 설계하기 위해서는 비즈니스 로직을 분리해야 합니다.
마이크로소프트의 윈도우 운영체제가 있습니다. 이 윈도우 운영체제는 엘지컴퓨터, 삼성컴퓨터, 그 외 다른 모든 기기들에 정상 작동합니다. 기기의 종류를 타지 않습니다. 엘지라고 윈도우엘지, 삼성이라고 윈도우삼성 등 운영체제가 다른 것이 아닙니다. 소프트웨어는 시스템의 정책을 가장 핵심적인 요소로 식별하고 나머지 사항들은 최대한 미루거나 연기할 수 있어야 합니다.
다시 윈도우로 돌아오면 윈도우는 어떤 장치에서 작동이 되어야 하는지 알 바 아닙니다. 윈도우로 무슨 기능을 만들건지를 가장 먼저 정의합니다. 웹 개발에서도 데이터베이스나 어떤 api를 사용할지는 최대한 미룹니다. 최대한 추상화합니다. oracle을 쓸건지 mysql을 쓸건지가 아닌 userId, userPw만 들고와서 로그인 기능을 정의합니다.
정책이 세부사항과 결합되지 않도록 엄격하게 분리합니다
선택사항을 열어둔다.
세부사항들을 최대한 미루거나 연기하는 과정을 가지고 정책들을 만들게 되면 선택사항에 대해 자유롭게 추가할 수 있습니다. 추상화된 객체를 대상으로 다양한 세부사항을 추가할 수 있습니다.
컴포넌트는 격리되어 있으며 비즈니스 로직은 어떤 세부사항에 대해 어떤 정보도 알 수 없습니다. 팀 단위의 개발자들은 다른 계층의 정보를 몰라도 추상화된 객체를 바탕으로 개발할 수 있습니다.
이렇게 되면 크게 두가지로 계층을 분리할 수 있습니다. 비즈니스 정책과 세부사항에 대한 정보.
이 두가지의 경계는 의존성 역전을 사용하여 핵심 로직을 분리해야 합니다. 유저가 로그인하기 위해 아이디, 패스워드를 입력했을 때 핵심 로직에서 직접 db에 접근하여 유저 정보를 가져오면 핵심 로직은 db에 의존적이게 됩니다. DIP를 통해 관심사를 분리하여 핵심 로직은 추상화된 로그인 객체를 가져옵니다. 이처럼 좋은 아키텍처는 유스케이스에 중점을 둡니다.
입력과 출력에서 멀리 떨어진 정책일수록 고수준 정책이며 변경이 덜 일어나고 더 중요한 이유로 변경되는 경향이 있습니다.
클린 아키텍처
이와 같은 규칙들을 바탕으로 클린 아키텍처를 제시합니다. 이외에도 다양한 아키텍처가 있지만 궁극적인 목표는 '관심사의 분리' 입니다. 소프트웨어를 계층으로 분리하여 그 목표를 달성합니다. 이 경우 테스트가 용이해지고 UI 변경이 쉬워지며 데이터베이스나 외부 에이전시에 독립성을 유지할 수 있습니다.
그림은 원의 안쪽으로 향할수록 고수준의 정책을 말합니다. 목표는 고수준의 정책이 저수준의 정책에 의존하지 않아야 합니다. 저수준 -> 고수준 방향으로 향합니다. 원 안의 요소는 외부의 요소에 대해 아무것도 모릅니다. 외부의 원의 요소가 내부의 원에 어떠한 영향도 끼쳐서는 안됩니다.
엔티티는 핵심 업무 규칙을 캡슐화합니다. 외부의 변경에 대해 변경될 가능성이 극히 낮습니다.
유스케이스는 애플리케이션에 특화된 업무 규칙을 포함합니다. 데이터가 들어오고 나가는 모든 흐름에 대해 캡슐화하여 엔티티가 핵심 업무 규칙을 사용해 목적을 달성하도록 이끕니다.
인터페이스 어댑터는 말그대로 외부 에이전시에 맞춰 편리한 형식으로 변환합니다. 데이터베이스가 이용하기 가장 편한 데이터의 형태로 변환시킵니다. 컨트롤러를 통해 웹의 정보를 읽어옵니다. SQL계층은 모두 이 곳에서 정의됩니다.
'JAVA > 요점정리' 카테고리의 다른 글
헥사고날 아키텍처를 사용한 스프링 부트 로그인 API 구현해보기 (0) | 2024.07.12 |
---|---|
구글 클라우드(GKE), 쿠버네티스, 도커 사용하여 스프링부트 프로젝트 배포하기(맥os M3 기준) (0) | 2024.07.04 |
자바 스프링부트 테스트코드 작성 JUnit5 그리고 Mock (0) | 2024.06.18 |
스프링 부트 초기세팅(Spring initializer, gradle kotlin, MariaDB, IntelliJ, Java17) (1) | 2024.06.14 |
클린코드(clean code)를 읽고 요점 정리 및 코딩의 방향성 잡기 (6) | 2024.06.12 |
댓글