.NET 개발을 하면 ORM은 99% Entity Framework를 사용한다. Entity Framework가 제공하는 DbContext는 Unit of Work와 레파지토리 패턴을 위해 만들어졌다고는 하지만, 비즈니스 프로젝트에서 Infrastructure에 대한 의존성이 생기기 때문에 비즈니스 프로젝트에서 DbContext를 사용하는 것은 좋은 방법이 아니다. 그러다 보니, 별도의 레파지로리와 UnitOfWork 인터페이스/클래스를 개발하여 개발을 진행하는데, 이게 보통 귀찮은 작업이 아니다. 각 Entity에 대해 Repository 인터페이스정의 후 이것을 구현해야 하고 일일이 유닛 테스트까지 작성하는 것은 비효율의 끝판왕을 보여주는 프로세스라고 할 수 있다. 예전에 자바를 사용할 때 JPA를 이용한 적이 있는데, 인테페이스만 지정하면 구현 클래스 없어도 바로 사용할 수 있었다는 것에 대한 아이디어에서 착안하여 오픈 소스 프로젝트 하나를 nuget에 올렸다.

 

아이디어는 심플하다. UnitOfWork 인터페이스가 Inject될 때 특정 인터페이스를 상속한 모든 인터페이스를 찾아서, 이를 Intercepter와 Refection을 이용하여 인스턴스를 생성하고 요청이 있을 경우 이 인스턴스를 돌려주는 것이다.

 

아래와 같이 전체 어셈플리 중 내가 정의한 IEpaRepository를 상속한 모든 인터페이스를 찾아서 Castle의 ProxyGenerator를 이용해 생성된 인터페이스를 저장한다.

AppDomain.CurrentDomain.GetAssemblies().SelectMany(a => a.GetTypes())
    .Where(p => p.IsInterface && p.GetInterfaces().Any(i => i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IEpaRepository<,>)))
    .ToList().ForEach(c =>
    {
        var proxy = _proxyGenerator.CreateInterfaceProxyWithoutTarget(c, new EpaRepositoryInterceptor(_dbContext));
        if (!_repositories.ContainsKey(c))
        {
            _repositories.Add(c, proxy);
        }
    });

 

EpaRepositoryInterceptor에서 Refection을 이용해 인스턴스를 만들어 줌.

/// <summary>
/// An interceptor class to create an instance for any repository interface inheriting <see cref="IEpaRepository{TEntity,TKey}"/>
/// </summary>
internal class EpaRepositoryInterceptor : IInterceptor
{
    private readonly IEpaDbContext _dbContext;

    public EpaRepositoryInterceptor(IEpaDbContext dbContext)
    {
        _dbContext = dbContext;
    }

    /// <inheritdoc />
    public void Intercept(IInvocation invocation)
    {
        var args = invocation.Method.DeclaringType?.GenericTypeArguments;
        if (args is not { Length: 2 })
        {
            throw new InvalidOperationException("The interface should have two generic types, Entity and Key.");
        }

        var type = typeof(EpaRepository<,>).MakeGenericType(args[0], args[1]);

        var instance = Activator.CreateInstance(type, _dbContext);
        var method = instance?.GetType().GetMethod(invocation.Method.Name);
        var resultSet = method?.Invoke(instance, invocation.Arguments);

        invocation.ReturnValue = resultSet;
    }
}

 

이렇게 인스턴스를 만들어 놓고 요청이 있을 때마다 이 가상의 인스턴스를 리턴해주면 굳이 IOrderRepository를 정의하고 OrderRepository를 구현할 필요성이 전혀 없어지기 때문에 생산성이 기하급수적으로 늘어날 수 있다.

 

약 3년 전에 비슷한 오픈소스를 만들었을 때는 제약사항이 너무 많았었는데, 이번에 새로 만든 버전은 정말 깔끔하고 심플하고, 기존 소스의 변화가 거의 없다시피하니 대만족.

 

자세한 예제와 WIKI는 nuget에서...

https://www.nuget.org/packages/EntityFrameworkCore.PersistenceApi/

 

EntityFrameworkCore.PersistenceApi 1.0.0

Auto-wired EntityFramework repository pattern with unit of work.

www.nuget.org

 

'개발 이야기' 카테고리의 다른 글

AWS 스탠다드 SQS를 FIFO SQS처럼 사용하기  (0) 2023.03.17
아주 괜찮은 WYSIWYG 에디터  (0) 2023.03.06
AWS SQS의 활용  (0) 2023.02.23
또 한번의 승진, Staff Developer로  (2) 2023.02.18
테스트 자동화 Cypress  (0) 2023.02.12
  • 네이버 블러그 공유하기
  • 네이버 밴드에 공유하기
  • 페이스북 공유하기
  • 카카오스토리 공유하기