Description
Azure Functions Java supports dependency injection by implementing com.microsoft.azure.functions.spi.inject.FunctionInstanceInjector
and registering the implementation via SPI. However, when the DI container creates resources that need to be closed (via the Closeable
interface), those resources will never have their close
method called, as Azure Functions and the FunctionInstanceInjector
interface provide no way of doing so. Similarly, there does not appear to be any way of closing resources on a graceful shutdown of the Azure Functions runtime/host (See #113, #729).
Official documentation states:
To avoid holding more connections than necessary, reuse client instances rather than creating new ones with each function invocation. We recommend reusing client connections for any language that you might write your function in.
And:
Do not create a new client with every function invocation.
Great - I would like to have my DI container hold a singleton for various clients (e.g., a Ktor HttpClient
, or a database client that uses a HikariCP connection pool) and then have function invocations retrieve those clients from the container. Unfortunately, it does not currently seem possible to ensure these resources close properly.
Proposed Solution
Update FunctionInstanceInjector
to implement Closeable
, and update the Azure Functions runtime to call the close
method when the runtime/host shuts down. The AF runtime is currently responsible for creating an instance of FunctionInstanceInjector
, so it feels appropriate that it should be responsible for disposing of it.
Here's an example of what I'd like to be able to do (in Kotlin, using the Koin DI library):
abstract class KoinFunctionInstanceInjector(
appDeclaration: KoinApplication.() -> Unit,
) : FunctionInstanceInjector {
private val application = koinApplication(appDeclaration)
override fun <T : Any> getInstance(clazz: Class<T>): T = application.koin.get(clazz.kotlin)
// Currently a compilation error, because `FunctionInstanceInjector` does not implement `Closeable`
override fun close() = application.close()
}
Then define a concrete class like:
class MyFunctionInstanceInjector :
KoinFunctionInstanceInjector({
modules(
module {
single { OkHttp.create() } onClose { it?.close() } bind HttpClientEngine::class
single { HikariDataSource(/*...*/) } onClose { it?.close() } bind DataSource::class
// ...
},
)
})
And registering MyFunctionInstanceInjector
via SPI. Calling application.close
in KoinFunctionInstanceInjector
ends up calling the onClose
lambda for any/all instance definitions in the Koin module, allowing the HttpClientEngine
and HikariDataSource
instances to be closed properly.
The only thing missing is ensuring that the MyFunctionInstanceInjector
instance that AF creates via SPI will have its close
method called.