Koin custom lifetime of service - kotlin

I am trying to setup/extend Koin so that it respects the lifetime of a Ktor request.
I would like to inject a new service instance per request and share it throughout, but destroy it at request end.
Has anyone already tried this? Is it possible to extend Koin to handle this scenario or should I just build something on my own?
Ideally I'm looking to create something like this:
single<CoolService> { CoolServiceImpl() }
factory<CoolerService> { CoolerServiceImpl() }
request<CoolestService> { CoolestServiceImpl() } // THIS

Related

How to get connection join/left event in ktor

I want to implement the record of adding client connections in ktor's websocket, but I can't find the corresponding document. Is there a way to achieve this?
like this
install(websockets)
...
routing {
onOpen(session){
...
}
}

Register dependent services on every request

I am working in Multi-tenant solution primarily there are 2 type of applications
WebAPI
Console app to process message from queue
I have implemented dependency injection to inject all services. I have crated TenantContext class where I am resolving tenant information from HTTP header and it's working fine for API, but console application getting tenant information with every message (tenant info is part of queue message) so I am calling dependency injection register method on every incoming message which is not correct, do you have any suggestion/solution here?
The way I am resolving ITenantContext in API
services.AddScoped<ITenantContext>(serviceProvider =>
{
//Get Tenant from JWT token
if (string.IsNullOrWhiteSpace(tenantId))
{
//1. Get HttpAccessor and processor settings
var httpContextAccessor =
serviceProvider.GetRequiredService<IHttpContextAccessor>();
//2. Get tenant information (temporary code, we will get token from JWT)
tenantId = httpContextAccessor?.HttpContext?.Request.Headers["tenant"]
.FirstOrDefault();
if (string.IsNullOrWhiteSpace(tenantId))
//throw bad request for api
throw new Exception($"Request header tenant is missing");
}
var tenantSettings =
serviceProvider.GetRequiredService<IOptionsMonitor<TenantSettings>>();
return new TenantContext(tenantId, tenantSettings );
});
Create two different ITenantContext implementations. One for your Web API, and one for your Console application.
Your Web API implementation than might look as follows:
public class WebApiTenantContext : ITenantContext
{
private readonly IHttpContextAccessor accessor;
private readonly IOptionsMonitor<TenantSettings> settings;
public WebApiTenantContext(
IHttpContextAccessor accessor,
IOptionsMonitor<TenantSettings> settings)
{
// Notice how the dependencies are not used in this ctor; this is a best
// practice. For more information about this, see Mark's blog:
// https://blog.ploeh.dk/2011/03/03/InjectionConstructorsshouldbesimple/
this.accessor = accessor;
this.settings = settings;
}
// This property searches for the header each time its called. If needed,
// it can be optimized by using some caching, e.g. using Lazy<string>.
public string TenantId =>
this.accessor.HttpContext?.Request.Headers["tenant"].FirstOrDefault()
?? throw new Exception($"Request header tenant is missing");
}
Notice that this implementation might be a bit naive for your purposes, but hopefully you'll get the idea.
This class can be registered in the Composition Root of the Web API project as follows:
services.AddScoped<ITenantContext, WebApiTenantContext>();
Because the WebApiTenantContext has all its dependencies defined in the constructor, you can do a simple mapping between the ITenantContext abstraction and the WebApiTenantContext implementation.
For the Console application, however, you need a very different approach. The WebApiTenantContext, as shown above, is currently stateless. It is able to pull in the required data (i.e. TenantId) from its dependencies. This probably won't work for your Console application. In that case, you will likely need to manually wrap the execution of each message from the queue in a IServiceScope and initialize the ConsoleTenantContext at the beginning of that request. In that case, the ConsoleTenantContext would look merely as follows:
public class ConsoleTenantContext : ITentantContext
{
public string TenantId { get; set; }
}
Somewhere in the Console application's Composition Root, you will have to pull messages from the queue (logic that you likely already have), and that's the point where you do something as follows:
var envelope = PullInFromQueue();
using (var scope = this.serviceProvider.CreateScope())
{
// Initialize the tenant context
var context = scope.ServiceProvider.GetRequiredService<ConsoleTenantContext>();
content.TenantId = envelope.TenantId;
// Forward the call to the message handler
var handler = scope.ServiceProvider.GetRequiredService<IMessageHandler>();
handler.Handle(envelope.Message);
}
The Console application's Composition Root will how have the following registrations:
services.AddScoped<ConsoleTenantContext>();
services.AddScoped<ITenentContext>(
c => c.GetRequiredServices<ConsoleTenantContext>());
With the registrations above, you register the ConsoleTenantContext as scoped. This is needed, because the previous message infrastructure needs to pull in ConsoleTenantContext explicitly to configure it. But the rest of the application will depend instead on ITenantContext, which is why it needs to be registered as well. That registration just forwards itself to the registered ConsoleTenantContext to ensure that both registrations lead to the same instance within a single scope. This wouldn't work when there would be two instances.
Note that you could use the same approach for Web API as demonstrated here for the Console application, but in practice it's harder to intervene in the request lifecycle of Web API compared to doing that with your Console application, where you are in full control. That's why using an ITenantContext implementation that is itself responsible of retrieving the right values is in this case an easier solution for a Web API, compared to the ITenantContext that is initialized from the outside.
What you saw here was a demonstration of different composition models that you can use while configuring your application. I wrote extensively about this in my series on DI Composition Models on my blog.

How does one consume Bugsnag from singleton?

I have an ASP.NET core application that implements a singleton service.
I would like errors to be sent to Bugsnag so I've added IClient bugsnag to my constructor but am getting the following error during startup:
Cannot consume scoped service 'Bugsnag.IClient' from singleton
I cannot find anything in the Bugsnag docs that mentions IClient being scoped or how to construct a singleton instance to use in my application.
As mentioned in the comments, a possible solution would be to use IServiceScopeFactory to create a scope to use in the singleton. This is not ideal because the whole reason for using Bugsnag is to have something that catches all unhandled errors in the application and reports them to a central point for monitoring.
UPDATE: since posting the question I came across a GitHub issue addressing this problem.
In short, you couldn't register the service as singleton , since the IClient has been registered as scoped but it called in the singleton service.
When we have a scoped instance, each time we load the page, a new instance of our ChildService is created and inserted in the parent service.
Whereas when we do a singleton, it keeps the exact same instance (Including the same child services). When we make the parent service a singleton, that means that the child service is unable to be created per page load.
ASP.NET Core is essentially stopping us from falling in this trap of thinking that a child service would be created per page request, when in reality if the parent is a singleton it’s unable to be done. This is why the exception is thrown.
If you want to use iclient in the singleton service, it's impossible. The right way is make that singleton service to scoped.
Update:
As #JHBonarius says, we could inject the IServiceScopeFactory to the singleton sercive and manage the scope service.
More details, you could refer to below codes:
public class Singleton : ISingleton
{
private readonly IServiceScopeFactory scopeFactory;
public Singleton(IServiceScopeFactory scopeFactory)
{
this.scopeFactory = scopeFactory;
}
public void MyMethod()
{
using(var scope = scopeFactory.CreateScope())
{
var db = scope.ServiceProvider.GetRequiredService<yourservice>();
// when we exit the using block,
// the IServiceScope will dispose itself
// and dispose all of the services that it resolved.
}
}
}

How to make request-bound data globally available in Ktor?

I'm building a microservice in Ktor. While we are working within the application module, Ktor provides access to the call object which contains authorization data.
My problem is that I need to access the call object from a service-level class. In Spring, you would do this by accessing the SecurityContext which is globally available via a ThreadLocal. Ktor, being coroutine-driven, does not have that option.
Do I really need to pass down the call object through my service layer methods, or is there a way in Ktor to have some sort of "call context" object that you can access from anywhere?
I think there is no build-in official opportunity at the moment.
I have written a Ktor-Feature, that implements that behaviour.
The answer is to complicated to describe it in a few words.
Check out the code + samples, if you are interested. Also an install guide is provided.
=> https://github.com/MaaxGr/ktor-globalcalldata
Here a short snippet, what my dsl looks like:
Call suspending function test() directly or indirectly from a route:
routing {
get("/test") {
test()
call.respond("OK")
}
}
Access call object via callData().call
suspend fun test() {
val url = callData().call.request.uri
println(url) // prints "/test"
}
The library also allows to add custom properties that can be bound to the current coroutine/call.

ServiceStack AuthProvider IsAuthorized is not called when calling service from ASP.NET code behind

I've a service operation which I marked with the Authenticate attribute
[Authenticate]
[Route("/route/to/service", "POST")]
public class OperationA: IReturn<OperationAResponse>
{
...
}
The method IsAuthorized of the AuthProvider is called correctly when I call the service using the REST URL or using JsonServiceClient inside a unit test but is not called if I call the service from ASP.NET code behind (not MVC controller).
I don't use IoC to resolve the service inside my code behind but I use this code...
MyService service = AppHostBase.Instance.Container.TryResolve<MyService>();
service.Post(operationA);
Is there something I'm missing?
Thank you for your attention.
Just to clarify:
I don't use IoC to resolve the service inside my code behind but I use this code...
MyService service = AppHostBase.Instance.Container.TryResolve<MyService>();
You are using the IOC here, i.e. resolving an auto-wired instance of MyService from ServiceStack's IOC.
If you're service doesn't make use of the HTTP Request or Response objects than you can treat it like any normal class and call C# methods. If the service does (e.g. Auth/Registration) then you will also need to inject the current HTTP Request Context as well.
The CustomAuthenticationMvc UseCase project has an example of how to do this:
var helloService = AppHostBase.Resolve<HelloService>();
helloService.RequestContext = System.Web.HttpContext.Current.ToRequestContext();
var response = (HelloResponse)helloService.Any(new Hello { Name = "World" });