injecting dependencies into task instances

classic Classic list List threaded Threaded
5 messages Options
Reply | Threaded
Open this post in threaded view
|

injecting dependencies into task instances

Adam Murdoch
Hi,

I recently made a change to add a new mechanism for injecting dependencies into tasks. Previously, to inject services into a task instance we were using either:

1. getServices().get(Type) or
2. constructor injection.

One downside with #1 is that the approach not declarative, so we can’t figure out statically which services a task is going to use, or when. This is important in order for us to determine things like the possible inputs and outputs of the task, or to do any early validation, or deal with services that require some work to make usable, and so on.

Approach #2 addresses this issue (mostly), but has a couple of downsides of its own. Firstly, for good or bad, we currently make the task types public and allow them to be subclassed. This means that the implementation services are exposed to subtypes and we can’t change the set of services without changing the constructor - thereby breaking backwards compatibility.

Secondly, approach #2 requires that all the services be created when the task is created, regardless of whether the task or service is ever required. The services are also retained for the entire life of the task object. This has a noticeable impact on performance and heap usage in particular. This was the main motivation for the change.

The ‘proper’ solution here is to separate out the configuration and the implementation pieces of a task, and defer creation of the implementation stuff, and the services it needs, until execution time. This will be part of the solution to allow tasks from a given project to execute in parallel.

In the meantime, and for legacy tasks, there is a new mechanism to inject services that addresses the downsides of #1 and #2 above. To use this, define a getter annotated with @Inject:

@Inject
protected MyService getMyService() { … doesn’t matter what goes in here ... }

When decorated, this method is replaced with a service lookup. The lookup is lazy, so that the service is not created until the getter is called.

This mechanism is considered incubating at this stage. It only works for tasks for now, but would probably be useful for all decorated types (and plugins, which aren’t decorated yet).


--
Adam Murdoch
Gradle Co-founder
http://www.gradle.org
VP of Engineering, Gradleware Inc. - Gradle Training, Support, Consulting
http://www.gradleware.com

Join us for Gradle Summit 2014, June 12th and 13th in Santa Clara, CA: http://www.gradlesummit.com

Reply | Threaded
Open this post in threaded view
|

Re: injecting dependencies into task instances

Perryn Fowler
How would one inject doubles of the services when testing?


On Tue, May 6, 2014 at 9:59 AM, Adam Murdoch <[hidden email]> wrote:
Hi,

I recently made a change to add a new mechanism for injecting dependencies into tasks. Previously, to inject services into a task instance we were using either:

1. getServices().get(Type) or
2. constructor injection.

One downside with #1 is that the approach not declarative, so we can’t figure out statically which services a task is going to use, or when. This is important in order for us to determine things like the possible inputs and outputs of the task, or to do any early validation, or deal with services that require some work to make usable, and so on.

Approach #2 addresses this issue (mostly), but has a couple of downsides of its own. Firstly, for good or bad, we currently make the task types public and allow them to be subclassed. This means that the implementation services are exposed to subtypes and we can’t change the set of services without changing the constructor - thereby breaking backwards compatibility.

Secondly, approach #2 requires that all the services be created when the task is created, regardless of whether the task or service is ever required. The services are also retained for the entire life of the task object. This has a noticeable impact on performance and heap usage in particular. This was the main motivation for the change.

The ‘proper’ solution here is to separate out the configuration and the implementation pieces of a task, and defer creation of the implementation stuff, and the services it needs, until execution time. This will be part of the solution to allow tasks from a given project to execute in parallel.

In the meantime, and for legacy tasks, there is a new mechanism to inject services that addresses the downsides of #1 and #2 above. To use this, define a getter annotated with @Inject:

@Inject
protected MyService getMyService() { … doesn’t matter what goes in here ... }

When decorated, this method is replaced with a service lookup. The lookup is lazy, so that the service is not created until the getter is called.

This mechanism is considered incubating at this stage. It only works for tasks for now, but would probably be useful for all decorated types (and plugins, which aren’t decorated yet).


--
Adam Murdoch
Gradle Co-founder
http://www.gradle.org
VP of Engineering, Gradleware Inc. - Gradle Training, Support, Consulting
http://www.gradleware.com

Join us for Gradle Summit 2014, June 12th and 13th in Santa Clara, CA: http://www.gradlesummit.com


Reply | Threaded
Open this post in threaded view
|

Re: injecting dependencies into task instances

Luke Daley-2
You can’t right now.

On 8 May 2014 at 10:00:49 am, Perryn Fowler ([hidden email]) wrote:

How would one inject doubles of the services when testing?


On Tue, May 6, 2014 at 9:59 AM, Adam Murdoch <[hidden email]> wrote:
Hi,

I recently made a change to add a new mechanism for injecting dependencies into tasks. Previously, to inject services into a task instance we were using either:

1. getServices().get(Type) or
2. constructor injection.

One downside with #1 is that the approach not declarative, so we can’t figure out statically which services a task is going to use, or when. This is important in order for us to determine things like the possible inputs and outputs of the task, or to do any early validation, or deal with services that require some work to make usable, and so on.

Approach #2 addresses this issue (mostly), but has a couple of downsides of its own. Firstly, for good or bad, we currently make the task types public and allow them to be subclassed. This means that the implementation services are exposed to subtypes and we can’t change the set of services without changing the constructor - thereby breaking backwards compatibility.

Secondly, approach #2 requires that all the services be created when the task is created, regardless of whether the task or service is ever required. The services are also retained for the entire life of the task object. This has a noticeable impact on performance and heap usage in particular. This was the main motivation for the change.

The ‘proper’ solution here is to separate out the configuration and the implementation pieces of a task, and defer creation of the implementation stuff, and the services it needs, until execution time. This will be part of the solution to allow tasks from a given project to execute in parallel.

In the meantime, and for legacy tasks, there is a new mechanism to inject services that addresses the downsides of #1 and #2 above. To use this, define a getter annotated with @Inject:

@Inject
protected MyService getMyService() { … doesn’t matter what goes in here ... }

When decorated, this method is replaced with a service lookup. The lookup is lazy, so that the service is not created until the getter is called.

This mechanism is considered incubating at this stage. It only works for tasks for now, but would probably be useful for all decorated types (and plugins, which aren’t decorated yet).


--
Adam Murdoch
Gradle Co-founder
http://www.gradle.org
VP of Engineering, Gradleware Inc. - Gradle Training, Support, Consulting
http://www.gradleware.com

Join us for Gradle Summit 2014, June 12th and 13th in Santa Clara, CA: http://www.gradlesummit.com


— 

Luke Daley
Gradleware
Join us for Gradle Summit 2014, June 12th and 13th in Santa Clara, CA: http://www.gradlesummit.com
Reply | Threaded
Open this post in threaded view
|

Re: injecting dependencies into task instances

Adam Murdoch
In reply to this post by Perryn Fowler

One would fix TestUtil to take care of this.

On 8 May 2014, at 10:00 am, Perryn Fowler <[hidden email]> wrote:

How would one inject doubles of the services when testing?


On Tue, May 6, 2014 at 9:59 AM, Adam Murdoch <[hidden email]> wrote:
Hi,

I recently made a change to add a new mechanism for injecting dependencies into tasks. Previously, to inject services into a task instance we were using either:

1. getServices().get(Type) or
2. constructor injection.

One downside with #1 is that the approach not declarative, so we can’t figure out statically which services a task is going to use, or when. This is important in order for us to determine things like the possible inputs and outputs of the task, or to do any early validation, or deal with services that require some work to make usable, and so on.

Approach #2 addresses this issue (mostly), but has a couple of downsides of its own. Firstly, for good or bad, we currently make the task types public and allow them to be subclassed. This means that the implementation services are exposed to subtypes and we can’t change the set of services without changing the constructor - thereby breaking backwards compatibility.

Secondly, approach #2 requires that all the services be created when the task is created, regardless of whether the task or service is ever required. The services are also retained for the entire life of the task object. This has a noticeable impact on performance and heap usage in particular. This was the main motivation for the change.

The ‘proper’ solution here is to separate out the configuration and the implementation pieces of a task, and defer creation of the implementation stuff, and the services it needs, until execution time. This will be part of the solution to allow tasks from a given project to execute in parallel.

In the meantime, and for legacy tasks, there is a new mechanism to inject services that addresses the downsides of #1 and #2 above. To use this, define a getter annotated with @Inject:

@Inject
protected MyService getMyService() { … doesn’t matter what goes in here ... }

When decorated, this method is replaced with a service lookup. The lookup is lazy, so that the service is not created until the getter is called.

This mechanism is considered incubating at this stage. It only works for tasks for now, but would probably be useful for all decorated types (and plugins, which aren’t decorated yet).


--
Adam Murdoch
Gradle Co-founder
http://www.gradle.org
VP of Engineering, Gradleware Inc. - Gradle Training, Support, Consulting
http://www.gradleware.com

Join us for Gradle Summit 2014, June 12th and 13th in Santa Clara, CA: http://www.gradlesummit.com




--
Adam Murdoch
Gradle Co-founder
http://www.gradle.org
VP of Engineering, Gradleware Inc. - Gradle Training, Support, Consulting
http://www.gradleware.com

Join us for Gradle Summit 2014, June 12th and 13th in Santa Clara, CA: http://www.gradlesummit.com

Reply | Threaded
Open this post in threaded view
|

Re: injecting dependencies into task instances

jkschneider
This post has NOT been accepted by the mailing list yet.
In reply to this post by Adam Murdoch
Feels like this should be documented in Chapter 39: Custom Plugins.  Wasted an awful lot of time tracking this down today.

https://docs.gradle.org/current/userguide/custom_plugins.html