-
Entity Framework는 Unit of Work가 필요한가?프로그래밍 2024. 10. 14. 14:10
많은 예제 중에 EF를 UoW화 사용하는 예제가 많이 있다.
필자는 그러한 예제가 틀렸다고 이야기하고 싶다.
UoW를 EF 상에서 구현하다 보면 DbContext를 작성하는 것과 다름없다고 느낀다면 필자는 그것이 맞다고 이야기하고 싶다.
EF의 DbContext 구현은 사실상 UoW의 구현이다.
그럼 왜 UoW를 구현하려고 할까?
그것은 이전 마이크로ORM인 Dapper나 ADO.NET와 연속성을 가지기 위해서라고 필자는 생각한다.
EF를 사용함에 있어 기존 구조가 Service - Repository의 구현이라면 누구나 Repository 패턴에 맞춰서 개발하고 싶을 것이고 Repository를 구현하는 데 있어 사용하는 패턴이 UoW인 것이다.
따라서, 필자는 EF의 DbContext를 구현하고 사용하므로 EF만을 사용하는 프로젝트에서 UoW는 불필요하다고 말하고 싶다.
Transaction은 어떻게 처리할까 의문이 들 수 있다.
아래의 예제를 보자.
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)] public class TransactionOptionsAttribute : Attribute { public IsolationLevel IsolationLevel { get; } public TimeSpan Timeout { get; } /// <summary> /// Controller에 선언되는 TransactionOptions /// </summary> /// <param name="isolationLevel"></param> /// <param name="timeoutSeconds"></param> public TransactionOptionsAttribute( IsolationLevel isolationLevel = IsolationLevel.ReadUncommitted , int timeoutSeconds = 5 ) { IsolationLevel = isolationLevel; #if DEBUG Timeout = TimeSpan.FromSeconds(3200); #else Timeout = TimeSpan.FromSeconds(timeoutSeconds); #endif } }
public class TransactionMiddleware { private readonly RequestDelegate _next; /// <summary> /// ctor /// </summary> /// <param name="next"></param> public TransactionMiddleware(RequestDelegate next) { _next = next; } public async Task InvokeAsync(HttpContext context) { // var controllerName = context.Request.RouteValues["controller"]?.ToString(); // var actionName = context.Request.RouteValues["action"]?.ToString(); var sessionContext = context.RequestServices.GetRequiredService<ISessionContext>(); try { var endpoint = context.GetEndpoint(); if (endpoint.xIsNotEmpty()) { var metadata = endpoint.Metadata; var transactionOptions = metadata.GetMetadata<TransactionOptionsAttribute>(); if (transactionOptions.xIsNotEmpty()) { if (sessionContext.DbContext.Database.CurrentTransaction.xIsNotEmpty()) { await sessionContext.DbContext.Database.UseTransactionAsync(sessionContext.DbContext.Database .CurrentTransaction!.GetDbTransaction()); } else { var cts = new CancellationTokenSource(transactionOptions.Timeout); var ct = cts.Token; await sessionContext.DbContext.Database.BeginTransactionAsync(transactionOptions.IsolationLevel, ct); } } } // 다음 미들웨어 호출 await _next(context); if(sessionContext.DbContext.Database.CurrentTransaction.xIsNotEmpty()) { await sessionContext.DbContext.Database.CurrentTransaction!.CommitAsync(); } } catch (Exception) { if(sessionContext.DbContext.Database.CurrentTransaction.xIsNotEmpty()) { await sessionContext.DbContext.Database.CurrentTransaction!.RollbackAsync(); } throw; } } }
위와 같이 트랜잭션을 위한 attribute를 선언해서 처리할 수 있다.
위 예제에서 중요한 것은 UseTransactionAsync가 되겠다.
UseTransactionAsync는 Transaction을 참여적으로 동작하게 하는 부분으로 DbContext를 상속한 다른 DbContext도 동작할 수 있게 할 수 있다.
다만, 위와 같이 할 경우 UoW의 역활을 파편화하는 것으로 볼 수 있으므로 부정적으로 볼 수 있겠지만, 적어도 Repository를 구현하지 않는다는 측면에서 분명한 이점이 있다.
오늘 이 부분은 닷넷 개발자라면 한번은 보았을 논란의 주제이기도 하다.
따라서 아래와 같이 정리한다.
기존에 Repository와 Dapper, ADO.NET로 구축한 UoW패턴을 사용한다고 EF를 같이 사용하려 한다면 UoW패턴을 고수하라.
신규 프로젝트이고 EF 만을 사용하여 구축한다면 과감히 UoW를 사용하지 않고 처리할 수 있다.
위와 같이 정리할 수 있겠다.
도움이 되길 바라며...
'프로그래밍' 카테고리의 다른 글
EF CORE의 상태 변경 (1) 2024.10.16 예약, 예매 시스템에 대한 고찰-2 (6) 2024.10.16 멀티스레드 환경에서의 데이터 전송에 대한 안정성 향상 방법 (3) 2024.10.10 테이블 캐시를 관리하는 방법 (0) 2024.10.04 EF Core Code First Entity Model을 작업하는 방법 (0) 2024.09.24