This repository has been archived by the owner on Aug 3, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 18
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[Package Signing] Added a Scoped Service Bus Message Handler (#82)
Often times, Service Bus message handlers will need to use some sort of Entity Framework context. This requires that the handlers' callbacks are executed within their own dependency injection scope. This change creates a new generic Service Bus message handler `ScopedMessageHandler` that, upon receiving a message, creates a new dependency injection scope. This change also makes `CachingSecretReader` thread-safe, as otherwise, the `ScopedMessageHandler` would need a lock.
- Loading branch information
1 parent
972413a
commit b84a8d8
Showing
8 changed files
with
165 additions
and
65 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.DependencyInjection; | ||
|
||
namespace NuGet.Services.ServiceBus | ||
{ | ||
/// <summary> | ||
/// Handles messages received by a <see cref="ISubscriptionProcessor{TMessage}"/>. | ||
/// Each message will be handled within its own dependency injection scope. | ||
/// </summary> | ||
/// <typeparam name="TMessage">The type of messages this handler handles.</typeparam> | ||
public class ScopedMessageHandler<TMessage> | ||
{ | ||
/// <summary> | ||
/// The factory used to create independent dependency injection scopes for each message. | ||
/// </summary> | ||
private readonly IServiceScopeFactory _scopeFactory; | ||
|
||
public ScopedMessageHandler(IServiceScopeFactory scopeFactory) | ||
{ | ||
_scopeFactory = scopeFactory ?? throw new ArgumentNullException(nameof(scopeFactory)); | ||
} | ||
|
||
/// <summary> | ||
/// Handle the message in its own dependency injection scope. | ||
/// </summary> | ||
/// <param name="message">The received message.</param> | ||
/// <returns>Whether the message has been handled. If false, the message will be requeued to be handled again later.</returns> | ||
public async Task<bool> HandleAsync(TMessage message) | ||
{ | ||
// Create a new scope for this message. | ||
using (var scope = _scopeFactory.CreateScope()) | ||
{ | ||
// Resolve a new message handler for the newly created scope and let it handle the message. | ||
return await ResolveMessageHandler(scope).HandleAsync(message); | ||
} | ||
} | ||
|
||
/// <summary> | ||
/// Resolve the message handler given a specific dependency injection scope. | ||
/// </summary> | ||
/// <param name="scope">The dependency injection scope that should be used to resolve services.</param> | ||
/// <returns>The resolved message handler service from the given scope.</returns> | ||
private IMessageHandler<TMessage> ResolveMessageHandler(IServiceScope scope) | ||
{ | ||
return scope.ServiceProvider.GetRequiredService<IMessageHandler<TMessage>>(); | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
47 changes: 47 additions & 0 deletions
47
tests/NuGet.Services.ServiceBus.Tests/ScopedMessageHandlerFacts.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
// Copyright (c) .NET Foundation. All rights reserved. | ||
// Licensed under the Apache License, Version 2.0. See License.txt in the project root for license information. | ||
|
||
using System; | ||
using System.Threading.Tasks; | ||
using Microsoft.Extensions.DependencyInjection; | ||
using Moq; | ||
using Xunit; | ||
|
||
namespace NuGet.Services.ServiceBus.Tests | ||
{ | ||
public class ScopedMessageHandlerFacts | ||
{ | ||
[Fact] | ||
public async Task CreatesMessageHandlerUsingScope() | ||
{ | ||
// Arrange | ||
var scopeFactoryMock = new Mock<IServiceScopeFactory>(); | ||
var scopeMock = new Mock<IServiceScope>(); | ||
var serviceProviderMock = new Mock<IServiceProvider>(); | ||
var handlerMock = new Mock<IMessageHandler<object>>(); | ||
|
||
scopeFactoryMock | ||
.Setup(f => f.CreateScope()) | ||
.Returns(scopeMock.Object); | ||
|
||
scopeMock | ||
.SetupGet(s => s.ServiceProvider) | ||
.Returns(serviceProviderMock.Object); | ||
|
||
serviceProviderMock | ||
.Setup(p => p.GetService(typeof(IMessageHandler<object>))) | ||
.Returns(handlerMock.Object); | ||
|
||
var target = new ScopedMessageHandler<object>(scopeFactoryMock.Object); | ||
|
||
// Act - send two empty messages. | ||
await target.HandleAsync(new object()); | ||
await target.HandleAsync(new object()); | ||
|
||
// Assert | ||
scopeFactoryMock.Verify(f => f.CreateScope(), Times.Exactly(2)); | ||
serviceProviderMock.Verify(p => p.GetService(typeof(IMessageHandler<object>)), Times.Exactly(2)); | ||
handlerMock.Verify(h => h.HandleAsync(It.IsAny<object>()), Times.Exactly(2)); | ||
} | ||
} | ||
} |