2016-07-06 7 views
0

내 웹 사이트에서 메시지를 가져 와서 버스에 올려 놓으면 서비스가이를 선택하고 감사 기능을 사용하여 AddedBy/UpdatedBy 필드를 자동으로 채 웁니다 행의.다른 스레드에서 실행중인 NServiceBus 및 NHibernate EventListeners

ASP.Net 응용 프로그램에서 로그인 한 사용자가 제공하는 Thread.CurrentPrincipal의 메시지 헤더에 사용자 ID를 기록하는 NServiceBus IMessageMutator 구성 요소를 사용하여이 작업을 수행합니다. 내 서비스에서 IMessageModule을 사용하여이 헤더를 추출하고 이것을 Thread.CurrentPrincipal에 바인딩합니다. 이 훌륭한 작품과 내 메시지 처리기 동안 Thread.CurrentPrincipal.Identity.Name 웹 응용 프로그램에서 메시지를 제기 한 사용자 ID에 올바르게 바인딩 된 볼 수 있습니다.

NHibernate의 IPreUpdateEventListener/IPreInsertEventListener를 사용하여 DB에 기록되기 전에 각 엔티티의 AddedBy/UpdatedBy를 설정합니다. 이것은 웹 사이트에서 완벽하게 작동하지만 내 NServiceBus 서비스에서 리스너가 실행되는 스레드는 처리기가 실행 된 스레드와 다르다. 즉 스레드의 CurrentPrincipal이 더 이상 내 IMessageModule에 바인딩 된 ID가 아님을 의미합니다.

NHibernate가 내 문제의 원인이라고 의심되는 호출 스택에서 DistributedTransactionFactory를 사용하는 것을 볼 수 있습니다. 커밋이 실패한 경우 메시지를 다시 시도하거나 오류 대기열에 넣지 않고 큐에서 메시지를 제거하지 못하고 업데이트가 DB로 롤백되지 않는 트랜잭션 성을 잃고 싶지 않습니다.

나는 웹을 둘러 보았고 모든 예제는 스레드의 CurrentPrincipal을 사용하여 행을 수정 한 사용자의 ID를 바인드합니다. 내가 찾고있는 중 하나가 메시지 처리기와 동일한 스레드에 NHibernate 수신기를 유지하거나 DB에 기록되기 전에 엔터티에 바인딩 될 수 있도록 사용자 ID를 수신기에 전달하는 방법입니다. 여기

내 청취자, 나는 그것을

public class EntityPersistenceListener : IPreUpdateEventListener, IPreInsertEventListener 
{ 
    public bool OnPreUpdate(PreUpdateEvent @event) 
    { 
     var audit = @event.Entity as EntityBase; 
     if (audit == null) 
      return false; 

     var time = DateTimeFactory.GetDateTime(); 

     var name = Thread.CurrentPrincipal.Identity.Name; 

     Set(@event.Persister, @event.State, "AddedDate", audit.AddedDate); 
     Set(@event.Persister, @event.State, "AddedBy", audit.AddedBy); 
     Set(@event.Persister, @event.State, "UpdatedDate", time); 
     Set(@event.Persister, @event.State, "UpdatedBy", name); 

     audit.AddedDate = audit.AddedDate; 
     audit.AddedBy = audit.AddedBy; 
     audit.UpdatedDate= time; 
     audit.UpdatedBy = name; 

     return false;    
    } 
} 

에있는 설정 방법을 생략 그리고 여기에 ID를 추출하고, 현재의 thread의 ID로 바인딩 NServiceBus 메시지 모듈입니다했다됩니다

public class TenantAndInstanceInfoExtractor : IMessageModule 
{ 
    private readonly IBus _bus; 

    public TenantAndInstanceInfoExtractor(IBus bus) 
    { 
     _bus = bus; 
    } 

    public void HandleBeginMessage() 
    { 
     var headers = _bus.CurrentMessageContext.Headers; 

     if (headers.ContainsKey("TriggeredById")) 
      Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(headers["TriggeredById"]), null); 
     else 
      Thread.CurrentPrincipal = new GenericPrincipal(new GenericIdentity(string.Empty), null); 
    } 

    public void HandleEndMessage() 
    { 

    } 

    public void HandleError() { } 
} 
+1

모건. 귀하의 요구 사항을 완전히 이해하는 데 문제가 있습니다. 다음주에 스카 이프 전화를 할 시간이 있으십니까? 나는 simphone에 "simon.cropp"입니다 – Simon

+0

안녕 사이먼, 네. 시간을 주선하기 위해 이메일을 보내 드리겠습니다. – Morgan

답변

0

모든 도움을 주신 사이몬에게 감사드립니다. 내 문제를 광범위하게 살펴본 후 NServiceBus가 내부적으로 어떻게 작동하는지에 대해 논의한 후에 나는 당신의 통찰력을 얻었고 NServiceBus를위한 작업 단위 모듈을 구현했습니다.

우리는 NHibernate 세션을 DB에 위탁하기 위해 메시지 당 생성 된 트랜잭션에 의존하고있었습니다. 이것은 분산 트랜잭션 컨트롤러에서 발생합니다 (특히 여기 NHibernate.Transaction.AdoNetWithDistributedTransactionFactory.DistributedTransactionContext에서 발생합니다). 이것은 스레드 풀을 이용합니다.

NServiceBus의 IManageUnitsOfWork 인터페이스를 사용하여 코드 예제에서 아래와 같이 메시지 처리기와 동일한 스레드에서 트랜잭션을 명시 적으로 커밋 할 수있었습니다.

미래의 독자를위한 부수적 인 언급으로,이 솔루션은 멀티 스레드 환경에서 실패한 것처럼 Thread.CurrentPrincipal을 사용하지 않는 것이 가장 좋은 해결책입니다.

public class HiJumpNserviceBusUnitOfWork : IManageUnitsOfWork 
{ 
    private readonly IUnitOfWork _uow; 

    public HiJumpNserviceBusUnitOfWork(IUnitOfWork uow) 
    { 
     _uow = uow; 
    } 

    public void Begin() 
    { 
     _uow.ClearCache(); 
     _uow.BeginTransaction(); 
    } 

    public void End(Exception ex = null) 
    { 
     if (ex != null) 
     { 
      _uow.ClearCache(); 
     } 
     else 
     { 
      _uow.CommitTransaction(); 
     } 
    } 
}