The letter 'D' in SOLID stands for
Dependency inversion principle. The principle states,
- High-level modules should not depend on low-level modules. Both should depend on abstractions.
- Abstractions should not depend on details. Details should depend on abstractions. Wikipedia
Newing up instances cause strict coupling between code modules. To keep them decoupled from each other, we follow the 'Dependency Inversion Principle'. In this way the modules are not dependent on each other's concrete implementation rather they are dependent upon abstractions e.g. interfaces.
An abstraction can have many many implementations. So, whenever we encounter an abstraction, there should be some way of passing a specific implementation to that. A class is held responsible for this kind of servings and it should be configured in such a way so. We call it a dependency injection container.
ASP.Net Core has a built-in dependency injection container. It's simple and can serve our purpose very well. Not only it can be configured to serve implementations to abstractions but it also can control the lifetime of the created instances.
Instead of using the concrete
DocumentExecutor, we can use their abstractions i.e.
DocumentExecutor. And for this purpose we have to configure the built-in dependency container as follows,
They both have
Singleton lifetimes since we want to create instances of them only once and use it for the rest of the application's lifecycle.
GameStoreQuery, we don't have any abstraction. We will just use the raw implementation but with a
Transient lifetime so that instance on the type is created per request,
The schema contains the
query and later in the series it will also have
mutation and subscription. We better make a separate class for it. The class is extended from the
Schema type from
GameStoreSchema is made constructor injectable so that we can inject and pass down
IServiceProvider to the base type i.e.
IServiceProvider.GetRequiredService<T>() is an utility function available in
GraphQL.Utilities that uses
Activator.CreateInstance() to create an instance of the generic type.
Finally, it's time to configure the
GameStoreSchema in the
ConfigureServices method as following,
ISchema is coming from the
GraphQL.Types as well.
Now, we can shift the middleware code to its middleware class (special type having a `Invoke/InvokeAsync(HttpContext httpContext)` method. Following is the middleware class named
Notice, how we replace all the concrete type initializations with abstractions and make our code loosely coupled. Every dependency injectable service is injected via the constructor (constructor injection) at this moment. A middleware sits in the request pipeline and have a
Singleton lifetime. But it doesn't mean that we can't work with services with
Scoped lifetime. That's where
Invoke/InvokeAsync method comes into play where we can inject services of
Scoped lifetime. To know more about DI in ASP.NET Core middleware, refer to this link.
The last but not least, we must attach the middleware in the application startup pipeline.
IApplicationBuilder has an extension method called
UseMiddleware which is used to attach middleware classes. So, the final look of the
Configure method is as follows,
This next section is all about refactoring and making the
GraphQLMiddleware more configurable.
AddGraphQL is an extension method that takes various options like a configurable service endpoint. The type of the object we are using to pass the options is named
GraphQLOptions. We can pass options from the
ConfigureServices() as follows,
For the time being,
GraphQLOptions only have a single property called
To use the configuration options in the
GraphQLMiddleware, we inject the constructor with
UseGraphQL is another one that is just a wrapper around
app.UseMiddleware() to make it look more meaningful like