Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add singleton factories to create instance per hub in management SDK #1070

Merged
merged 13 commits into from
Nov 17, 2020

Conversation

Y-Sindo
Copy link
Member

@Y-Sindo Y-Sindo commented Oct 19, 2020

Summary of the changes

  • Fully apply DI
  • Add new methods GenerateClientAccessToken to get client endpoint and access token.
  • Breaking changes: ILoggerFactory specified in CreateHubContextAsync is only passed to per hub instance. Global singleton instances shares one ILoggerFactory.

Usage example

Method1: use global service collection

public void ConfigureServices(IServiceCollection services)
{
      services.AddSignalRServiceManager();
      services.AddSingleton(endpointRouter);  //optional
      services.AddSingleton(loggerFactory);//optional
      services.WithAssembly(Assembly.GetCallingAssembly());//optional
}

Method2: use ServiceManagerBuilder

In this way, dynamic configuration is not supported.

    var builder =new ServiceManagerBuilder();
    builder.WithOptions(...);
    builder.WithConfiguration(...);
    builder.WithRouter(...);
    builder.WithLoggerFactory(...);
    var serviceManager=builder.Build();

@Y-Sindo Y-Sindo marked this pull request as draft October 19, 2020 11:40
@Y-Sindo Y-Sindo marked this pull request as ready for review October 20, 2020 02:09
@Y-Sindo Y-Sindo requested review from vicancy and wanlwanl and removed request for vicancy October 20, 2020 03:28
@Y-Sindo Y-Sindo marked this pull request as draft October 20, 2020 06:15
@Y-Sindo Y-Sindo marked this pull request as ready for review October 21, 2020 04:56
@Y-Sindo Y-Sindo marked this pull request as draft October 21, 2020 08:51
@Y-Sindo Y-Sindo force-pushed the unified-service-manager branch 3 times, most recently from b6c5984 to 46f2113 Compare October 26, 2020 06:10
@Y-Sindo Y-Sindo changed the title DI pattern Multiple-endpoint service manager Add singleton factories to create instance per hub in management SDK Oct 26, 2020
@Y-Sindo Y-Sindo marked this pull request as ready for review October 26, 2020 06:12
@Azure Azure deleted a comment from Y-Sindo Oct 26, 2020
@Y-Sindo Y-Sindo force-pushed the unified-service-manager branch from def780d to b0ed9c4 Compare November 10, 2020 06:35
@Y-Sindo Y-Sindo requested a review from wanlwanl November 10, 2020 06:41
@Y-Sindo Y-Sindo force-pushed the unified-service-manager branch from b0ed9c4 to fe5b720 Compare November 10, 2020 10:53
@Y-Sindo Y-Sindo requested a review from vicancy November 10, 2020 11:04
public async Task<IServiceHubContext> Create(string hubName, ILoggerFactory loggerFactory = null, CancellationToken cancellationToken = default)
{
var manager = await _managerFactory.Create(hubName, cancellationToken, loggerFactory);
var servicesPerHub = new ServiceCollection();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why the service collection is for each hub? isn't it enough for each service manager?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because IHubContext<Hub> is for each hub, and one ServiceManager could have multiple HubContext

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I am also confused here, why DI ctor not working ? public ServiceHubContextFactory(ServiceHubLifetimeManagerFactory managerFactory, IHubContext<Hub> context)

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For two reasons:

  1. IHubContext<Hub> is not singleton. And its impl HubContext<> is an internal class, so we can't do services.AddTransient(typeof(IHubContext<>),typeof(HubContext<>));.
  2. HubContext<Hub> depends on HubLiftTimeManager<>, which depends on hub name in our impl.

var container = containerFactory.Create(hubName,loggerFactoryPerHub);
var connectionManager = new ServiceConnectionManager<Hub>();
connectionManager.SetServiceConnection(container);
_ = connectionManager.StartAsync();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This class is a factory for creating ServiceHubLifetimeManager, but it also start connection manager here. start should be called in other place

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Strictly speaking, CreateServiceHubContext() is a method for creating ServiceHubContext, but we start connection manager inside it, and this is unavoidable. Given this, I don't think starting connection manager in another factory is unacceptable?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about moving the preparison for connection manager into WebSocketsHubLifetimeManager? So that WebSocketsHubLifetimeManager is no knowledge of hub name, and is also able to put into service collection, and make it transient? And set the connection container and start the container in ServiceHubContextFactory.CreateAsync like the old version? @vicancy

                        var containerFactory = _serviceProvider.GetRequiredService<MultiEndpointConnectionContainerFactory>();
                        var container = containerFactory.Create(hubName,loggerFactoryPerHub);
                        var connectionManager = new ServiceConnectionManager<Hub>();
                        connectionManager.SetServiceConnection(container);

Copy link
Member Author

@Y-Sindo Y-Sindo Nov 12, 2020

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand correctly, WebSocketsHubLifetimeManager is transient means that IServiceHubContext is transient too?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you plan to get a new one for each request?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The key is that HubContext<> is an internal class, and we can't add it as transient.

connectionManager.SetServiceConnection(container);
_ = connectionManager.StartAsync();
await container.ConnectionInitializedTask.OrTimeout(cancellationToken);
return loggerFactoryPerHub == null ? ActivatorUtilities.CreateInstance<WebSocketsHubLifetimeManager<Hub>>(_serviceProvider, connectionManager) : ActivatorUtilities.CreateInstance<WebSocketsHubLifetimeManager<Hub>>(_serviceProvider, connectionManager, loggerFactoryPerHub);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since you already put most of the class as a service into service collection, why not put WebSocketsHubLifetimeManager and RestHubLifetimeManager into service collection as well?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

WebSocketsHubLifetimeManager and RestHubLifetimeManager are not singletons, they are for hub. And we don't know the hub name until CreateServiceHubContext(...) is called, while hub name is bound to ServiceHubLifetimeManager.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I thinks it's better prepare all the thing before actually start the container, so put the startAsync to ServiceHubContextFactory.CreateAsync is better

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the actual difference between starting connection manager in ServiceHubLifetimeManagerFactory and ServiceHubContextFactory? Now ServiceHubContextFactory doesn't need to know the actual impl of IServiceHubLifetimeManager . I think to put connectionManager.StartAsync() in ServiceHubContextFactory breaks the hierarchy of the code.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what I mean is to put the used classes into service collections if possible, when everything is ready, start the connections

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is not singleton and we can't create connectionManager until we know hub name?

.AddSingleton<MultiEndpointConnectionContainerFactory>()
.AddSingleton<IConfigureOptions<HubOptions>, ManagementHubOptionsSetup>();

services.AddLogging()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shall we let users decide if they AddLogging ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How about replace AddLogging() with services.TryAdd(ServiceDescriptor.Singleton<ILoggerFactory, LoggerFactory>()); to make sure we have a default impl of ILoggerFctory?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We can follow the general practice for a library package, for example, does SignalR pakage add default logger?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SignalR package doesn't addLogger. But since we already use ILoggerFactory, why we don't add default logger for user? If no default loggerFactory is set, program throws error when users don't specify logger. User can add their own loggerFactory and the default one won't take effect.

public async Task<IServiceHubContext> Create(string hubName, ILoggerFactory loggerFactory = null, CancellationToken cancellationToken = default)
{
var manager = await _managerFactory.Create(hubName, cancellationToken, loggerFactory);
var servicesPerHub = new ServiceCollection();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah I am also confused here, why DI ctor not working ? public ServiceHubContextFactory(ServiceHubLifetimeManagerFactory managerFactory, IHubContext<Hub> context)

var serviceHubContext = await serviceManager.CreateHubContextAsync("hub", loggerFactory);
var connectionContainer = ((ServiceHubContext)serviceHubContext).ServiceProvider.GetRequiredService<IServiceConnectionContainer>();
var serviceHubContext = await serviceManager.CreateHubContextAsync("hub", default);
var connectionContainer = ((ServiceHubContext)serviceHubContext).ServiceProvider.GetRequiredService<IServiceConnectionContainer>();//TODO
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

remove this //TODO comments with little meanings, or add more details into it about what todo

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test is for reconnection function and doesn't work anymore, because connectionContainer isn't in the service provider of a ServiceHubContext and it's not convenient to add it to the service collection per hub. I think ideally this test should do for connection container class. I intend to add a unit test for connection container separately latter.

Copy link
Member

@vicancy vicancy left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@Y-Sindo Y-Sindo merged commit 1dcfaab into Azure:dev Nov 17, 2020
@Y-Sindo Y-Sindo deleted the unified-service-manager branch November 17, 2020 08:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants