lightbend / config

configuration library for JVM languages using HOCON files
https://lightbend.github.io/config/
6.16k stars 965 forks source link

OSGi docs/examples/support #79

Open havocp opened 11 years ago

havocp commented 11 years ago

Moving discussion from https://github.com/spray/spray/pull/329

There are some issues with loading config when using OSGi because not all the reference.conf are available to ConfigFactory.load.

We need to explore how to modify the samples to support OSGi and provide guidance on how to write a library that works fine in an OSGi context:

https://github.com/typesafehub/config/blob/master/examples/simple-app/src/main/scala/SimpleApp.scala https://github.com/typesafehub/config/blob/master/examples/simple-lib/src/main/scala/simplelib/SimpleLib.scala

We may also want to create a bit of glue code to help people get this right, which would be in a separate config-osgi.jar or else used optionally by the main jar.

It isn't clear to me yet what this all looks like so let's discuss.

mpilquist commented 11 years ago

We need to explore how to modify the samples to support OSGi and provide guidance on how to write a library that works fine in an OSGi context

For library authors, the main principle is to always provide an API which takes a fully initialized Config in addition to whatever other APIs exist. This allows OSGi config to be loaded in whatever way we need without libraries making any assumptions on classloader layouts. An alternative is always providing a ClassLoader based API. This is less flexible than taking a Config but somewhat easier to use, especially if the library is going to be performing dynamic classloading. The various apply overloads on ActorSystem are good examples.

We may also want to create a bit of glue code to help people get this right, which would be in a separate config-osgi.jar or else used optionally by the main jar.

As long as we only call ClassLoader#getResource or ClassLoader#getResources, then https://gist.github.com/mpilquist/6002064 provides a safe way to load all reference.conf files that are reachable from a given bundle. It does this by looking at the packages the bundle is wired to and finding the providing bundles, and then recursing in order to produce a list of transitive bundles that are currently wired to the starting bundle. Once this transitive bundle set is generated, a classloader implementation is created that implements getResource and getResources by calling the equivalent methods on each bundle.

The advantage of this approach is that it makes the OSGi framework do all the heavy lifting of bundle resolution while removing any manual configuration from the user.

If we decide to pursue this approach, we'd need to provide the appropriate overloads of the load method -- basically a mirror of the existing ConfigFactory API where each method takes a bundle. A simpler alternative would be to provide a few common overloads and then provide a method that creates the ClassLoader instance. Something like OsgiConfigFactory.classLoaderFor(bundle).

havocp commented 11 years ago

For library authors, the main principle is to always provide an API which takes a fully initialized Config in addition to whatever other APIs exist.

This is what simple-lib does already right? So the library guidance could be unchanged hopefully.

If we decide to pursue this approach, we'd need to provide the appropriate overloads of the load method -- basically a mirror of the existing ConfigFactory API where each method takes a bundle.

And then applications would use OsgiConfigFactory.load(bundle) and pass it down to all libraries?

Sounds pretty reasonable. Some kind of OsgiConfigFactory.classLoaderFor could be good (there are an awful lot of load() overloads otherwise), though it does expose a little more complexity to the developer.

@jsuereth when you suggested an optional osgi dependency what did you mean exactly; that we have API that relies on OSGi but just don't include osgi in the maven deps? or did you mean use osgi via reflection? any experience with the sanity of something like that?

I guess we'd need to work toward a patch that has:

  1. the OsgiConfigFactory (as optional dep or in a separate jar)
  2. unit tests proving that the setup works for whatever the tricky osgi scenarios are
  3. javadoc (can just link back to the similar ConfigFactory methods probably)
  4. maybe an examples/osgi-app ? or is it as simple as a comment in the current sample app?
jsuereth commented 11 years ago

I was suggesting as an optional dependency, although scala sometimes has issues with these. If you can keep osgi related classes isolated (both by package and reference), then it should be fine.

Osgi also supports optional dependencies... so it's an option. Otherwise, would have to be a separate bundle. On Jul 15, 2013 5:11 PM, "Havoc Pennington" notifications@github.com wrote:

For library authors, the main principle is to always provide an API which takes a fully initialized Config in addition to whatever other APIs exist.

This is what simple-lib does already right? So the library guidance could be unchanged hopefully.

If we decide to pursue this approach, we'd need to provide the appropriate overloads of the load method -- basically a mirror of the existing ConfigFactory API where each method takes a bundle.

And then applications would use OsgiConfigFactory.load(bundle) and pass it down to all libraries?

Sounds pretty reasonable. Some kind of OsgiConfigFactory.classLoaderForcould be good (there are an awful lot of load() overloads otherwise), though it does expose a little more complexity to the developer.

@jsuereth https://github.com/jsuereth when you suggested an optional osgi dependency what did you mean exactly; that we have API that relies on OSGi but just don't include osgi in the maven deps? or did you mean use osgi via reflection? any experience with the sanity of something like that?

I guess we'd need to work toward a patch that has:

  1. the OsgiConfigFactory (as optional dep or in a separate jar)
  2. unit tests proving that the setup works for whatever the tricky osgi scenarios are
  3. javadoc (can just link back to the similar ConfigFactory methods probably)
  4. maybe an examples/osgi-app ? or is it as simple as a comment in the current sample app?

— Reply to this email directly or view it on GitHubhttps://github.com/typesafehub/config/issues/79#issuecomment-21004147 .

briantopping commented 11 years ago

Some comments. Let's ignore these for now if we're working iteratively on the bigger picture, but the answers may help define the contract for config if we understand them.

As far as actual packaging, is there ever a situation where clients of config will rely on the configuration of their dependencies (such as spray relying on or inheriting the configuration of akka)? It's really a question of containment and implicit coupling.

There's probably four reasonable answers to that -- "yes, and that's supported", "yes, but they shouldn't", "no", or "we never thought about it and don't really know what people are doing in the wild, but maybe we should". I'm guessing it's the first of those. It's nice and lightweight for a command line property to be able to propagate through a statically deployed system (for instance), but makes dynamic runtime modularity rather difficult. Is hot redeployment with different properties supported or considered right now (i.e. is there a way to "unmerge" a set of properties going out of scope)? What happens to higher order modules that depended on previous versions of lower ordered properties that might have been changed or removed in a redeployed lower order module?

One of the reasons OSGi doesn't deal with cross-bundle resource dependencies very well is the expectation of containment is a lot tighter. This makes a lot of sense if resources are coming and going regularly. A solution to this is the whiteboard pattern whereby a listener bundle watches bundle registration events and maintains capabilities that it's in charge of. In a static (non-OSGi, possibly others) environment, the "registration event" only happens once, like the call to ConfigFactory.load, but a call to withFallback is equivalent to a registration event for all intents, so there's already parallels with the code, but maybe we're missing this "unmerge" functionality still.

Also remember that behavior of a bundle can dynamically react to it's environment through the bundle activator. For the benefit of those with less OSGi experience, any JAR can be considered on a matrix with "JAR", "Static Bundle" and "Dynamic Bundle" on the vertical axis, and dependencies on OSGi in the form of "none", "optional" and "required" on the horizontal axis (ignore for a moment that that non-bundle JARs don't depend on OSGi in real life). The differentiator between static and dynamic behavior is simply whether there is a bundle activator in the manifest or metadata in the bundle's OSGI-INF directory (for declarative configuration), there's no changes to the code.

What this means is the config bundle could become dynamic, and as soon as the dynamic entry points are hit by the container, we know we can start watching other bundles for configuration elements. The Config object that was returned from there on out would not be the current Config object, but would be a proxy for this dynamic collection of properties that were being managed by the now-dynamic config bundle.

Orthogonal to how this is hooked in is the configuration optionally using OSGi Configuration Admin. The goal with this would be to hook the OSGi property store into the config resolver chain so that deployers could manage properties in a more manageable way at runtime. Configuration Admin is a key component of OSGi, so googling for it will return a sizable cache of information. I've used CA in practice and it's occasionally makes believers out of the OSGi skeptics, it's good stuff. This is a can that could be kicked down the road, but I'd regret not bringing it up if a goal of config is to have as few releases as possible.

mpilquist commented 11 years ago

I agree that we don't need the end-all-be-all OSGi configuration loading mechanism in order to provide value. An incremental approach would be more useful. Dealing with dynamism appropriately would be beneficial from a long term OSGi perspective. I think we should tackle static config loading that's aware of the OSGi classloading architecture first though -- especially since the highest profile libraries that use Typesafe Config don't handle config reloading (e.g., https://groups.google.com/d/topic/akka-user/bQA1zJP3yOY/discussion).

With all that said, how would you like to proceed Havoc? I can put together an initial PR to start discussion if you like. Let me know.

havocp commented 11 years ago

Someone was just asking me about this issue again, and I guess I left it a little unclear. In short: I definitely need a PR simply because I don't know OSGi, but I'm happy to see the problem addressed. The high-level goals I think are:

It's hard for me to be more specific because I just don't understand this area without digging in on it. I don't know if these are all the important issues, just the ones I thought of.

Implementation details that are important, pretty obvious stuff I guess:

A concrete PR to discuss would be great!

mpilquist commented 11 years ago

I'd be happy to submit a PR to get the conversation started. I'm a bit busy at the moment, but later in the year I should be able to get something together.

rkuhn commented 10 years ago

Sorry for not seeing this one earlier. My OSGi knowledge is slowly growing, but I’m still a neophyte. What I can contribute is some input on how Akka uses the config library, especially in this context.

An ActorSystem uses exactly one ClassLoader to handle all its reflective access, and it takes it from the user directly (if given), from the Thread (getContextClassLoader) or from ActorSystem.class.getClassLoader—in this order. It then loads the default configuration from that ClassLoader, relying on all of the used modules being there because we have truly banned all default values from code (this was one of the best decisions we ever made). In order to make this work in OSGi we created the akka-osgi bundle which contains all reference.conf files, since it is impossible to export/import resources which live at the root of the class path. This comes at an unwelcome price, namely that all classes referenced e.g. in the serializers list will be loaded eagerly, even if the bundle the serializer is coming from is not being used.

One alternative scheme I have thought about—but please keep in mind that I am just a layman—is to move the location of reference.conf into the com.typesafe.config.reference package (well, we’d have to merge from both locations, I guess) and to use DynamicImport-Package: com.typesafe.config.reference on the akka-osgi bundle. Would dynamic import work for scanning and finding all reference.conf resources?

In any case, we depend on external input to get this right, and if @mpilquist’s PR-to-be solves the issue then that is of course very welcome!

briantopping commented 10 years ago

Hi Roland, I think we've all been busy. But for the monthly bump on this thread, here's mine. We're revisiting this now that Spray is 1.2.0 and mulling around what has to change for us to upgrade.

What's going on with spray-osgi makes sense, but it's a pattern that needs to be replicated by clients to succeed. This is more challenging when something like Spray is in between the user code and Akka. If they don't replicate the pattern of copying all the configs, it's up to the user to wire the same build magic into their own build, and it's not very fun at all to maintain that.

Over the holiday, I'm going to tinker with a Configuration Admin service as I previously wrote about. It seems reasonable that if a config can be transformed to a CA store and locked to the specific version of the bundle, the base config that was used to load the CA store will (by definition of a release) never change, so it can be used reliably. That's unstable with snapshots, but in theory some attribute could record the snapshot serial number and reload everything if it's changed (wiping out local changes seems to be the right thing to do in that case).

If I can pull it off, a client of config will not know that it is working from CA-based properties and the deployer can twiddle with those properties at runtime, without recompiling modules.

rkuhn commented 10 years ago

Brian, we have spent the last two days hakking on the OSGi sample, including more Akka subsystems and gaining experience in general. Our conclusion is that at least for Akka using the ConfigAdmin directly does not make sense: an ActorSystem needs configuration, but in addition it needs to be able to load all those classes which are referenced from that configuration. The OSGi way would be to use some services which provide the desired implementations (or factories thereof), but that is not a direction in which we want to go. The reason is that ActorSystem itself is already a container, and we would be building quite a lot of parallel structure to the OSGi container.

So the decision for Akka is that we only support the usage of an ActorSystem strictly confined to a single OSGi module (and its dependency subtree, which then naturally includes akka-actor). In plain words this means that the approach of offering an ActorSystem as a service to which Actors can be deployed dynamically is not how we intend Akka to be used—simply because an ActorSystem that is dynamic in this way would be a completely different animal from what we have now. Therefore we are going with the bundle-traversing class loader approach proposed by Michael. This ClassLoader will then have visibility to all exported classes of all modules in the subtree plus all resources (even non-exported which as we all know is necessary here), and that should cover the OSGi functionality which we intend to support.

This is not to say that working on making Config play nice with ConfigAdmin is without merit, it just means that Akka usage will most likely not profit from it. One thing to keep in mind: it is fully within reason to use multiple ActorSystems in the design of your application, and OSGi would even allow you to use different Akka versions for those.

mpilquist commented 10 years ago

Regarding registering the ActorSystem as an OSGi service -- this is a much more powerful model but requires OSGi specific code to handle the dynamics of bundle start/stop, config merging, and classloading. I use both models -- a single shared actor system for well behaved actors and bundle specific actor systems to bulkhead actors that aren't well behaved.

The shared ActorSystem allows, for example, having 1 bundle start a Spray server and other bundles register a prefix URI path and ActorRef that handles web requests. Hence, you get a modular HTTP server with fairly minimal work. This beats the OSGi HttpService or embedding Jetty/Tomcat in my opinion.

With that all said, I think this type of usage should be possible with Akka but not directly supported. For apps that are using the dynamics afforded by OSGi, a certain amount of glue code is reasonable. (If there's interest, I may be able to open source some of that glue code as a separate project or example.)

havocp commented 10 years ago

btw, there's also #49 which I'm not sure what to do with.

rocketraman commented 8 years ago

@mpilquist I'd like to see that glue code. Right now, the issue I'm having is that akka.actor.deployment configuration should come from the bundle that exposes the actor, but of course Akka expects it in the application.conf of the bundle that creates the ActorSystem. Obviously, this sucks -- if I'm creating actors in bundles I want those bundles to be dynamic and be able to come and go.

briantopping commented 8 years ago

@rocketraman: FWIW, I've moved to separate ActorSystem per bundle, clustered. The clustering code is attached to the OSGi lifecycle.

rocketraman commented 8 years ago

@briantopping Sounds like a reasonable workaround, thanks.