AI VIDEO BRIEFING

명세 패턴(Specification Pattern)으로 EF Core 쿼리 정리하기: 장단점과 실용적 대안

같은 비즈니스 규칙을 서비스 코드와 EF Core 쿼리에 두 번 작성하는 문제를, 명세 패턴으로 한 곳에 캡슐화하는 방법과 그 한계, 더 가벼운 대안을 정리했습니다.

명세 패턴으로 EF Core 쿼리와 비즈니스 규칙 중복 없애기 영상 대표 이미지

핵심 메시지

  • 같은 자격 검증 규칙을 인메모리 서비스와 EF Core 쿼리에 중복으로 작성하면 유지보수가 어려워진다.
  • 명세 패턴은 규칙을 Expression<Func<T,bool>>로 캡슐화해, 인메모리 평가와 데이터베이스 쿼리 양쪽에 같은 정의를 재사용한다.
  • 개별 명세를 And·Or·Not으로 조합하면 런타임에 동적으로 조건을 구성할 수 있다.
  • 명세 버전은 쿼리가 파라미터화되어 더 안전하고, DB가 쿼리 플랜을 재사용해 성능 이점도 생긴다.
  • 규칙이 고정적이라면 직접 구현의 이득은 작다. 동적 규칙이 필요하면 Ardalis.Specification 라이브러리를, 아니면 IQueryable 확장 메서드를 권장한다.

쉽게 이해하기

발표자는 전형적인 애플리케이션에서 흔히 생기는 문제로 시작합니다. 고객의 프로모션 자격을 확인하는 비즈니스 규칙이 if 문 몇 개를 연결한 형태로 서비스에 들어 있는데, 같은 규칙을 데이터베이스에서 자격 있는 고객을 조회할 때 LINQ 쿼리로 다시 작성하게 된다는 것입니다. 동일한 논리가 두 곳에 흩어지면서 중복이 발생합니다.

객체지향에서 이 문제를 다루는 대표적 방법이 명세 패턴입니다. 추상·제네릭 기반 클래스를 만들고, 비즈니스 규칙을 나타내는 ToExpression 메서드가 Expression<Func<T,bool>>을 반환하게 합니다. 이 Expression 타입은 람다를 캡슐화해 메모리에서 컴파일해 실행할 수도 있고, 그대로 EF Core에 넘겨 SQL로 변환할 수도 있어 한 정의로 두 문제를 동시에 해결합니다. IsSatisfiedBy 도우미는 식을 컴파일해 특정 객체에 대해 규칙 충족 여부를 즉시 평가합니다.

조건들을 하나씩 별도 명세로 분리하면 ActiveCustomer, RegisteredBefore, MinimumSpend, AllowedCountry처럼 각각을 만들 수 있고, 생성 시점에 인자를 받아 매개변수화합니다. 예컨대 30일 기준을 하드코딩하지 않고 컷오프 날짜를 전달합니다. 다만 각 명세 인스턴스는 생성 시점의 인자로 클로저를 만들기 때문에, 하나의 인스턴스를 여러 쿼리에 재사용할 때는 이 점을 유의해야 합니다.

개별 명세를 합치기 위해 And·Or·Not 명세를 도입합니다. Expression의 AndAlso·OrElse·Not과 방문자(visitor)로 파라미터를 일치시켜 좌·우 명세를 논리 연산으로 결합하고, 기반 클래스에 And/Or/Not 도우미 메서드를 두어 체이닝으로 조합합니다. 결과적으로 서비스의 자격 검사는 IsSatisfiedBy 호출로, EF Core의 where 절은 ToExpression 호출로 동일한 명세를 재사용합니다.

발표자는 정직하게 비용도 짚습니다. 규칙이 이렇게 고정적이라면 명세 타입, 조합용 타입, 개별 명세까지 추가 코드의 보상이 크지 않습니다. 런타임에 동적으로 규칙을 만들어야 할 때 비로소 명세 패턴이 값어치를 하며, 그때도 직접 구현보다 Steve Smith(Ardalis)의 Specification NuGet 라이브러리를 권합니다. 그리고 더 실용적인 대안으로, 비즈니스 규칙을 IQueryable<T> 확장 메서드(EligibleForPromotion)로 캡슐화하면 클래스를 잔뜩 만들지 않고도 같은 효과를 얻고, 반환을 Expression으로 두면 인메모리 객체에도 재사용할 수 있다고 제안합니다.

주요 인사이트

  • Expression<Func<T,bool>>를 쓰는 핵심 이유는 같은 규칙 정의를 메모리에서는 컴파일해 실행하고 EF Core에서는 SQL로 번역하도록, 한 코드로 두 실행 경로를 만족시키기 위해서다.
  • 명세 버전이 만들어내는 SQL은 등록일·총지출·허용 국가가 파라미터로 들어가 더 안전하며, 데이터베이스가 동일 플랜을 재사용할 수 있어 성능에 유리하다.
  • 암시적 변환 연산자(implicit operator)를 정의해두면 ToExpression 호출을 생략하고 명세를 그대로 where에 넘길 수 있어 호출부가 깔끔해진다.
  • 패턴 도입 자체가 목적이 되어선 안 된다. 규칙이 고정적이면 확장 메서드 같은 가벼운 방법이 낫고, 동적 구성이 필요할 때만 검증된 라이브러리로 패턴을 적용하는 편이 합리적이다.

자주 묻는 질문

명세 패턴은 어떤 문제를 해결하나요?

같은 비즈니스 규칙(예: 프로모션 자격 검사)을 인메모리 서비스의 if 문과 데이터베이스 LINQ 쿼리에 중복 작성하는 문제를 해결합니다. 규칙을 Expression으로 한 번 캡슐화해 양쪽에서 재사용합니다.

명세 패턴을 쓰면 생성되는 SQL이 달라지나요?

네. 영상에서 비교한 결과, 명세 버전은 등록일 컷오프·총지출·허용 국가가 파라미터로 처리됩니다. 이는 더 안전하고, 데이터베이스가 쿼리 플랜을 재사용할 수 있어 성능 개선으로 이어질 수 있습니다.

발표자는 명세 패턴을 직접 구현하라고 권하나요?

아니요. 동적 규칙이 필요하면 직접 구현하기보다 Ardalis의 Specification NuGet 라이브러리를 권합니다. 규칙이 고정적이라면 IQueryable 확장 메서드로 규칙을 캡슐화하는 더 가벼운 방법을 추천합니다.

원문과 출처

이 글은 원본 영상의 자막을 바탕으로 한국어 독자를 위해 요약했습니다. 전체 맥락과 최신 정보는 원문에서 확인하세요.

YouTube 원본 영상 보기 ↗

관련 AI 소식

#EF Core#명세 패턴#.NET#C##소프트웨어 설계