본문으로 건너뛰기

고객 관리

이커머스 상품 필터링은 Specification 패턴의 대표적인 사용 사례입니다. 하지만 이 패턴은 Product 외의 다른 Aggregate에도 동일하게 적용됩니다. 이 장에서는 고객 관리 도메인에서 Specification을 활용하여, 패턴의 범용성을 확인합니다.

Expression과 non-Expression Specification을 상황에 맞게 선택하여 사용합니다.

  1. 다른 집합체에 Specification 적용: Product가 아닌 Customer 도메인에서의 활용
  2. Expression vs non-Expression 선택 기준: 각 방식의 적합한 사용 시나리오 이해
  3. 혼합 조합: ExpressionSpecification과 Specification을 &, | 연산자로 조합
  4. 대소문자 무시 검색: Expression Tree에서의 문자열 비교 전략

Expression vs non-Expression Specification

섹션 제목: “Expression vs non-Expression Specification”

모든 조건이 Expression Tree로 표현될 필요는 없습니다. 단순한 속성 확인은 IsSatisfiedBy만 오버라이드하는 것이 더 간결합니다.

// ExpressionSpecification: EF Core SQL 번역이 필요한 경우
public sealed class CustomerEmailSpec : ExpressionSpecification<Customer>
{
public override Expression<Func<Customer, bool>> ToExpression()
{
string emailStr = Email;
return customer => (string)customer.Email == emailStr;
}
}
// non-Expression Specification: 인메모리 검증만 필요한 경우
public sealed class CustomerActiveSpec : Specification<Customer>
{
public override bool IsSatisfiedBy(Customer entity) => entity.IsActive;
}

Expression에서 대소문자 무시 검색

섹션 제목: “Expression에서 대소문자 무시 검색”

Expression Tree 내부에서는 string.Contains(string, StringComparison)을 사용할 수 없습니다. 대신 .ToLower().Contains() 패턴을 사용합니다.

public override Expression<Func<Customer, bool>> ToExpression()
{
string searchLower = ((string)SearchName).ToLower();
return customer => ((string)customer.Name).ToLower().Contains(searchLower);
}

ExpressionSpecification과 non-Expression Specification은 기반 클래스인 Specification<T>&, |, ! 연산자를 통해 자유롭게 조합할 수 있습니다.

// CustomerActiveSpec(non-Expression) & CustomerNameContainsSpec(Expression)
var spec = new CustomerActiveSpec() & new CustomerNameContainsSpec(new CustomerName(""));
CustomerManagement/
├── Domain/
│ ├── ValueObjects/
│ │ ├── CustomerId.cs
│ │ ├── CustomerName.cs
│ │ └── Email.cs
│ ├── Customer.cs
│ ├── ICustomerRepository.cs
│ └── Specifications/
│ ├── CustomerEmailSpec.cs # ExpressionSpecification
│ ├── CustomerNameContainsSpec.cs # ExpressionSpecification
│ └── CustomerActiveSpec.cs # non-Expression Specification
├── Infrastructure/
│ └── InMemoryCustomerRepository.cs
├── SampleCustomers.cs
└── Program.cs
Specification기반 클래스설명
CustomerEmailSpecExpressionSpecification이메일 정확 일치
CustomerNameContainsSpecExpressionSpecification이름 부분 일치 (대소문자 무시)
CustomerActiveSpecSpecification활성 고객 확인
구분내용
도메인고객 관리 (Customer)
Value ObjectsCustomerId, CustomerName, Email
ExpressionSpecificationCustomerEmailSpec, CustomerNameContainsSpec
non-Expression SpecificationCustomerActiveSpec
핵심 패턴Expression/non-Expression 혼합 조합

Expression vs non-Expression 선택 기준

섹션 제목: “Expression vs non-Expression 선택 기준”
기준ExpressionSpecificationSpecification
EF Core SQL 번역지원미지원
구현 복잡도높음 (Expression Tree)낮음 (직접 로직)
사용 시나리오DB 쿼리 필터링인메모리 검증
Value Object 처리implicit 변환 필요직접 접근 가능

Q1: 언제 ExpressionSpecification 대신 일반 Specification을 사용하나요?

섹션 제목: “Q1: 언제 ExpressionSpecification 대신 일반 Specification을 사용하나요?”

A: EF Core 등 ORM을 통한 SQL 번역이 필요하지 않고, 인메모리에서만 검증하는 단순한 조건이라면 일반 Specification이 더 간결합니다. CustomerActiveSpec처럼 단순 속성 확인이 대표적인 예입니다.

Q2: Expression과 non-Expression Specification을 조합할 수 있는 이유는?

섹션 제목: “Q2: Expression과 non-Expression Specification을 조합할 수 있는 이유는?”

A: 두 가지 모두 Specification<T> 기반 클래스를 상속하므로, &, |, ! 연산자를 통해 자유롭게 조합할 수 있습니다. 조합 결과는 인메모리에서 IsSatisfiedBy를 통해 평가됩니다.

Q3: Expression Tree에서 StringComparison을 사용할 수 없는 이유는?

섹션 제목: “Q3: Expression Tree에서 StringComparison을 사용할 수 없는 이유는?”

A: Expression Tree는 SQL 등으로 번역되어야 하므로, .NET 전용 API인 StringComparison은 번역할 수 없습니다. 대신 .ToLower().Contains() 패턴을 사용하면 SQL의 LOWER() 함수로 자연스럽게 번역됩니다.


이것으로 Specification 패턴 튜토리얼의 본문이 모두 끝났습니다. 기초부터 실전까지 — 조건 캡슐화, Expression Tree, Repository 통합, 그리고 다양한 도메인 적용까지 학습했습니다. 부록에서는 대안 패턴과의 비교, 안티패턴, 용어집, 참고 자료를 제공합니다.

부록 A: Specification vs 대안 비교