John Murph wrote:
I have some code for this at git://github.com/sappling/gradle.git <http://github.com/sappling/gradle.git> in branch listener_manager. I'm not sure if we should integrate this as is, or finish changing over all the other existing listeners to this scheme first. Next week I will try to get a TestListener submitted that can listen to JUnit (and testng, hopefully) tests. For that purpose, this code also contains generic listener remoting support in package org.gradle.listener.remote.

Please take a look at it give me some feedback. I'm especially concerned if the remoting support seems like the right approach (and suggestions for error handling in this code would be nice too).


Some comments:

- Generally, I think this is the right direction to take. However, I think it's a little invasive at the moment. More on this below.

- I don't think we should use the Listener marker interface. Requiring it means, for example, we can't use 3rd party listener interfaces. It's just unnecessary coupling. We should be able to broadcast on any interface which has only void methods.

- We should leave the strongly typed addNnnListener() methods, rather than replace them with a single addListener(). We should add addListener() as a convenience only.

Having a single method means we lose the static typing and documentation goodness. It also means we can't have different event streams which use the same interface, such as, GradleLauncher.addStandardOutputListener() and addStandardErrorListener().

I think the only classes which should have an addListener() method are Gradle and GradleLauncher. These are the only places where adding a global listener of a given type make sense.

- I think the changes to LoggingConfigurer are a good example of how the approach is a little too invasive. To me, ListenerManager and LoggingConfigurer are services, and it's always a smell (to me) when a service is passed into another service via the service interface methods. Why should I need a ListenerManager to configure logging? It feels like ListenerManager is leaking into places it doesn't really belong.

Some alternative approaches:

1. Inject the ListenerManager into the DefaultLoggingConfigurer, and leave the addNnnListener() methods on LoggingConfigurer. DefaultLoggingConfigurer uses the ListenerManager to create 2 broadcasters with type StandardOutputListener. That is, it creates 2 event streams. One stream is for standard out, the other for standard error.

2. Move the above logic out of DefaultLoggingConfigurer, and inject the 2 broadcasters into DefaultLoggingConfigurer. Remove the addNnnListener() methods. This way, DefaultLoggingConfigurer know nothing of ListenerManagers. It just knows it needs to emit std out and std err streams.

interface LoggingConfigurer {
   void configure(LogLevel level)
}

class DefaultLoggingConfigurer {
public DefaultLoggingConfigurer(StandardOutputListener stdoutBroadcaster, StandardOutputListener stderrBroadcaster) {
      ...
  }

  public void configure(LogLevel level) {
     ... attaches the broadcasters to the appropriate appenders ...
  }
}

I think I like option 2.


- DefaultInternalRepository should not register itself. In general, I think it's better for a service to simply declare that it is a listener of some type and have the environment take care of the registering, rather than for the service to know how to register itself. This is just a variation of inversion of control.

- Remote approach looks good, but I would hold off going too far down that path until the native test changes are merged in, because it does pretty much the same stuff.


Adam

Reply via email to