dotnet / orleans

Cloud Native application framework for .NET
https://docs.microsoft.com/dotnet/orleans
MIT License
10.07k stars 2.03k forks source link

Orleans service provider not available to application layer. #934

Closed jason-bragg closed 7 years ago

jason-bragg commented 8 years ago

A while back an IServiceProvider was introduced into the Silo to allow application developers to inject dependencies into Orleans. The service provider is initialized with a configurable hook that creates a service provider from a service collection.

From test code: public class TestStartup { public IServiceProvider ConfigureServices(IServiceCollection services) { services.AddSingleton<IInjectedService, InjectedService>(); services.AddTransient(); return services.BuildServiceProvider(); } }

This allows application developers to provide Orleans a service provider to use. However, there is, to my knowledge, not currently a way for application logic to actually use this provider. It is marked as internal and accessible only to Orleans code.

As it is, if I want to use the same service provider as Orleans uses, I need to provide my own service provider (as in the test code from above) but do it in a way where I can also return the same provider to my application code. This is peculiarly inconvenient.

1) Is this isolation intended, or just an unfortunate implementation detail of the first pass implementation? 2) If we wanted to expose the Orleans service provider to the application layer, how should this be done? Singleton? Attach to static data like Silo? ...?

gabikliot commented 8 years ago

Cc @attilah

attilah commented 8 years ago

I owe the community a document page, but meanwhile the unit test for the DI would be a great starting point.

Since Orleans provides a precompiled host and usually you get a chance to add your own logic via Bootstrap providers, DI is implemented similar.

You can define a Startup type in the configuration file like:

    <Startup Type="UnitTests.General.TestStartup,Tester" />

We're utilizing Asp.Net vNext DI implementation, so their rules are our rules.

In this startup type you must define a ConfigureServices method, where you can register your own grains, services, etc. which will be later injected into your grains' ctor when you're using it.

Here is the Startup class from the test:

    public class TestStartup
    {
        public IServiceProvider ConfigureServices(IServiceCollection services)
        {
            services.AddSingleton<IInjectedService, InjectedService>();

            services.AddTransient<SimpleDIGrain>();

            return services.BuildServiceProvider();
        }
    }

If you are like me and like to use another DI container like Autofac (my preference), in this method you can configure that container and return the provider built by the Autofac adapter.

I hope this helps!

jason-bragg commented 8 years ago

Thanks @attilah, but I'm familiar with how to configure the service provider, my question was in regards to getting access to it at the application layer. The IServiceProvider created the ConfigureServices call is stored in an internal property Silo.CurrentSilo.Services. This property being internal means there is no way for the application layer (my grains, for example) to get access to it.

In my service provider builder, I can place the service provider into my own singleton, so I can access it in the grain, but that is quite hacky. This kind of kludge should not be necessary.

I would like the service provider created for Orleans to be available to the application layer, so it can be used to get access to other injected dependencies.

attilah commented 8 years ago

Can you please describe the use case for it? And why not ctor or property injection of services are suffice?

Imho using the serviceprovider in code without a common service locator is an antiattern.

jason-bragg commented 8 years ago

@attilah The particular instance where I've encountered this is that I'm writing a stream provider with dependencies I'd like to inject. There are a number of ways I can achieve this, but my preference is to use Orleans's service provider, but as I previously mentioned, it is internal.

From what you've put forward so far, I'm guessing the approach you'd prefer would be to have stream providers be constructed using the ServiceProvider, that way the service provider can inject itself at construction time or in properties for stream providers that need access to it. Is this correct?

jason-bragg commented 8 years ago

Tinkering with this more, I've encountered some difficulties using the current DI infrastructure. To use the common service locator in any grains, providers, or any other constructed classes, the constructed class 'should' have the ServiceProvider injected into it at construction time. To do this requires the following: 1) The Orleans infrastructure must use the service provider to create the object 2) The service provider must be able to create the class of interest. 3) The service provider must support constructor or property injection.

1 Can be addressed buy updating Orleans code to use the service provider to create other classes of interest. 2 and 3 are the real problem. Using the default service provider, we get 2, but not 3. Since Orleans infrastructure must work with the default service provider, we can't use property or constructor injection without providing a custom service provider. If we fix this, by replacing the default service provider with one that can use property and constructor injection, we then need a way for it to know about application layer grains.

I think we need a public way to register classes (grains at least) in the IServiceCollection prior to creating the service provider, and the default service provider needs to support constructor injection.

With this we can properly use dependency injection in grain, provider, and or any other Orleans created components.

Thoughts?

If there is general agreement on this, I'll open a feature request issue for this and close out this thread.

attilah commented 8 years ago

Extending Orleans usage into more parts of the framework itself to instantiate and register the logging, plugin and provider classes for example is on my personal roadmap. That is what will enable the broad usage is DI for application code, which is currently limited to services and grains. Beside grains, no other Orleans constructs can leverage the DI capabilities. For first we'd like to have a solid ground for DI within Orleans, and now that we've that we can enhance it.

jason-bragg commented 8 years ago

Understood. To see where some of these questions were coming from, please check out PR #972 Specifically GeneratorAdapterFactory.