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]