logo

DDD, Hexagonal, Onion, Clean, CQRS, ... 모두 종합한 방법 - 1부

[번역] DDD, Hexagonal, Onion, Clean, CQRS, ... How I put it all together

ddd, hexagonal, onion, cqrs, translation


이 포스트는 소프트웨어 아키텍처에 대한 시리즈소프트웨어 아키텍처 연대기의 일부입니다. 해당 포스트들에서 저는 소프트웨어 아키텍처에 대해 배운 내용과 그에 대해 어떻게 생각하는지, 그리고 그 지식을 어떻게 사용하는지에 대해 글을 씁니다. 이 포스트의 내용은 이 시리즈의 이전 포스트를 읽으면 더 이해가 쉽게 될 것입니다.

대학을 졸업한 뒤 저는 몇 년 전까지만 해도 고등학교 교사의 길을 걷다가 그만두고 정규 소프트웨어 개발자가 되기로 결심했습니다.

그때부터, 항상 “잃어버린” 시간을 되찾고 가능한 한 빨리 많은 것을 배워야 한다고 느꼈습니다. 그래서 소프트웨어 디자인과 아키텍처에 특히 집중하며 실험과 읽고 쓰기에 조금 중독되었습니다. 그런 관계로 이 포스트를 쓰는 이유는 제가 배우는 것에 도움이 되기 때문입니다.

지난 포스트들에서 제가 배운 많은 개념과 원칙 또 그것들에 대해 추론하는 방법을 썼습니다. 하지만 저는 이 포스트들이 단지 큰 퍼즐 조각이라 봅니다.

현재 포스트는 이 모든 조각들을 어떻게 맞추는지에 대한 것이며, 이름을 지어야 할 것 같아서 저는 이를 명시적 아키텍처 Explicit Architecture 라고 부릅니다. 게다가, 이 개념들은 모두 “전투 시험을 통과”했으며 아주 까다로운 플랫폼 프로덕션 코드에 사용됩니다. 하나는 전 세계 수천 개의 웹 샵이 있는 SaaS e-com 플랫폼이고, 다른 하나는 월 2천만 개 이상의 메시지를 처리하는 메시지 버스로 2개국에 운영 중인 마켓플레이스 입니다.

시스템의 기초 블록

먼저 EBI포트 & 어댑터 Ports & Adapters 아키텍처를 생각해 봅시다. 두 가지 다 애플리케이션 내부 코드, 외부 코드 그리고 내부와 외부 코드를 연결하는 데 사용되는 코드를 명시적으로 구분합니다.

또한, 포트 & 어댑터 아키텍처는 시스템에서 세 가지 기본 코드 블록을 명시적으로 식별합니다:

  • 어떤 유형의 사용자 상호 작용이라도 동작할 수 있는 유저 인터페이스 user interface
  • 유저 인터페이스가 실제로 작업을 하는 데 사용되는 시스템 비지니스 로직 business logic 또는 애플리케이션 코어 application core
  • 애플리케이션 코어를 데이터베이스, 검색 엔진이나 서드파티 API로 연결하는 인프라스트럭처 Infrastructure 코드

000-explicit-architecture-svg.png

애플리케이션 코어는 정말로 신경 써야 하는 부분입니다. 그것은 우리의 코드가 해야 할 일을 할 수 있게 해주는 것이며, 그것이 바로 애플리케이션입니다. 여러 사용자 상호작용(프로그레시브 웹 앱, 모바일, CLI, API 등)을 다양하게 사용할 수 있지만 실제로 작업을 수행하는 코드는 같은 것이며 애플리케이션 코어에 위치하므로 어떤 UI를 트리거하는 것인지는 중요하지 않습니다.

상상할 수 있듯이, 일반적인 애플리케이션 흐름은 유저 인터페이스 코드에서 애플리케이션 코어를 거쳐 인프라스트럭처 코드로, 그리고 다시 애플리케이션 코어로 이동 후 마침내 유저 인터페이스에 응답을 전달합니다.

010-explicit-architecture-svg.png

도구

시스템에서 가장 중요한 코드인 애플리케이션 코어와는 거리가 멀지만, 데이터베이스 엔진, 검색 엔진, 웹 서버 또는 CLI 콘솔과 같은 애플리케이션에서 사용하는 도구들이 있습니다. (마지막 두 개도 전달 메커니즘 delivery mechanisms)

020-explicit-architecture-svg.png

CLI 콘솔을 데이터베이스 엔진과 같은 “양동이”에 넣는 것이 이상해 보일 수 있지만, 목적의 종류가 다르더라도 실제로는 애플리케이션에서 사용하는 도구들입니다. 주요 차이점은 CLI 콘솔과 웹 서버는 애플리케이션에 무언가를 하라고 지시하는 데 사용되는 반면, 데이터베이스 엔진은 애플리케이션에서 무언가를 하라고 지시한다는 것입니다. 이는 매우 적절한 구분인데 이러한 도구들과 애플리케이션 코어를 연결하는 코드 구축 방법에 강한 영향을 미치기 때문입니다.

도구와 전달 메커니즘을 애플리케이션 코어에 연결

도구를 애플리케이션 코어에 연결하는 코드 단위를 어댑터(포트 & 어댑터 아키텍처)라고 합니다. 어댑터는 비즈니스 로직이 특정 도구와 통신할 수 있도록(그 반대도 가능) 효과적으로 코드를 구현한 것입니다.

애플리케이션에 어떤 작업을 수행하도록 지시하는 어댑터를 기본 또는 주도하는 어댑터 Primary or Driving Adapters 라고 하며, 애플리케이션에서 어떤 작업을 수행하도록 지시하는 어댑터를 보조 또는 주도되는 어댑터Secondary or Driven Adapters 라고 합니다.

포트

그러나 이러한 어댑터는 무작위로 생성된 것이 아닙니다. 포트는 매우 특정한 진입점을 애플리케이션 코어에 맞추기 위해 생성됩니다. 포트는 도구가 애플리케이션 코어를 사용하는 방법 또는 애플리케이션 코어에서 사용되는 방법에 대한 사양에 불과합니다. 이 사양인 포트는 대부분 언어와 가장 단순한 폼에선 인터페이스 Interface가 되겠지만, 실제로는 여러 인터페이스와 DTO로 구성될 수 있습니다.

포트(인터페이스)는 비즈니스 로직 내부에 속하는 반면, 어댑터는 외부에 속한다는 점에 유의해야 합니다. 이 패턴이 제대로 동작하려면, 단순히 도구 API를 모방하는 것이 아니라 애플리케이션 코어의 요구에 맞게 포트를 만드는 것이 가장 중요합니다.

기본 또는 주도하는 어댑터 Primary or Driving Adapters

기본 또는 주도하는 어댑터는 포트를 감싸고 이를 사용하여 애플리케이션 코어에 수행할 작업을 지시합니다. 그것들은 전달 메커니즘에서 오는 것이 무엇이든 간에 애플리케이션 코어의 메서드 호출로 변환합니다.

030-explicit-architecture-svg.png

다시 말해, 주도하는 어댑터는 컨트롤러 또는 콘솔 명령에 필요한 인터페이스(포트)를 구현한 클래스인 일부의 객체들로, 함께 생성자에 주입되는 컨트롤러 또는 콘솔 명령입니다.

보다 구체적인 예로, 포트는 컨트롤러에 필요한 서비스 인터페이스 또는 리포지토리 인터페이스일 수 있습니다. 그런 다음 서비스, 리포지토리 또는 쿼리의 구체적인 구현체가 컨트롤러에 주입되어 사용됩니다.

다른 것들로는, 포트는 커맨드 버스 Command Bus 나 쿼리 버스 Query Bus 인터페이스일 수 있습니다. 이 경우, 커맨드 또는 쿼리 버스의 구체적인 구현체가 컨트롤러에 주입되고, 커맨드 또는 쿼리를 구성하여 관련 버스에 전달합니다.

보조 또는 주도되는 어댑터 Secondary or Driven Adapters

포트를 감싼 주도하는 어댑터와 달리, 주도되는 어댑터는 인터페이스인 포트를 구현한 다음, 포트가 필요한 곳(type-hinted)이면 어디든지 애플리케이션 코어에 주입됩니다.

040-explicit-architecture-svg.png

예를 들어, 데이터를 유지해야 하는 기본 애플리케이션이 있다고 가정해 봅시다. 그리고 데이터 배열을 저장하는 메서드와 ID로 테이블의 한 줄을 삭제하는 메서드를 가지는 요구사항을 충족한 영속성 인터페이스를 만듭니다. 그 이후, 애플리케이션이 데이터를 저장하거나 삭제해야 하는 곳이라면, 앞서 정의한 영속성 인터페이스를 구현하는 객체가 생성자에 필요합니다.

이제 해당 인터페이스를 구현할 MySQL 전용 어댑터를 만듭니다. 이 어댑터는 테이블에서 배열을 저장하고 한 줄을 삭제하는 메서드를 가질 것이고, 영속성 인터페이스가 필요한 곳 어디든지 주입할 것입니다.

가령 PostgreSQL이나 MongoDB로 데이터베이스 벤더를 변경하기로 한 경우, 영속성 인터페이스를 구현하고 PostgreSQL 전용인 새로운 어댑터를 만든 후 이전 어댑터 대신 새 어댑터를 주입하기만 하면 됩니다.

제어 반전 Inversion of control

이 패턴에 대해 주목해야 할 점은 어댑터가 특정 도구와 특정 포트(인터페이스를 구현한)에 의존한다는 것입니다. 그러나 우리의 비즈니스 로직은 그 요구 사항에 맞게 설계된 포트(인터페이스)에만 의존하므로 특정 어댑터나 도구에 의존하지 않습니다.

050-explicit-architecture-svg.png

이것은 의존성 방향이 중앙을 향한다는 것을 의미하며, 아키텍쳐 수준에서 제어 원칙의 반전입니다. (혹은 역전이라고도 함)

다시 한 번 말하지만, 단순히 도구 API를 모방하는 것이 아니라 애플리케이션 코어의 요구 사항에 맞게 포트를 만드는 것이 가장 중요합니다.

출처

DDD, Hexagonal, Onion, Clean, CQRS, … How I put it all together