팁 (조언)
이 콘텐츠는 .NET Docs 또는 오프라인으로 읽을 수 있는 무료 다운로드 가능한 PDF로 제공되는 eBook인 'ASP.NET Core와 Azure로 현대 웹 애플리케이션 설계하기'에서 발췌한 내용입니다.
"빌더가 프로그래머가 프로그램을 작성하는 방식으로 건물을 지은다면, 그 다음에 나온 첫 번째 딱따구리는 문명을 파괴할 것입니다."
- 제럴드 와인버그
유지 관리 기능을 염두에 두고 소프트웨어 솔루션을 설계하고 설계해야 합니다. 이 섹션에 설명된 원칙은 깨끗하고 유지 관리 가능한 애플리케이션을 만드는 아키텍처 결정을 안내하는 데 도움이 될 수 있습니다. 일반적으로 이러한 원칙은 애플리케이션의 다른 부분과 긴밀하게 결합되지 않고 명시적 인터페이스 또는 메시징 시스템을 통해 통신하는 개별 구성 요소에서 애플리케이션을 빌드하는 방법을 안내합니다.
일반적인 디자인 원칙
문제의 분리
개발할 때의 지침 원칙은 우려의 분리입니다. 이 원칙은 소프트웨어가 수행하는 작업의 종류에 따라 분리되어야 한다고 주장합니다. 예를 들어 사용자에게 표시할 주목할 만한 항목을 식별하는 논리와 이러한 항목을 더 눈에 띄게 만드는 특정 방식으로 서식을 지정하는 애플리케이션을 고려합니다. 항목을 형식화할지 선택하는 동작은 항목을 실제로 형식화하는 동작과 별도로 유지해야 합니다. 이 두 동작은 그저 우연히만 서로 관련된 별개의 사안이기 때문입니다.
아키텍처적으로 애플리케이션은 핵심 비즈니스 동작과 인프라 및 사용자 인터페이스 논리를 분리하여 이 원칙을 따르도록 논리적으로 빌드할 수 있습니다. 이상적으로 비즈니스 규칙과 논리는 애플리케이션의 다른 프로젝트에 의존해서는 안 되는 별도의 프로젝트에 상주해야 합니다. 이러한 분리는 비즈니스 모델을 테스트하기 쉽고 하위 수준 구현 세부 정보에 긴밀하게 결합하지 않고도 발전할 수 있도록 합니다(인프라 문제가 비즈니스 계층에 정의된 추상화에 따라 달라지는 경우에도 도움이 됩니다). 문제를 분리하는 것은 애플리케이션 아키텍처에서 레이어를 사용하는 데 중요한 고려 사항입니다.
캡슐화
애플리케이션의 다른 부분은 캡슐화를 사용하여 애플리케이션의 다른 부분에서 격리해야 합니다. 외부 계약을 위반하지 않는 한 애플리케이션 구성 요소 및 계층은 공동 작업자를 중단하지 않고 내부 구현을 조정할 수 있어야 합니다. 캡슐화를 적절하게 사용하면 동일한 인터페이스가 유지되는 한 개체와 패키지를 대체 구현으로 바꿀 수 있으므로 애플리케이션 디자인에서 느슨한 결합 및 모듈화를 달성할 수 있습니다.
클래스에서 캡슐화는 클래스의 내부 상태에 대한 외부 액세스를 제한하여 수행됩니다. 외부 행위자가 개체의 상태를 조작하려는 경우 개체의 프라이빗 상태에 직접 액세스하지 않고 잘 정의된 함수(또는 속성 setter)를 통해 이 작업을 수행해야 합니다. 마찬가지로 애플리케이션 구성 요소 및 애플리케이션 자체는 상태를 직접 수정할 수 있도록 허용하지 않고 협력자가 사용할 수 있도록 잘 정의된 인터페이스를 노출해야 합니다. 이 접근방식을 사용하면 공개 계약이 유지되는 한 협업이 중단될 걱정 없이 애플리케이션의 내부 디자인을 시간이 지나며 진화시킬 수 있습니다.
변경 가능한 전역 상태는 캡슐화와 반대입니다. 한 함수의 변경 가능한 전역 상태에서 가져온 값은 다른 함수에서 동일한 값을 갖는 데 의존할 수 없습니다(또는 동일한 함수에서 더 이상). 변경 가능한 전역 상태에 대한 우려를 이해하는 것은 C#과 같은 프로그래밍 언어가 문에서 메서드, 클래스에 이르기까지 모든 곳에서 사용되는 다양한 범위 지정 규칙을 지원하는 이유 중 하나입니다. 주목할 점은 애플리케이션 내 및 애플리케이션 간의 통합을 위해 중앙 데이터베이스에 의존하는 데이터 중심 아키텍처가, 그 자체로서 데이터베이스가 나타내는 변경 가능하고 전역적인 상태에 의존하겠다는 선택을 하고 있다는 것입니다. 도메인 기반 디자인 및 클린 아키텍처의 주요 고려 사항은 데이터에 대한 액세스를 캡슐화하는 방법과 지속성 형식에 대한 직접 액세스에 의해 애플리케이션 상태가 유효하지 않게 되지 않도록 하는 방법입니다.
종속성 반전
애플리케이션 내의 종속성 방향은 구현 세부 정보가 아니라 추상화 방향이어야 합니다. 대부분의 애플리케이션은 컴파일 시 종속성 흐름이 런타임 실행 방향으로 향하도록 작성되어 직접적인 종속성 그래프를 생성합니다. 즉, 클래스 A가 클래스 B의 메서드를 호출하고 클래스 B가 C 클래스의 메서드를 호출하는 경우 컴파일 타임 클래스 A는 클래스 B에 따라 달라지고 클래스 B는 그림 4-1에 표시된 것처럼 클래스 C에 따라 달라집니다.
그림 4-1. 직접 종속성 그래프입니다.
종속성 반전 원칙을 적용하면 A가 B가 구현하는 추상화에서 메서드를 호출할 수 있으므로 A가 런타임에 B를 호출할 수 있지만 B는 컴파일 시간에 A로 제어되는 인터페이스에 종속됩니다(따라서 일반적인 컴파일 시간 종속성을 반전 ). 런타임에 프로그램 실행 흐름은 변경되지 않지만 인터페이스가 도입되면 이러한 인터페이스의 다양한 구현을 쉽게 연결할 수 있습니다.
그림 4-2. 반전된 종속성 그래프입니다.
종속성 반전은 느슨하게 결합된 애플리케이션을 구축하는 데 중요한 요소입니다. 구현 세부사항은 하위 수준 대신 상위 수준 추상화에 의존하고 구현되도록 작성될 수 있기 때문입니다. 결과적으로 결과 애플리케이션은 테스트 가능하고 모듈식이며 유지 관리가 가능합니다. 종속성 반전 원칙에 따라 종속성 주입을 구현할 수 있습니다.
명시적 종속성
메서드 및 클래스는 올바르게 작동하기 위해 필요한 모든 공동 작업 개체를 명시적으로 요구해야 합니다. 이를 명시적 종속성 원칙이라고 합니다. 클래스 생성자는 클래스가 유효한 상태에 있고 제대로 작동하기 위해 필요한 항목을 식별할 수 있는 기회를 제공합니다. 생성 및 호출할 수 있지만 특정 전역 또는 인프라 구성 요소가 있는 경우에만 제대로 작동하는 클래스를 정의하는 경우 이러한 클래스는 클라이언트에 부정직 하게 사용됩니다. 생성자 계약은 클라이언트에 지정된 항목만 필요하지만(클래스가 매개 변수가 없는 생성자를 사용하는 경우 아무 것도 필요하지 않음) 런타임에 개체에 실제로 다른 항목이 필요했음을 알 수 있습니다.
명시적 종속성 원칙에 따라 클래스와 메서드는 작동하기 위해 필요한 항목에 대해 클라이언트에 정직하게 설명합니다. 원칙에 따라 코드가 더욱 자체 문서화되고 코딩 계약이 더 사용자에게 친숙해집니다. 사용자가 메서드 또는 생성자 매개 변수의 형태로 필요한 것을 제공하는 한, 작업 중인 개체가 런타임에 올바르게 작동한다는 것을 신뢰하게 됩니다.
단일 책임
단일 책임 원칙은 개체 지향 디자인에 적용되지만 문제를 분리하는 것과 유사한 아키텍처 원칙으로 간주될 수도 있습니다. 개체에는 하나의 책임만 있어야 하며 변경해야 하는 이유가 하나만 있어야 한다고 명시되어 있습니다. 특히 개체가 변경되어야 하는 유일한 상황은 하나의 책임을 수행하는 방식을 업데이트해야 하는 경우입니다. 이 원칙을 따르면 기존 클래스에 추가 책임을 추가하는 대신 다양한 종류의 새 동작을 새 클래스로 구현할 수 있으므로 더 느슨하게 결합된 모듈식 시스템을 생성하는 데 도움이 됩니다. 새 클래스에 따라 달라지는 코드가 없으므로 새 클래스를 추가하는 것이 기존 클래스를 변경하는 것보다 항상 안전합니다.
모놀리식 애플리케이션에서는 애플리케이션의 계층에 높은 수준의 단일 책임 원칙을 적용할 수 있습니다. 프레젠테이션 책임은 UI 프로젝트에 남아 있어야 하지만 데이터 액세스 책임은 인프라 프로젝트 내에서 유지되어야 합니다. 비즈니스 논리는 쉽게 테스트할 수 있고 다른 책임과 독립적으로 발전할 수 있는 애플리케이션 핵심 프로젝트에 보관해야 합니다.
이 원칙이 애플리케이션 아키텍처에 적용되고 논리 엔드포인트로 이동하면 마이크로 서비스가 발생합니다. 지정된 마이크로 서비스에는 단일 책임이 있어야 합니다. 시스템의 동작을 확장해야 하는 경우 일반적으로 기존 서비스에 책임을 추가하는 대신 추가 마이크로 서비스를 추가하여 수행하는 것이 좋습니다.
DRY(반복 금지)
이 방법은 오류의 빈번한 원인이므로 애플리케이션은 여러 위치에서 특정 개념과 관련된 동작을 지정하지 않아야 합니다. 요구 사항이 변경되면 이 동작을 변경해야 합니다. 하나 이상의 동작 인스턴스가 업데이트되지 않고 시스템이 일관되지 않게 동작할 수 있습니다.
논리를 복제하는 대신 프로그래밍 구문에 캡슐화합니다. 이 동작에 대한 단일 기관을 구성하고 이 동작이 필요한 애플리케이션의 다른 부분에 새 구문을 사용하도록 합니다.
비고
우연히 반복적인 행동들을 함께 묶지 마십시오. 예를 들어 두 상수의 값이 같다고 해서 개념적으로 서로 다른 항목을 참조하는 경우 상수가 하나만 있어야 한다는 의미는 아닙니다. 중복은 잘못된 추상화에 결합하는 것보다는 항상 선호됩니다.
지속성 무지
PI(지속성 무지)는 지속되어야 하지만 코드가 지속성 기술의 선택에 영향을 받지 않는 형식을 나타냅니다. .NET의 이러한 형식은 특정 기본 클래스에서 상속하거나 특정 인터페이스를 구현할 필요가 없으므로 POKO(Plain Old CLR Objects)라고도 합니다. 지속성 무시 개념은 비즈니스 모델을 다양한 방식으로 저장할 수 있어 애플리케이션에 추가적인 유연성을 제공하는 데 유용합니다. 지속성 선택은 시간이 지남에 따라 한 데이터베이스 기술에서 다른 데이터베이스 기술로 변경되거나 애플리케이션이 시작한 항목(예: 관계형 데이터베이스 외에 Redis Cache 또는 Azure Cosmos DB 사용) 외에도 추가적인 형태의 지속성이 필요할 수 있습니다.
이 원칙을 위반하는 몇 가지 예는 다음과 같습니다.
필수 기본 클래스입니다.
필수 인터페이스 구현입니다.
자신을 저장하는 클래스(예: 활성 레코드 패턴).
필수 매개 변수 없는 생성자입니다.
가상 키워드가 필요한 속성입니다.
지속성 관련 필수 특성입니다.
클래스에 위의 기능 또는 동작이 있어야 한다는 요구 사항은 지속할 형식과 지속성 기술 선택 간의 결합을 추가하여 향후 새로운 데이터 액세스 전략을 채택하기가 더 어려워집니다.
제한된 컨텍스트
바인딩된 컨텍스트는 Domain-Driven 디자인의 중심 패턴입니다. 이를 별도의 개념적 모듈로 분할하여 대규모 애플리케이션 또는 조직의 복잡성을 해결하는 방법을 제공합니다. 그런 다음 각 개념 모듈은 다른 컨텍스트(따라서 경계)와 분리되어 독립적으로 발전할 수 있는 컨텍스트를 나타냅니다. 경계가 지정된 각 컨텍스트는 개념에 대한 고유한 이름을 자유롭게 선택할 수 있어야 하며 자체 지속성 저장소에 대한 단독 액세스 권한이 있어야 합니다.
최소한 개별 웹 애플리케이션은 데이터베이스를 다른 애플리케이션과 공유하지 않고 비즈니스 모델에 대한 자체 지속성 저장소를 사용하여 경계가 있는 컨텍스트가 되기 위해 노력해야 합니다. 제한된 컨텍스트 간의 통신은 공유 데이터베이스가 아닌 프로그래밍 방식 인터페이스를 통해 수행되며, 이를 통해 비즈니스 논리 및 이벤트가 발생하는 변경 내용에 대응하여 수행될 수 있습니다. 바인딩된 컨텍스트는 마이크로 서비스에 밀접하게 매핑되며, 이는 자체의 개별 바인딩된 컨텍스트로 구현되는 것이 이상적입니다.
추가 리소스
.NET