Specification Pattern

꽤 괜찮은 글이 있어 공유합니다.

Avoid Proliferating DbContext or IQueryable in .NET Apps | Blog (ardalis.com)

요지

  1. DbContex, DbSet, IQueryable 을 데이터 레이어 외부로 노출시키지 마라.
  2. Specification 객체로 쿼리를 한정하라.

DbContex, DbSet, IQueryable 을 외부로 노출시키지 마라.

대표적인 이유는 IQueryable 이 IEnumerable 을 파생하고 있어, 사용 코드에서 IEnumerable로 취급할 수 있기 때문입니다.

foreach 돌릴 때마다 쿼리가 전송되기 하고, 사용자 코드 단에서 IEnumerable 의 확장 메서드를 정의했을 경우, IQueryable 을 쿼리로 변환하는 쿼리 컨버터가 이를 해석할 수 없기 때문에 예외가 발생합니다.
이런 경우, 컴파일 타임에 문제가 드러나지도 않습니다.

Specification 객체로 쿼리를 한정하라.

아래 코드처럼 위험한 코드도 없죠.

var allUsers = context.Users.ToList();

이러한 성능 위험을 초래하는 코드 뿐만 아니라, 코드가 제한 없이 데이터에 접근하는 것 자체가 매우 위험하다고 생각해왔습니다. 이는 웹 API 같은 도메인 외부 노출 인터페이스는 당연하고, 도메인 내부의 코드도 마찬가지입니다.

이를 위해, 데이터 서비스가 DbContext 를 감싸고, 필터링된 인터페이스만 노출하는 구조를 채택할 수 있습니다.

sealed class UserService
{
   public const int MaxUserRetrieveCount = 50;
   private IQueryable<User> _users;
   private int _retrieved;
   ...
   public List<User> Next(int count) 
   {
      if (count < 1)
         return new();

      if (MaxUserRetrieveCount < count)
         count = MaxUserRetrieveCount;

      var skip = _retrieved;
      _retrieved += count;
      return _users.Skip(skip).Take(count).ToList();
   }
   ...
}

저는 이 방법을 주로 사용해 왔는데 가장 큰 문제는 유지 보수입니다.

필터링 케이스가 변경/추가될 때마다 UserService 의 코드가 변경되어야 해서 그렇습니다.
특히 여러 곳에서 호출되고 있는 케이스를 수정하려면 수정해야 할 코드의 양이 쉽게 많아집니다.

Specification 패턴은 단일 필터링 케이스를 캡슐화하기 때문에 유지보수 측면에서 매우 뛰어난 것 같습니다.

6개의 좋아요

직접 쿼리를 제한하고 Interface로 접근한다는것이 원론적으로 맞긴 하나 ?
사실 실무에서 그러기가 만만치 않습니다.
제한할 쿼리를 만든다는것은 이미 업무 로직을 거의 끝냈다는 애기일수도 있고
팀단위로 개발할때 작은 변경에도 담당자가 수정해줘야 할수도 있고
그리고 디버깅시 한단계 더 거쳐야 한다는것일수도 있고
뭐 단점만 나열하면 이런데 맞는 방향은 맞으나 귀찮다라는 단점같네요

2개의 좋아요

사실 말씀하신 것들 모두 인터페이스를 사용할 경우 해결되는 문제이기도 합니다. OOP에서 주장하는게 그거죠.

다만 문제는 모든 팀원들의 OOP 수준이 같지 않다는 점 일 것 같습니다.

인터페이스의 개념이나 용도를 잘 모르니까 그저 인터페이스를 클래스 말고 또 뭔가 만들어줘야하는 귀찮은 것.

이라고 생각하기 때문일 것 같네요. 제가 그럤던 것처럼…

3개의 좋아요

아 물론 여기서 언급하시는 인터페이스는 코딩의 인터페이스는 아니지만 결국 인터페이스를 통해 접근만 하면 사용하는 측에서는 인터페이스에만 접근하고 실질적인 구현은닉을 함으로써 개발자가 코드에 집중한다는 맥락에서 말씀드렸습니다.

오해는 없으시기를…!

2개의 좋아요

어떤 레이어를 별도로 업무 분장했으면, 그 레이어에 관한 코드는 그 담당자에게 요청하는 것이 당연한 것 아닌지요?

2개의 좋아요

이 패턴은 인터페이스라는 메시지를 효율적으로 확장하는 방법을 제시하는 것으로 이해했습니다.

메시지 갯수를 확장(메서드 종류를 늘림)할 것보다는, 메시지는 단일화하고, 매개 변수를 통해 행위를 확장하는 것이죠.

4개의 좋아요

네네 제가 코드의 interface를 예시를 들어서 오히려 제가 말하려 했던 의도가 헷갈리실 것 같습니다.

인터페이스라는 것의 본질을 말하고 싶었습니다.

저에게 인터페이스는 콘센트 같은 것이라서 사람이 전기를 사용하고 싶을 때 콘센트 뒷편에서 어떻게 작동하는지는 몰라도 되고 그저 220V 전원 선을 꽂기만 하면 된다 라는 명확한 행동만 있게 해줄 수 있는 고마운 개념입니다.

제가 아는 선에선 모든 인터페이스라 불리는 것들이 다 이런 개념을 갖고 있는 것 같습니다.

3개의 좋아요