I'd like to get some guidance on correct, clear, and concise initialization 
of singleton, read-only access objects
wrapping cached run-once computations, with either lazy or eager 
initialization.

I brought this up peripherally in
  http://groups.google.com/forum/?fromgroups#!topic/google-guice/u0V97-FZBTQ
but I wanted to avoid taking my attempted threadjacking any further and am 
starting a new topic.

Here's a sample use case:

A Database provides a table of String keys and values.  I'd like to provide 
a read-only get(String)->String access
object, and I'd like to initialize it safely, so that the resulting object 
is thread-safe.  I'd like to express this
concisely, in a way that is clear to code readers so they will be incented 
to copy the pattern.

Lazy init can be done in Java using the static hack, as descrbied by Bob 
here
  http://blog.crazybob.org/2007/01/lazy-loading-singletons.html
  and in Wikipedia here
  http://en.wikipedia.org/wiki/Initialization-on-demand_holder_idiom 

but I'd like to do it using Guice, for all the usual reasons, such as so I 
can integrate these providers in with normal injection.

I hope my problem statement is clear. I'm looking for guidance on the best 
solution.

Here are three unsatisfactory ideas and one maybe OK one. 

Proposal A:
Proposal A does the work of reading the database in the constructor.

This code is clear, and it's certainly concise, and as near as I can 
understand, it's thread-safe.

Unfortunately, I belive it isn't correct in Guice because of the problems 
associated with doing work in constructors
(proxy objects, for example).

    @LazySingleton
    class CachedThings {
      private final Map<String,String> cache;

      @Inject CachedThings(DB db) {
        this.cache = readCache(db);
      }

      public String get(String x) {
        return cache.get(x);
      }

      private Map<String,String> readCache(DB db) {
        Map<String,String> result = new HashMap<String,String>();
        for (Row row : db) { map.add(row.a, row.b); }
        return result;
      }

    }

Proposal B:

Proposal B splits the data access object from the database reader 
operation, moving the database read into its own
Provider, where it can operate safely.  This appears to be just as 
thread-safe as the previous version, but is
considerably less concise.  It also exposes a @Named TypeLiteral that is 
not only ugly, but also opens the
possibility of someone directly injecting that Map and causing unwanted 
expensive database reads.

    @LazySingleton
    class CachedThings {
      private final Map<String,String> cache;

      @Inject CachedThings(@Named("hack") Map<String,String> cache) {
        this.cache = cache;
      }

      public String get(String x) {
        return cache.get(x);
      }

      static class MyModule extends AbstractModule {
         protected void configure() {
           bind(new 
TypeLiteral<Map<String,String>>(){/**/}.annotatedWith(Names.named("hack")).toProvider(ThingProvider.class).in(LazySingleton.class);
 
       
         }
      }

      @LazySingleton 
      static class ThingProvider implements Provider<Map<String,String>> {
        private final DB db;

        @Inject ThingProvider(DB db) {
          this.db = db;
        }

        private Map<String,String> get() {
          Map<String,String> result = new HashMap<String,String>();
          for (Row row : db) { map.add(row.a, row.b); }
          return result;
        }
    }


Proposal C:
Proposal C uses @Provides methods so avoid to the problematic verbosity of 
TypeLiteral, and indeed the code is cleaner, but we still have the @Named 
hack and
internal data exposure.  (I've written the MyModule as a static class of 
CachedThings, which is questionable, so we might need to deduct a few points
for the additional verbosity needed to move the module out.)

    @LazySingleton
    class CachedThings {
      private final Map<String,String> cache;

      @Inject CachedThings(@Named("hack") Provider<Map<String,String>> 
thingProvider) {
        this.cache = thingProvider.get();
      }

      public String get(String x) {
        return cache.get(x);
      }

      static class MyModule extends AbstractModule {
         protected void configure() { }

         @LazySingleton @Provides @Named("hack") 
Provider<Map<String,String>> getThing(DB db) {
           Map<String,String> result = new HashMap<String,String>();
           for (Row row : db) { map.add(row.a, row.b); }
           return result;
        }
      }
    }

Proposal D:
I don't know much about injected methods other than that they run after 
constructors and the results can't be final.
Is this correct with regard to Guice initialization sequence?  Is it safe 
for multi-threaded readonly access of the resulting HashMap?

Maybe this is the right solution is to use an @Inject setCache(DB) method?

    @LazySingleton
    class CachedThings {
      private Map<String,String> cache;

      @Inject CachedThings(...) {
        ... other stuff here if necessary ...
      }

      @Inject void setCache(DB db) {
         Map<String,String> result = new HashMap<String,String>();
         for (Row row : db) { map.add(row.a, row.b); }
         cache = result;
      }

      public String get(String x) {
        return cache.get(x);
      }
    }

Thank you,
Leigh.

-- 
You received this message because you are subscribed to the Google Groups 
"google-guice" group.
To view this discussion on the web visit 
https://groups.google.com/d/msg/google-guice/-/HUKKPENRVqcJ.
To post to this group, send email to google-guice@googlegroups.com.
To unsubscribe from this group, send email to 
google-guice+unsubscr...@googlegroups.com.
For more options, visit this group at 
http://groups.google.com/group/google-guice?hl=en.

Reply via email to