Hi,
I'm discovering some interesting implications of dynamic services.
In hibernate-core, building dynamic services causes at least two
services to be realized.
Let us assume that dynamic services are created based upon a
contribution to a service.
For example, dynamic services of hibernate-core are created based upon
contributions to the service HibernateMarkerConfiguration. This service
is injected into the dynamic contributor method (parameter injection)
The use of a HibernateMarkerConfiguration service could be considered
bending the semantics of a "service" a bit, since the service doesn't do
anything apart from providing the markers.
However, there really is only one way right now to contribute the
markers, and that's by contributing them to a service.
The real "problem", however, is not that I'm using a service.
The real problem is that in order to realize the
HibernateMarkerConfiguration service, other services have to be realized
as well, in particular the MasterObjectProvider and ServiceOverride.
This means that after the collectDynamicServices() and
collectDynamicContributions() steps, no dynamic contributions can be
done to MasterObjectProvider and ServiceOverride. Worse, if your
contributions to ServiceOverride depend on HibernateMarkerConfiguration
or something like that, there is a recursion error. Obviously, there's
addInstance() to deal with this, but this will not solve the problem
that no new contributions can be done by dynamic services. In our case
it's even worse, since we have a static contribution to ServiceOverride
which depends on HibernateMarkerConfiguration to figure out whether
there are any markers. Voila, recursion. (See appendix below)
The idea is that dynamic service contributors could use injected
parameters for their building, to increase expressivity.
The implication of this is that a combination of modules can cause
errors, while each individual module works fine. Maybe module X depends
on service S to dynamically create classes, and module Y wants to add
(dynamic) contributions to service S. Independently, these modules can
be in the registry, but their combination will fail, because the service
is realized before the dynamic contribution is done. ((Note: in my
current implementation, this won't even be noticed by the Registry,
because there is no checking whether dynamic contributions are done to
realized services))
Fill in for X, Hibernate-core, for Y, Hibernate, and for S the
ServiceOverride service (Where ServiceOverride elementOf
dependencies(X), due to the injection of HibernateMarkerConfiguration
depending on MasterObjectProvider to be realized which depends on
ServiceOverride to be realized) and we nearly have our case**. It would
even be a problem if X and Y are equal.
There are several ways to cope with the issue.
POSSIBLE SOLUTION 1
A. Allow updating the configuration of services. Several possible
strategies include: rebuilding the service when the configuration is
updated; calling an updateConfiguration() method in the implementation
class. Note that updating the configuration always means adding a new
configuration entry.
B. Generate an error when there is a dynamic contribution to a service
that has already been realized and that is not update-able according to A.
C. Allow injection of objects into Dynamic Service Contributors and
Dynamic Contribution Contributors.
This does not mean every service must be modified to allow
rebuilding/updateConfiguration() calls. By default, services can be "not
update-able". Then, whenever users have clashes, we could modify
services to be update-able. For example, MasterObjectProvider and
ServiceOverride could possibly be made update-able. We need to design
the syntax for this update idea, possibly defining a new annotation to
services or service implementations ("@Updateable", with an enum
Updating.PROHIBIT, Updating.REBUILD, Updating.METHOD; in the last case
exactly one public method in the implementation must have the
@ConfigurationUpdate annotation OR be named updateConfiguration)
POSSIBLE SOLUTION 2
No injection of objects into Dynamic Service Contributors and Dynamic
Contribution Contributors. A different mechanic must be found to
contribute to dynamic services/contributions. Perhaps Dynamic
Services/Contributions could be identified using a "Dynamic id" (which
can be shared by different dynamic contributors) and which other classes
can contribute to.
This will severly limit the options for dynamic service/contribution
contributions, as they may not have injected services (or something like
the ObjectLocator) as parameters.
This means no services will be realized during the dynamic part.
This will not mean that a HibernateMarkerConfiguration service will not
exist; it will still exist because there could be static services that
may want to know which database configurations there are (e.g.
HibernateUtil in my solution).
With solution 1, this might mean more work and maintenance of existing
services, although good test cases for every modified service will make
this process much smoother.
With solution 2, this might severely limit the options for dynamic services.
The use case of our multiple database problem can be generalized to the
problem of services with multiple configurations. Essentially that's
what we have here: 6 services (hibernate-core) that have N
configurations (e.g. Red and Blue in the appendix).
Perhaps there are other use cases that need to be considered in the
context of dynamic services/contributions. An example of this is Spring
integration, which I have not looked into yet. (One problem there is
that discovery of spring objects relies on the ServletContext object,
which set in the application globals by tapestry-core during startup...
but perhaps core could be modified to contribute the ServletContext
object in some other way, so it can be discovered by a dynamic solution
- until that is solved, tapestry-spring has to use a TapestyFilter
subclass).
POSSIBLE SOLUTION 3
Alternatively, instead of this dynamic service jungle, we could consider
only solving the "services with multiple configurations" problem by
using a @MultipleConfigurations("MCid") annotation.
Modules could statically contribute to a MCid using some syntax.
(@ContributeMultipleConfiguration("MCid") Class[] contributeMarkers() {
return new Class[] {Red.class, Blue.class} })
If placed on a builder, this means "for every contributed marker,
generate a service with additional marker; if no markers contributed,
generate a service without additional markers;" and the marker (or null
if no markers defined anywhere) would be injected into the build method.
If placed on a contributor, this means exactly the same.
Alternatively, contributors may also have the parameter
("MultipleConfiguration") (with subclasses MultipleMappedConfiguration,
MultipleOrderedConfiguration....) which exposes the Class[] array of
markers.
We would have to consider what to do with the case where no markers are
contributed to a MultipleConfiguration. In the solution I just
described, it would fall back to generating a single service. However,
maybe one would always want to create the "no markers" service, using
ServiceOverride to set the default service implementation to the "null"
service.... e.g. with multiple database, if {Red,Blue} are contributed,
create Session, @Red SessionRed and @Blue SessionBlue; if no markers are
defined, use Session. (My current implementation is: if {} is
contributed, create Session; if {A,B,C...} is contributed, create @A
SessionA, @B SessionB, @C SessionC, ...)
TLDR;
collecting dynamic services/contributions cause services to be realized,
causing problems with contributions, as well as causing recursion.
solution 1: allow services to be updated when there are new contributions
solution 2: use a different mechanism to contribute configuration to
dynamic service contributors and do not allow services to be realized
during collection
solution 3: do not allow dynamic services, instead create a syntax for
the "services with multiple configurations" concept
I would love feedback. Can't make all the decisions on my own, I'm too
inexperienced for that.
Also, there may be even more things I've overlooked. Please let me know
if there are more issues.
Tom.
** I say nearly, because my current implementation has
HibernateMarkerConfiguration injected into contributeServiceOverride,
which obviously won't work. I could modify this to create a
dynamiccontribution, contributing to Service Override. In this case, the
contribution will be lost.
Appendix:
Registry changes by dynamics
AspectDecorator : DEFINED => VIRTUAL
HibernateMarkerConfiguration : DEFINED => REAL
MasterObjectProvider : DEFINED => REAL
ServiceOverride : DEFINED => REAL
SymbolSource : DEFINED => VIRTUAL
TypeCoercer : DEFINED => VIRTUAL
DefaultHibernateConfigurerBlue : => DEFINED
DefaultHibernateConfigurerRed : => DEFINED
HibernateEntityPackageManagerBlue : => DEFINED
HibernateEntityPackageManagerRed : => DEFINED
HibernateSessionManagerBlue : => DEFINED
HibernateSessionManagerRed : => DEFINED
HibernateSessionSourceBlue : => DEFINED
HibernateSessionSourceRed : => DEFINED
PackageNameHibernateConfigurerBlue : => DEFINED
PackageNameHibernateConfigurerRed : => DEFINED
SessionBlue : => DEFINED
SessionRed : => DEFINED
Op 27-10-2010 2:15, Tom van Dijk schreef:
For your pleasure, I implemented the "abstract class" alternative, as a
different branch.
There are now a number of branches on my git
trunk
|
fix1326 (fixes TAP5-1326)
|
[2 commits without a branch] (fixes TAP5-1321 and TAP5-1320)
/ \
/ \
iocmod2 iocmod3 (fixes TAP5-1313, dynamic services)
| |
[commit] [commit]
| |
hibmod2 hibmod3 (fixes TAP5-48, multiple databases)
the "2" branch uses annotated services
the "3" branch uses the abstract class
Maybe I should learn to "tag" instead of making a load of branches :)
Tom.
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]