클린 아키텍처
몇년간 다양한 아키텍처가 등장했다.
- Hexagonal Architecture
- Onion Architecture
- Screaming architecture
- DCI
- BCE
위 아키텍처의 핵심은 관심사의 분리이다. 소프트웨어의 계층을 나누며 관심사의 분리라는 목적을 달성하려 했다. 각각은 최소한 비즈니스 룰을 위한 계층 하나와 인터페이스를 위한 계층 하나를 가진다.
아키텍처가 갖는 보통의 규칙
- 프레임워크나 라이브러리에 독립적이어야 한다.
- 테스트가 가능해야 한다.
- ex) 비즈니스 로직은 UI, 데이터베이스, 웹서버와 같은 외부 요소 없이도 테스트 가능해야 한다.
- UI에 독립적이어야 한다.
- 웹 UI 로 작동하던 프로그램은 콘솔 UI로 바뀌어도 정상적으로 작동해야 한다.
- 데이터베이스에 독립적이어야 한다.
- 비즈니스 로직이 데이터베이스에 영향을 받으면 안 된다.
- 어떠한 외부 에이전시에도 독립적이어야 한다.
- 비즈니스 로직은 외부 세상에 대해 알 필요가 없다.
맨 위의 다이어그램은 모든 아키텍처를 하나의 아이디어로 결합하려고 한 시도이다.
의존성 규칙 (The Dependency Rules)
위의 다이어그램에서 각 써클은 소프트웨어의 다른 영역을 표현한다. 안으로 들어갈수록 더 고수준의 소프트웨어가 된다. 바깥 써클은 메커니즘이다. 안쪽 써클은 정책이다.
소스코드 의존성은 오직 안으로만 향한다. 안쪽 써클은 바깥쪽 써클에 대해 모른다. 바깥쪽에서 작성된 코드는 안쪽에서 언급되면 안 된다. 함수, 클래스, 변수, 소프트웨어 엔티티 등 모두 동일하다.
같은 데이터 토큰으로, 바깥 써클에서 사용된 데이터 포맷은 안쪽 써클에서 사용되지 않는 것이 좋다. 특히, 포맷이 프레임워크에서 생성됐다면 더더욱 그러하다. 클린 아키텍처에서는 바깥 써클이 안쪽 써클에 어떠한 영향도 주는 것을 원치 않는다.
엔티티 (Entities)
엔티티는 전사적 비즈니스 규칙(Enterprise wide business rules)을 캡슐화한다. 엔티티는 메서드가 있는 객체일 수도 있고, 데이터 구조와 함수의 집합일 수도 있다. 엔티티가 많은 다른 애플리케이션에서 사용될 수 있는 한 이는 문제가 되지 않는다.
엔터프라이즈 수준이 아니고 그냥 단일 애플리케이션을 만든다면, 엔티티는 애플리케이션의 비즈니스 오브젝트이다. 가장 일반적이고 높은 수준의 규칙을 캡슐화한다.
외부에서 어떠한 변화가 일어났을 때에도 엔티티는 변화할 일이 적어야 한다. 이를테면, 페이지 네비게이션이나 보안상의 변경이 일어났다해도 엔티티가 변경되진 않을 것이다. 애플리케이션에 대한 어떤 운영상의 변화도 엔티티 계층에 영향을 미칠 수는 없다.
유즈 케이스 (Use Cases)
이 계층은 애플리케이션에 관련된 비즈니스 규칙을 지닌다. 시스템의 모든 유즈 케이스를 캡슐화하고 구현한다. 유즈 케이스는 엔티티 간의 데이터의 흐름을 지휘한다. 유즈케이스의 목표를 이루기 위해 엔티티가 전사적(enterprise wide) 비즈니스 규칙을 따르게 한다.
유즈 케이스 계층에서의 변화는 엔티티에 영향을 주면 안된다. 그리고 유즈 케이스 밖 영역에 있는 다양한 계층은 유즈 케이스 계층에 영향을 주지 못한다.
그러나, 애플리케이션의 운영에 변화가 생기면 유즈 케이스에 영향을 줄 수 있다. 유즈 케이스의 세부사항에 변화가 생기면, 이 계층의 코드에도 분명 영향이 갈 것이다.
인터페이스 어댑터 (Interface Adapters)
이 계층의 소프트웨어는 유즈 케이스와 엔티티를 위한 가장 편리한 포맷에서부터 웹이나 데이터베이스와 같은 에이전시를 위한 포맷까지 변환해주는 어댑터들의 모음이다. 이를테면, GUI 의 MVC 아키텍처를 완전히 포함한다. Presenters
, Views
, Controllers
가 다 여기에 포함된다. Model
은 컨트롤러에서 유즈 케이스로 넘어가는 데이터 구조일 것이다. 또한 유즈 케이스에서 다시 Presenters
와 Views
로 넘어간다.
비슷하게, 데이터는 여기서 엔티티와 유즈케이스에 가장 편리한 형식에서 사용되는 영속성(persistence) 프레임워크에 가장 편리한 형태로 변환된다. 이 계층에서는 데이터베이스의 벤더 등에 대해 알 수 없다. 만일 데이터베이스가 SQL 데이터베이스라면, 모든 SQL 을 이 계층으로 제한해야 한다. 이 계층에서도 특정한 부분(Repository
)만 데이터베이스와 연관이 있어야 한다.
이 계층 안에는 외부 서비스와 같은 어떤 외부 형태에서 유즈 케이스와 엔티티에 사용되는 내부 형태로 변환시켜주는 필수적인 어댑터들이 있다.
프레임워크와 드라이버 (Frameworks and Drivers)
가장 바깥 계층은 데이터베이스나 웹 프레임워크 같은 도구와 프레임워크로 이루어져 있다. 이 계층에서는 주로 많은 코드를 작성하지 않는다. 그냥 안쪽에 있는 써클들과 통신할 수 있는 접착제 코드(glue code) 등을 작성한다.
이 계층에 모든 구체적인 것들이 있다. 웹이나 데이터베이스가 구체적인 것들이다. 우리는 이 계층에 있는 것들을 가장 적은 손해를 끼치는 바깥쪽에 위치시켜야 한다.
오직 4개의 써클만 존재하는가?
아니다. 써클은 그냥 어떤 틀같은 것이다. 4개의 써클 이외에 또 다른 써클이 필요할 때도 있다. 의존성 규칙(The Dependency Rules) 을 지키는 한 다른 써클을 추가해도 된다. 추상 수준이 높아질수록 써클은 안쪽으로 가야 한다. 바깥으로 갈수록 더 변화하기 쉬운 구체적인 것들을 둔다.
경계를 넘는 것
위의 다이어그램 우측 하단에 어떻게 경계를 넘는지에 대한 예가 있다. Controllers
와 Presenters
는 다른 계층에 존재하는 Use Cases
를 통해 통신하고 있다. 제어의 흐름을 기억해야 한다. Controllers
에서 시작하여 Use Cases
로 이동했다가 Presenters
에서 실행되며 마무리된다. 소스코드 의존성도 다시 한번 기억하자. 둘은 안쪽 Use Cases
를 가리킨다.
보통 의존성 역전 원칙을 이용하여 이 명백한 모순(apprent contradiction)을 해결한다. 자바 같은 언어에서는 인터페이스와 상속 관계를 정리하여 소스코드 의존성이 경계를 넘는 올바른 지점에서 제어의 흐름을 역전하도록 한다.
이를테면, Use Cases
가 Presenters
를 호출해야 한다고 하자. 그런데 만일 직접 호출한다면, 의존성 규칙(The Dependency Rules) 을 위반하게 된다. 안쪽에서 바깥쪽을 호출할 수 없다. 그래서 우리는 Use Cases
가 인터페이스(Use Case Output Port
) 를 호출하게 한다. 그리고 바깥에 있는 Presenters
에서는 이 인터페이스를 구현하게 된다.
아키텍처의 모든 경계를 넘어다니기 위해 같은 테크닉이 사용된다. 제어 흐름의 역전을 만드는 소스 코드 종속성을 생성하기 위해 동적인 다형성의 이점을 취했다. 결국엔 제어의 흐름이 어느 방향으로 가든 종속성 규칙을 유지할 수 있게 됐다.
어떤 데이터가 경계를 넘는가?
보통 경계를 넘는 데이터는 간단한 자료 구조이다. 원한다면, 간단한 자료 구조나 심플한 데이터 전달 오브젝트를 사용할 수 있다. 이 데이터는 함수 호출의 인자가 될 수 있다. 혹은 해쉬 맵에 넣거나 생성자를 이용해서 오브젝트로 만들어 패킹할 수도 있다. 가장 중요한 것은 고립된 간단한 데이터 구조가 경계를 넘어다닌다는 것이다. 데이터베이스 rows 와 엔티티를 속여서 통과시키지 말자. 의존성 규칙 을 위반하는 데이터 구조를 만들지 말자.
이를테면, 많은 데이터베이스 프레임워크는 응답에서 편리한 데이터 포맷을 반환한다. 보통 RowStructure
라고 부른다. RowStructure
가 경계 안쪽으로 넘겨지면, 안쪽 써클에서는 바깥 쪽 써클에 대한 정보를 알게 돼 의존성 규칙 을 위배하게 된다.
만일 넘긴다면, DTO 같은 것에 담아서 넘기는 것이 좋다. 경계를 넘어 데이터를 전달할 때는 안쪽 써클에 가장 편리한 폼 안에 담겨있어야 한다.
결론
위의 간단한 규칙들을 지키는 것은 어렵지 않다. 규칙을 지킴으로써, 우리를 미래에 일어날 많은 머리 아픈 일들에서 해방시켜줄 것이다. 소프트웨어를 계층으로 나누고, 의존성 규칙 을 따름으로써, 우리는 위의 독립적으로 동작하는 본질적으로 테스트 가능한 시스템을 만들게 될 것이다. 데이터베이스나 웹 프레임워크와 같은 어떠한 외부의 시스템이 구식이 되었을 때, 최소한의 수고만 하여 구식이 된 부분을 변경할 수 있을 것이다.
레퍼런스
'Java > 자바 잡지식' 카테고리의 다른 글
자바 @Retention 애노테이션 정리 (0) | 2022.04.23 |
---|---|
자바 EE 란? (0) | 2022.04.20 |
Test Double 이란? (0) | 2022.04.13 |
Gradle 에서 Plugins 의 역할은 무엇일까? (0) | 2022.04.03 |
자바의 synchronized 키워드 정복하기 (0) | 2022.04.03 |