도메인 로직 및 파이프라인을 가진 CQRS 핸들러 (C#) – Derek Comartin의 비디오를 통한 설명
CQRS(Command Query Responsibility Segregation)는 .NET 애플리케이션에서 읽기 및 쓰기 작업 간의 명확한 분리를 유지하는 데 도움이 되는 강력한 디자인 패턴입니다. 이러한 분리를 통해 확장성, 테스트 용이성 및 복잡한 비즈니스 로직에 대한 제어력을 향상시킬 수 있습니다. 하지만 애플리케이션이 커짐에 따라 C#으로 작성된 CQRS 핸들러는 로직이 너무 많아져 유지 관리 및 테스트가 어려워질 수 있습니다.
데릭 코마틴은 " 도메인 로직과 파이프라인을 사용하여 비대해진 CQRS 핸들러 정리하기 "라는 제목의 유익한 영상에서, 로직을 도메인 모델로 옮기고 단계별로 명령 로직을 관리하는 파이프라인을 구축하여 CQRS 명령 핸들러를 정리하는 방법을 자세히 설명합니다. 이 글에서는 데릭의 접근 방식을 심층적으로 살펴보고 C#에서 유지보수성이 뛰어난 CQRS 구현을 구축하는 방법을 알아보겠습니다.
과도하게 부풀려진 핸들러의 문제점
데릭은 많은 웹 애플리케이션에서 흔히 발생하는 시나리오, 즉 C#으로 CQRS 핸들러를 열면 엉망이 된다는 점을 강조하며 이야기를 시작합니다. 데이터 유효성 검사, 권한 부여, 비즈니스 로직, 상태 전환, 이벤트 게시, 로깅 등 모든 것이 복잡하게 얽혀 있습니다.
그는 배송을 지시하는 명령 객체를 사용하여 이를 설명합니다. 담당자는 다음을 담당합니다:
데이터 저장소에 접근하여 배송을 로드합니다.
배송 준비 상태인지 확인합니다.
상태 업데이트(즉, 데이터 업데이트).
변경 사항을 데이터 액세스 계층에 저장합니다.
이메일 보내기.
- 도메인 이벤트 게시.
이 모든 일이 한 곳에서 일어납니다. 이는 CQRS 패턴의 취지에 위배됩니다. CQRS 패턴의 목표는 관심사를 분리하고 성능 및 유지보수성을 향상시키는 것이기 때문입니다.
도메인 로직을 데이터 모델로 이동
데릭의 첫 번째 단계는 유효성 검사 및 상태 전환 로직을 데이터 모델로 옮기는 것입니다. 그는 Shipment 클래스 내부에 Dispatch() 메서드를 생성합니다. 이제 도메인 로직이 여기에 위치하게 됩니다.
핸들러에서 배송 상태를 수동으로 확인하는 대신, 해당 로직을 이 메서드에 캡슐화하여 데이터 무결성을 보장하고 배송이 발생하는 모든 곳에서 일관된 동작을 수행합니다. 이는 CQRS 기반 애플리케이션에서 클린 아키텍처를 구현하는 데 핵심적인 요소입니다.
예를 들어, shipment.Dispatch()를 호출하는 모든 곳에서는 모든 유효성 검사 및 상태 전환이 자동으로 수행됩니다. 이는 CQRS 설계 패턴과 일치하며, 핸들러와 도메인 로직 간의 명확한 분리를 유지하는 데 도움이 됩니다.
논리를 중앙집중화하는 것의 가치
데릭은 이러한 변화가 불필요한 추상화를 추가하는 것이 아니라고 지적합니다. 그보다는 애플리케이션 코드의 여러 부분에서 사용되는 로직을 중앙 집중화하는 것에 관한 것입니다. 여러 명령 처리기가 배송을 처리해야 하는 경우, 이러한 사용자 지정 로직은 도메인 모델 내의 한 곳에 있어야 합니다.
이렇게 하면 데이터 모델이 더욱 견고해지고 CQRS 핸들러의 C# 구현이 더 간단하고 유지 관리하기 쉬워집니다.
파이프라인 패턴 소개
명령 처리기를 더욱 깔끔하게 정리하기 위해 데릭은 파이프라인 패턴을 도입합니다. 이 구조는 명령을 일련의 작고 단일 목적의 단계로 처리하며, 각 단계는 컨텍스트 객체를 받아 다음 단계를 호출합니다.
이는 개념적으로 ASP.NET Core 미들웨어와 유사하며, 각 단계는 흐름의 특정 부분에 초점을 맞춥니다.
배송물 검색 (즉, 데이터 읽기)
디스패칭(쓰기 작업 실행)
출판 행사
- 데이터 저장소에 저장
이러한 단계에서는 파이프라인을 통해 흐르는 공유 명령 객체를 사용합니다. 이를 통해 명령과 쿼리 책임 분리를 깔끔하고 모듈식으로 구현할 수 있습니다.
파이프라인의 샘플 구현
데릭은 샘플 구현에서 파이프라인을 다음과 같은 단계로 구성했습니다.
배송물 적재 – 저장소를 사용하여 데이터 액세스 계층에서 데이터를 가져옵니다.
배송 발송 – Dispatch() 메서드를 호출하여 도메인 로직을 적용합니다.
도메인 이벤트 추가 - 컨텍스트에 "ShipmentDispatched" 이벤트를 연결합니다.
이벤트 게시 – 외부 시스템에 알림을 보내기 위한 이벤트 전송.
- 변경 사항 저장 – 데이터 저장소에 대한 업데이트를 저장합니다.
각 단계는 별개의 명령 논리 조각을 나타내어 데이터 유효성 검사를 강화하고 책임 소재를 분리합니다.
데릭은 또한 이메일 알림이 이제 도메인 이벤트에 반응하여 별도로 처리된다고 언급했습니다. 이는 이벤트 소싱 원칙과 일치하며 궁극적인 일관성을 촉진합니다.
테스트 및 유지보수성 향상 효과
이 패턴의 가장 큰 장점 중 하나는 테스트 용이성입니다. 대규모 명령 처리기를 사용하는 경우 저장소, 메일 서비스, 로거 등 여러 종속성이 있을 수 있습니다. 하지만 핸들러를 파이프라인 단계로 나누면 각 단계는 몇 가지 종속성만 필요로 합니다.
이 모듈식 접근 방식을 통해 필요에 따라 가짜 객체나 모의 객체를 사용하여 의존성 주입으로 개별 단계를 쉽게 테스트할 수 있습니다. 예를 들어 Dispatch()를 호출하는 단계를 테스트하는 경우 이메일 서비스나 이벤트 게시자를 모킹할 필요가 없습니다.
이러한 관심사 분리는 책임 분리 CQRS 패턴을 따르므로 읽기 및 쓰기 모델을 더 깔끔하고 집중적으로 만들 수 있습니다.
구성 가능성과 재사용성
파이프라인 방식의 또 다른 장점은 구성이 가능하다는 점입니다. 아웃박스 패턴과 같은 것을 사용하면 쓰기 모델이 영구 저장된 후에만 이벤트가 게시되도록 할 수 있습니다. 이러한 수준의 제어는 일관성과 납품 보장이 중요한 CQRS 구현에 필수적입니다.
또한 여러 CQRS 핸들러에서 단계를 공유할 수 있습니다. 예를 들어 일반적인 "변경 사항 저장" 단계 또는 "요청 유효성 검사" 단계를 공유할 수 있습니다.
명령 및 쿼리 처리를 지원하는 MediatR 라이브러리와 같은 도구를 사용하면 IServiceCollection 서비스를 통해 .NET Core 애플리케이션에 종속성 주입으로 이러한 단계를 등록할 수도 있습니다.
이 시스템을 설정하려면 Visual Studio의 패키지 관리자 콘솔을 통해 Install-Package MediatR 명령을 실행할 수 있습니다. 이는 C#에서 CQRS를 구현할 때 흔히 사용하는 단계입니다.
절충점
데릭은 이러한 접근 방식에 따르는 복잡성 증가를 두려워하지 않습니다. 파이프라인은 간접적인 방식을 도입하며, 호출 스택을 보면 마치 미로를 헤매는 듯한 느낌을 받을 수 있습니다.
하지만 복잡한 비즈니스 로직의 경우, 이러한 절충안은 종종 가치가 있습니다. 핸들러에 10개 이상의 종속성과 수백 줄의 로직이 있는 경우, CQRS를 사용하면 개발자가 이러한 흐름을 더 잘 구조화하고 유지 관리할 수 있습니다.
리팩토링 시점에 대한 최종 생각
데릭은 마지막으로 시청자들에게 CQRS 핸들러 C# 구현이 정말로 비대해졌는지 신중하게 고려해 보라고 당부합니다. 모든 시나리오에 파이프라인이 필요한 것은 아닙니다. 그의 목표는 가능성을 보여주는 것이며, 개발자는 자신의 CQRS 구현을 평가하고 이러한 패턴이 도움이 될지 여부를 판단해야 합니다.
그는 개발자들이 코드에서 관심사 분리가 일관성 유지, 코드 모듈화, 읽기 및 쓰기 작업 관리 개선에 도움이 될 수 있는 부분을 살펴보도록 권장합니다. 특히 CQRS 웹 애플리케이션에서 이러한 분리가 효과적입니다.
결론
데릭 코마틴의 비디오 는 도메인 로직 캡슐화와 파이프라인을 사용하여 CQRS 핸들러를 정리하는 실용적인 가이드를 제공합니다. 이러한 접근 방식은 애플리케이션 코드를 개별 모델로 분해하여 코드 비대화 문제를 해결하고 데이터 무결성을 강화하며 유지 관리성을 향상시키는 데 도움이 됩니다.
직원 데이터, 제품 세부 정보 또는 새로운 사용자 명령을 처리하든 관계없이 파이프라인 및 도메인 중심 설계와 함께 CQRS 패턴을 적용하면 코드베이스의 확장성, 테스트 용이성 및 견고성이 향상됩니다.
데이터 전송 객체(DTO)를 사용하고, 모델을 분리하며, 읽기 및 쓰기 로직을 명확하게 분리하면 .NET 애플리케이션의 구조가 더욱 탄탄해지고 시간이 지남에 따라 발전시키기가 더 쉬워집니다.



