Skip to content

Storage providers instantiation#1

Open
jdom wants to merge 3 commits intojason-bragg:masterfrom
jdom:storage-providers
Open

Storage providers instantiation#1
jdom wants to merge 3 commits intojason-bragg:masterfrom
jdom:storage-providers

Conversation

@jdom
Copy link

@jdom jdom commented Sep 24, 2016

This PR is just work in progress before I go to sleep, but it is basically how I envision the providers instantiation (as we know they are not singleton services).
This is modeled from how AspNet allows optional features.
The important thing is how the Startup class looks like, and how the configuration is done via extension methods on IServiceCollection and ISiloBuilder (the latter I added emulating the IApplicationBuilder from Asp.NET)

This does not yet use IOptions<T> to configure the storage providers' specific info, but that's coming later if you feel the approach is good.

For now I put everything in the abstractions project, but in theory they should be moved to different pieces (I added code comments specifying where they should live).

This does not yet use IOptions to configure the storage providers' specific info, but that's coming later
@jason-bragg
Copy link
Owner

Thanks for tinkering with me!
Suspected I'd be alone in here..


namespace Microsoft.Orleans.Host.Abstractions
{
public interface ISiloBuilder
Copy link
Owner

Choose a reason for hiding this comment

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

Was planning to put silo builder in Microsoft.Orleans.Silo.Abstractions.

A goal of this design is logical separation of actor model, i.e silo and service framework, i.e host. Silo should be able to be run in any service framework. Host is our framework as the silo is not much value without a framework to run it in.

public interface IStartup
{
IEnumerable<Type> Members { get; }
IEnumerable<Type> Members { get; } // not sure I understand why this?
Copy link
Owner

Choose a reason for hiding this comment

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

This is for the purpose of constructing any systems the host contains. Like a silo, in this way, a silo is just another pluggable component in the service framework. Provider are too. IMO, this silo should not concern itself with providers, they are a framework detail.

{
logger = loggerFactory.CreateLogger<Thingy>();
lifecycle.Subscribe(this);
lifecycle.Subscribe(this); //<-- passing this before finishing constructing is dangerous (although practical)
Copy link
Owner

Choose a reason for hiding this comment

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

yeah, I was being lazy. There are extension functions to connect callbacks. I'll put out a PR to illustrate.

namespace Microsoft.Orleans.Host.Abstractions
{
// this would go into the azure storage providers DLL
public static class AzureStorageProviderExtensions
Copy link
Owner

Choose a reason for hiding this comment

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

extensions should go in an {namespace}.Extensions namespace according to framework standards. Users should have the option to opt in.

Copy link
Author

@jdom jdom Sep 24, 2016

Choose a reason for hiding this comment

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

Yup, as I mentioned in the description, I just put everything in the same project for now and didn't even consider the namespace, just showing what the programing Api would look like (mainly startup class) . Also, some of these new files mention that they should go in different Dlls that you only pull in if you use them

// this would go into the (optional) storage providers DLL.
public interface IStorageProvidersFactory
{
ISiloBuilder Silo { get; }
Copy link
Owner

Choose a reason for hiding this comment

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

Why should as silo builder be part of this interface..?

Copy link
Author

Choose a reason for hiding this comment

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

Because it allows you to access runtime service that can be used by it or passed along for concrete provider factories to use, as in this azure storage example

namespace Microsoft.Orleans.Host.Abstractions
{
// this would go into the (optional) storage providers DLL.
public interface IStorageProvidersFactory
Copy link
Owner

Choose a reason for hiding this comment

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

This interface doesn't have any characteristics of a factory..

Copy link
Author

Choose a reason for hiding this comment

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

Yup, I agree, remnants of previous prototyping. This is more an IStorageProviderManager

Copy link
Author

Choose a reason for hiding this comment

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

or IStorageProvidersBuilder actually, and then it would be more obvious why it has an ISiloBuilder too.

namespace Microsoft.Orleans.Host.Abstractions
{
// this would go into the azure storage providers DLL
public static class AzureStorageProviderExtensions
Copy link
Author

@jdom jdom Sep 24, 2016

Choose a reason for hiding this comment

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

Yup, as I mentioned in the description, I just put everything in the same project for now and didn't even consider the namespace, just showing what the programing Api would look like (mainly startup class) . Also, some of these new files mention that they should go in different Dlls that you only pull in if you use them

namespace Microsoft.Orleans.Host.Abstractions
{
// this would go into the (optional) storage providers DLL.
public interface IStorageProvidersFactory
Copy link
Author

Choose a reason for hiding this comment

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

Yup, I agree, remnants of previous prototyping. This is more an IStorageProviderManager

// this would go into the (optional) storage providers DLL.
public interface IStorageProvidersFactory
{
ISiloBuilder Silo { get; }
Copy link
Author

Choose a reason for hiding this comment

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

Because it allows you to access runtime service that can be used by it or passed along for concrete provider factories to use, as in this azure storage example

{
public static void AddAzureStorage(this IStorageProvidersFactory factory, string providerName, string connectionString)
{
var concreteFactory = ActivatorUtilities.CreateInstance<AzureStorageProviderFactory>(factory.Silo.ApplicationServices);
Copy link
Author

Choose a reason for hiding this comment

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

I believe that the AzureStorageProviderFactory might not be required if I use the right overload for ActivatorUtilities.CreateInstance, since the factory is there mainly to forward injected service dependencies and separate them from instance specific data/config

@jason-bragg
Copy link
Owner

So I updated tree with a second prototype pass on the provider model.
Still not convinced it's right, but trying to explore a couple ideas.

Separation of framework from Virtual Actor container (Silo)
Providers add capabilities to the framework. If the framework itself is extensible, providers are not necessary, so the question is how to make the framework extensible enough to support the behaviors of providers. Once we figure out how the silo works within the framework, we can access these extensibility points from within the silo. This changes the question from how do we support providers to what are provider behaviors and how do we support these behaviors within the framework.

Providers are not Services
Providers can't properly be considered services from the DI sense, as they have per instance initialization. Grains also have this problem, but for now I'm focusing on the general problem: How do we support per instance initialization.

Current Prototype
In the current prototype, I'm using a factory pattern with an optional configuration (after creation) step, to support per instance initialization. For providers, I use the factory pattern to create a provider via DI, so it can be constructed with dependencies, then configure it after construction. The provider itself, gets things it needs like lifecycle and logger factory at construction time, is configured, and is initialized when appropriate in the lifecycle.
A provider group serves as a singleton in the DI system as a means of accessing specific providers. At construction time, it constructs all configured providers, but doesn't know anything about the provider’s construction, configuration, or initialization.
Grains that need a provider to have the provider group injected at construction time, and then ask the provider group for the provider by name.

In this pattern, the component that knows most about the provider is the provider itself and its configuration step. Everything else in the system knows only what it needs. Provider group just knows that it exists and the caller just knows the client behavior.

While this design achieves most of my goals, there is a problem with configuration, because the behavior is supported by a collection of classes that all need to setup. This degree of complexity can't easily be hidden by a per provider extension function like AddStorageProvider, so simplifying the configuration/setup is something that will need more thought.

@jason-bragg
Copy link
Owner

Took another pass on configuration. Much nicer now.
Patter also allows lookup of provider by any key. Sample uses string and Guid.
Configuration code below.

            services.AddSingleton<ISiloRuntime, DummySiloRuntime>()
                    .AddTransient<SomePersistentGrain>()
                    .AddProviderGroup<string,IStorageProvider>()
                        .AddAzureStorageProvider("Azure", "secret")
                        .AddAzureStorageProvider("Azure2", "secret2")
                    .Build()
                    .AddProviderGroup<Guid, IStorageProvider>()
                        .AddAzureStorageProvider(Guid.NewGuid(), "secret3")
                        .AddAzureStorageProvider(Guid.NewGuid(), "secret4")
                        .AddAzureStorageProvider(Guid.NewGuid(), "secret5")
                        .AddAzureStorageProvider(Guid.NewGuid(), "secret6")
                    .Build();

@jason-bragg
Copy link
Owner

Tinkered with silo and multi-silo hosts using the provider model.

Since service provider supports singletons and provider model (probably need to rename it) supports unique instances, we can create, configure, initialize, run, and stop one or more silos in the host, by simply changing the startup class.

Members management needs to change though, I think. Members, like services, need to be modifiable in a better way. Have an idea how to do this, but need to think on it more.

Copy link

@xiazen xiazen left a comment

Choose a reason for hiding this comment

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

I probably should just use GitHub comment , instead of starting a review, since this is a prototype PR.

But hey I'm playing with the new GitHub feature!

{
public static void AddAzureStorage(this IStorageProvidersBuilder factory, string providerName, string connectionString)
{
var concreteFactory = ActivatorUtilities.CreateInstance<AzureStorageProviderFactory>(factory.Silo.ApplicationServices);
Copy link

@xiazen xiazen Sep 26, 2016

Choose a reason for hiding this comment

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

feels like concreteFactory can be reused for creating other AzureStorageProvider, which should be a singleton. maybe the method signature should be
public static void AddAzureStorage(this IStorageProvidersBuilder factoryBuilder, AzureStorageProviderFactory factory, string providerName, string connectionString)

// this would go into the azure storage providers DLL
public static class AzureStorageProviderExtensions
{
public static void AddAzureStorage(this IStorageProvidersBuilder factory, string providerName, string connectionString)
Copy link

@xiazen xiazen Sep 26, 2016

Choose a reason for hiding this comment

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

Why isn't this method member of AzureStorageProviderFactory?

If you worry about AzureStorageProviderFactory'saccessability as an internal class, maybe we can use an interface as a medium to publicize AddAzureStorage method:
interface IAzureStorageProviderFactory: { public AddAzureStorage(...) }

Copy link
Author

Choose a reason for hiding this comment

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

because this extendes the IStorageProvidersBuilder interface, not the concrete azure factory. In fact, as I mentioned in a comment, the factory itself might even go away, it's just an implementation detail.

// this would go into the (optional) storage providers DLL.
public interface IStorageProvidersBuilder
{
ISiloBuilder Silo { get; }
Copy link

Choose a reason for hiding this comment

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

not sure why IStorageProviderBuildershould have a member of ISiloBuilder. Feels to me IStorageProviderBuildershould be member of ISiloBuilder, so it will build StorageProviders based on silo's lifecycle event. Not the other way around.

Copy link
Author

Choose a reason for hiding this comment

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

this way storage providers are an opt-in framework feature that do not require to be bundled in the virtual actors core. You decide whether you want to use storage providers explicitly, and since it's entirely opt-in, you might decide to use an alternative storage providers model entirely, because the framework would be flexible enough to support it.

public static void ConfigureStorageProviders(this ISiloBuilder silo, Action<IStorageProvidersBuilder> configureMethod)
{
var factory = silo.ApplicationServices.GetRequiredService<IStorageProvidersBuilder>();
configureMethod(factory);
Copy link

Choose a reason for hiding this comment

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

Ah I see why you give IStorageProviderBuilder a SiloBuilder memeber. You want to get the registered IStorageProvidersBuilderfrom it. But this is the sevice locator pattern we want to avoid. Well I think you can do ConfigureStorageProviders(IStorageProviderBuilder builder, Action<IStorageProvidersBuilder> configureMethod) and get IStorageProvidersBuilder before calling this method

Copy link
Author

Choose a reason for hiding this comment

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

not sure I follow. This is not client code. The programming API for the user does not use the service locator pattern, it's all abstracted away by these configuration methods, in the same way as Asp.NET hides these too. If we change the signature to what you suggested, it means the user will be in charge of resolving the IStorageProvidersBuilder himself and then pass it in (probably by putting the burden on him to use the service locator pattern

}

public interface IStorageProvider
{
Copy link

Choose a reason for hiding this comment

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

I think this can inherite from ILifecycleObserver . So that all IStorageProvider which share similar life cycle style/pattern can be managed in the same LifeCycle class.

@xiazen
Copy link

xiazen commented Sep 26, 2016

Per @jason-bragg comment. Not sure why I cannot just replied to @jason-bragg comment

Minor:

Grains that need a provider to have the provider group injected at construction time, and then ask the provider group for the provider by name.

If Grain takes provider group as a param in contruction time, this is service locator pattern again, which I think we should really avoid. Maybe we can have a

Class GrainFactory:
{
   GrainFactory(ProviderGroup group);
   Create(Grain grain); // Grain will give you a list of Provider names it needs. And GrainFactory will find those providers in ProviderGroup and create the Grain. 
}

Since ProviderGroup is really just a dependency of GrainFactory, not every single Grain.

Confusion:

While this design achieves most of my goals, there is a problem with configuration, because the behavior is supported by a collection of classes that all need to setup. This degree of complexity can't easily be hidden by a per provider extension function like AddStorageProvider, so simplifying the configuration/setup is something that will need more thought.

Why isn't Providers managed by LifeCycle class as ILifecycleObserver? So each type of Provider can have its own OnStartUp method or OnConfigure method which makes the lifecycle management much more flexible.
Something like this:

class AzureProvider : ILifecycleObserver
{
  AzureProvider(Config config);
 void OnInitialize(Config config) {// AzureProvider configure method}
 void OnStartUp() {//AzureProvider start up configure}
 void OnStop() {...}
}

class AWSProvider: ILifecycleObserver
{
 AWSProvider(Config config)
void OnInitialize() {// AWSProvider configure method}
 void OnStartUp() {//AWSProvider start up configure}
 void OnStop() {...}

}

configure providers :

class ProviderLifeCycle : LifeCycle
{
//inherit from LifeCycle class, just want it to be just managing providers, similar as a ProviderGroup 
}
class StartUp:
{
 public IServiceProvider ConfigureServices(IServiceCollection services)
{
  ProviderLifecycle providersManager = new ProviderLifeCycle();
  providerManager.Subscribe(config1.name, new AzureProvider(config1));  
  providerManager.Subscribe(config2.name, new AzureProvider(config2));
  providerManager.Subscribe(config3.name, new AWSProvider(config3));
  services.addSingleton<ProviderLifeCycle>(providersManager);
   ...
}
}

This strike to me that LifeCycle, or ILifecycleObservershould have a method like GetObserver(Tkey key), so it can get one Observer from LifeCycleobject and Start it or Terminate it or just use it (to stream or store something or resolve dependency for grains), instead of a group termination or a group start up. Just to adds more flexibility.

{
Silo = silo;
}
public ISiloBuilder Silo { get; }
Copy link

Choose a reason for hiding this comment

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

It something is a builder, don't miss the Builder from the name, it can be confusing.

Copy link
Author

Choose a reason for hiding this comment

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

OK. Yeah, I don't understand why in ASP.NET they just call their IApplicationBuilder "app"... Or maybe it was just in param names, but not necessarily properties

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.

4 participants