gradle icon indicating copy to clipboard operation
gradle copied to clipboard

Introduce lazy injection of internal services

Open alllex opened this issue 1 year ago • 7 comments

Allows the declaration of service dependencies without initializing services until they are actually needed.

This can be desirable from the architectural point of view: the services required as dependencies may not be ready to be consumed at the injection time. It can also be used as a performance optimization to avoid the initialization of services that are not used in all scenarios.

Wrapping the dependency service type with a new LazyService<T> interface is all that is required to defer service initialization:

@Provides
protected MyService createMyService(
    LazyService<ExpensiveService> lazyService
) {
    // sometime later in the implementation
    lazyService.getInstance();
}

In Kotlin, leveraging the delegated properties, it's possible to consumer the lazy service transparently:

class MyService(
    store: LazyService<ConfigurationCacheStateStore>
) {

    // the first call to the getter will initialize the backing service
    val store: ConfigurationCacheStateStore by store

}

This capability is present in other DI frameworks, such as Lazy in Dagger.


This PR also demonstrates the new functionality on the DefaultConfigurationCache, by making it avoid manual management of dependent services for state storage and removing imperative late bindings between the services.

alllex avatar Jul 05 '24 23:07 alllex

@bot-gradle test on linux without pts

alllex avatar Jul 23 '24 23:07 alllex

I've triggered the following builds with parameters: -DenablePredictiveTestSelection=false for you. Click here to see all build failures.

bot-gradle avatar Jul 23 '24 23:07 bot-gradle

@bot-gradle test without pts

alllex avatar Jul 24 '24 08:07 alllex

I've triggered the following builds with parameters: -DenablePredictiveTestSelection=false for you. Click here to see all build failures.

bot-gradle avatar Jul 24 '24 08:07 bot-gradle

I'm wondering what problem are we fixing here exactly. Expensive services being injected but then going unused sounds like a code smell to me. Are we paving over some architectural problems perhaps with this?

lptr avatar Jul 25 '24 08:07 lptr

I'm wondering what problem are we fixing here exactly.

Here we are solving the problem of having to manually manage the lifecycle of services that cannot be instantiated at the dependency injection time. With LazyService in our vocabulary we can express the dependency declaratively via constructor without changing the runtime behavior.

Expensive services being injected but then going unused sounds like a code smell to me. Are we paving over some architectural problems perhaps with this?

The part in the PR description about the expensive services is something we could use LazyService for in the future. No changes in this PR makes use of that possibility.

alllex avatar Jul 25 '24 09:07 alllex

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. If you don't want the stale bot to close it, then set a milestone for it.

github-actions[bot] avatar Aug 24 '24 09:08 github-actions[bot]

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. If you don't want the stale bot to close it, then set a milestone for it.

github-actions[bot] avatar Sep 25 '24 09:09 github-actions[bot]

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. If you don't want the stale bot to close it, then set a milestone for it.

github-actions[bot] avatar Nov 02 '24 19:11 github-actions[bot]

the services required as dependencies may not be ready to be consumed at the injection time

This feels wrong. It sounds like we are paving over some problem with added complexity, instead of solving it. What is the problem we cannot resolve with the existing tools?

There are multiple issues with the introduction of lazy services, but the biggest one is that it introduces the concept of an "unready" service. In the current codebase there is a more-or-less clear requirement that dependencies of a service should be initialized before being injected; this makes reasoning about the state of services relatively straightforward. If we allow injecting non-ready services, that adds a ton of complexity and "flimsiness" (for want of a better word) to the process of service initialization, making our service infrastructure significantly harder to comprehend.

We should be going in the opposite direction, and making things more explicit and simple to reason about. Let's fight the urge for a quick-and-dirty solution, dig deeper and understand better what the problem is, and then fix it.

lptr avatar Nov 04 '24 10:11 lptr

This pull request has been automatically marked as stale because it has not had recent activity. It will be closed in 14 days if no further activity occurs. If you don't want the stale bot to close it, then set a milestone for it.

github-actions[bot] avatar Dec 04 '24 17:12 github-actions[bot]

This pull request has been automatically closed due to inactivity.

github-actions[bot] avatar Dec 18 '24 18:12 github-actions[bot]