Hi Lukas,

OK, here is the API challenge of the day.
This is to sum up my views on Executor and Configuration and hopefully also
make users of the library react! :)

I believe there are several cases for Executor creation:
- Create an Executor with ad-hoc parameters.
- Create an Executor with a Configuration.
- And eventually: create an Executor with a Configuration, overridden with
ad-hoc parameters.

I am mainly focusing on the 2nd/3rd cases: creating an Executor with a
Configuration.

When someone uses a Configuration, I believe that the purpose is to not
repeat parameters, to have the same parameters for all Executors. In some
cases, which I think are limited, someone could have more than one
Configuration, but would still use those as shared parameters for sets of
Executors. The purpose of this e-mail is to suggest strengthening this
notion of Configuration as a global shareable set of parameters.


In my view, because a Configuration shares parameters, it should by nature
not share object instances that have a state that depends on execution.
Otherwise, 2 calls from different threads could fail if they happen at the
same time; 2 sequencial queries where the first one lazily fetches the
results could also fail.
The best a Configuration should do in this area is to provide a factory of
such execution-dependant objects. Actual instances should be set on the
Executor directly.

The main objects that may have a state which depends on execution currently
are:
- ExecuteListener: an actual instance is plugged to the Configuration
object. Storing state directly in that class is very dangerous and the
workaround is to use the data map of the ExecuteContext to store and
retrieve execution-related state. Failing to do so may work until 2 threads
call it at the same time, etc.
- ConnectionProvider: the specification is very loose on how to implement
it, which means that the same connection could be acquired by 2 threads
using the same configuration.

Because of the current structure of the API, it is easy to misuse and to
share a configuration object which eventually shares stateful execute
listeners and connections (through connection providers). It would
eventually blow up randomly depending on threading or query execution flows
(preferably once deployed in production) and would of course be very hard
to debug.


I think ExecuteListener and ConnectionProvider should be replaced so that
the user can:
- Set an actual instance of X (see below) on an Executor instance.
- Set a factory of X in the Configuration, which can be used by many
Executor instances.


About ExecuteListener, the user could either:

1. Define an ad-hoc ExecuteListener instance:
Executor executor = new Executor(configuration);
executor.getExecuteListeners().add(new MyExecuteListener());

2. Define a global ExecuteListener factory:
configuration = new Configuration();
configuration.getExecuteListenerFactories().add(new
ExecuteListenerFactory() {
  public ExecuteListener createExecuteListener() {
    return new MyExecuteListener();
  }
});
// Everywhere the configuration is used, then:
Executor executor = new Executor(configuration);

A few notes:
- If per-execution state is needed, the factory would just create a new
instance, which is the general case. Of course, the factory can supply the
same instance of the ExecuteListener if it wishes so, but then it is not a
factory anymore and as such the implementor will have to create its
ExecuteListener subclass with thread safety in mind. Such cases are for
example for aggregation of statistics over multiple queries and threads,
and is by nature required to be coded in a thread safe way.
- If using a factory seems like clutter, keep in mind that the
configuration is to define shared parameters and is not done at many places
in the code. Moreover, with Lambda in Java 8 this turns into a one liner.


About the ConnectionProvider, the user could either:

1. Define an ad-hoc connection instance:
Executor executor = new Executor(conn, sqlDialect);

2. Define a global factory:
configuration = new Configuration();
configuration.setConnectionProvider(new MyPoolBasedConnectionProvider());
// Everywhere the configuration is used, then:
Executor executor = new Executor(configuration);

A few notes:
- The ConnectionProvider would not be loose in its meaning as it is
currently and it would specify that every connection that is obtained
through acquire() cannot be returned twice until it is returned with
release(conn). This way, the connection provider can be used by concurrent
queries.
- A side effect, but not the main motivation, is that the
ConnectionProvider would be a safe place to acquire/release dedicated
connections. 3rd party ExecuteListener or whatever tools could make use of
such capability.
- Maybe we should also have signatures like: "new Executor(Configuration,
conn)". The general idea would be that the configuration is used, but the
executor can override some aspects of it like what I did with the
ExecuteListener above. In fact, maybe we should have an API so that user
can call "new Executor(Configuration)" and then call
"executor.setConnection(conn)" if so they wish.
- Because there is this idea that the Configuration is shared, when an
Executor is instanciated it would create an internal copy to merge
overridden aspects (connection, execute listeners, even rendering
capabilities that could be accessed through executor.getConfiguration()).


I strongly believe that it would make the API easier to understand (global
vs ad-hoc) and would reduce the chances of misusing it. Please let me know
if my thoughts are completely off-track or if such API would be beneficial
to jOOQ.

-Christopher

-- 
You received this message because you are subscribed to the Google Groups "jOOQ 
User Group" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/groups/opt_out.


Reply via email to