Atom Feed SITE FEED   ADD TO GOOGLE READER

Strict vs. Forgiving APIs

Suppose it's the early 1990's and you're James Gosling implementing String.substring(int, int) for the first time. What should happen when the index arguments are out-of-range? Should these tests pass? Or throw?
  public void testSubstring() {
assertEquals("class", "superclass".substring(5, 32));
assertEquals("super", "superclass".substring(-2, 5));
assertEquals("", "superclass".substring(20, 24));
assertEquals("superclass", "superclass".substring(10, 0));
}


Forgiving APIs


In a forgiving API, these tests pass. The implementation would recognize the out-of-range indices and correct for them. Benefits of forgiving APIs:
  • Fault-tolerant. An off-by-one mistake won't bring a production system to its knees.
  • Easier to code against. If you don't know what to use for a given argument, just pass null and the implementation will do something reasonable.


Strict APIs


In a strict APIs, the out-of-range arguments to substring are forbidden and the method throws an IllegalArgumentException. Benefits of strict APIs:
  • Fail-fast. An off-by-one mistake will be caught in unit tests, if they exist.
  • Easier to maintain. By limiting the number of valid inputs, there's less behaviour to maintain and test.
  • More Predictable. Mapping invalid inputs to behaviour is an artform. In the example, should substring(10, 0) return the empty string? Or "superclass"? What would the caller expect?


For maintainability, I almost always prefer strict APIs. I like to think of the classes in my code as the gears in a fine Swiss watch. Everything fits together tightly, with firm constraints on both the inputs and the outputs. I can refactor with confidence because the system simply won't work if I've introduced problems into it. With a forgiving API, I could introduce bugs and not find out about them until much later.

What's a Hierarchical Injector?

Our application has two implementations for one interface. EnergySource is implemented by both Plutonium and LightningBolt:
class DeLorean {
@Inject TimeCircuits timeCircuits;
@Inject FluxCapacitor fluxCapacitor;
@Inject EnergySource energySource;
}

interface FluxCapacitor {
boolean isFluxing();
}

@Singleton
class RealFluxCapacitor implements FluxCapacitor {
@Inject TimeCircuits timeCircuits;
boolean isFluxing;

public boolean isFluxing() {
return isFluxing;
}
}

class TimeCircuits {
Date whereYouveBeen;
Date whereYouAre;
Date whereYourGoing;
}

interface EnergySource {
void generateOnePointTwentyOneGigawatts();
}

class Plutonium implements EnergySource { ... }

class LightningBolt implements EnergySource { ... }

And to allow for sequels, we assume other implementations of EnergySource are possible. We'd like to create an Injector immediately and create a Plutonium-powered DeLorean. Shortly thereafter, we'd like to re-use that same Injector, but with a lightning bolt for energy.


Option one: Factory classes


We can solve this problem by introducing a DeLorean.Factory interface that accepts an EnergySource as its only parameter:
class DeLorean {
private final TimeCircuits timeCircuits;
private final FluxCapacitor fluxCapacitor;
private final EnergySource energySource;

DeLorean(TimeCircuits timeCircuits,
FluxCapacitor fluxCapacitor,
EnergySource energySource) {
this.timeCircuits = timeCircuits;
this.fluxCapacitor = fluxCapacitor;
this.energySource = energySource;
}

static class Factory {
@Inject TimeCircuits timeCircuits;
@Inject FluxCapacitor fluxCapacitor;

DeLorean create(EnergySource energySource) {
return new DeLorean(timeCircuits, fluxCapacitor, energySource);
}
}
}

This works for our specific problem, but in general it's quite awkward:
  • It requires a gross amount of boilerplate code.
  • It discourages refactoring of the DeLorean class.
  • It increases the complexity of getting an EnergySource.
  • It doesn't work unless EnergySource is a direct dependency of the DeLorean class. Otherwise you need to create lots of little factories that cascade.
  • And EnergySource is no longer in-the-club—it doesn't participate in Guice's injection, AOP, scoping, etc.


Option two: AssistedInject


AssistedInject is a Guice extension that's intended to reduce the boilerplate of option one. Instead of a factory class, we write a factory interface plus annotations:
class DeLorean {
TimeCircuits timeCircuits;
FluxCapacitor fluxCapacitor;
EnergySource energySource;

@AssistedInject
DeLorean(TimeCircuits timeCircuits,
FluxCapacitor fluxCapacitor,
@Assisted EnergySource energySource) {
this.timeCircuits = timeCircuits;
this.fluxCapacitor = fluxCapacitor;
this.energySource = energySource;
}

interface Factory {
DeLorean create(EnergySource energySource);
}
}

This fixes some problems. But the core issue still remains: getting an instance of EnergySource is difficult. Unlike regular Guice (@Inject is the new new), you need to change all callers if you add a dependency on EnergySource.

Option three: Hierarchical Injectors


The premise is simple. @Inject anything, even stuff you don't know at injector-creation time. So our DeLorean class would look exactly as it would if EnergySource was constant:
class DeLorean {
TimeCircuits timeCircuits;
FluxCapacitor fluxCapacitor;
EnergySource energySource;

@Inject
DeLorean(TimeCircuits timeCircuits,
FluxCapacitor fluxCapacitor,
EnergySource energySource) {
this.timeCircuits = timeCircuits;
this.fluxCapacitor = fluxCapacitor;
this.energySource = energySource;
}
}

To use it, we start with an Injector that had bindings for everything except for EnergySource. Next, we create a second injector that extends the first, and binds either Plutonium or LightningBolt. This second injector fills in its missing binding.

The injectors share singletons, so we don't have to worry about having multiple TimeCircuits. Static analysis is applied to both injectors as a whole, where complete information is known. And all objects are in-the-club and get Guice value-adds like injection, scoping and AOP.

This is the solution to the mystical Robot Legs problem, wherein we have a RobotLeg class, that needs be injected with either a LeftFoot or a RightFoot, depending on where the leg will ultimately be used.

Criticism of Hierarchical Injectors


They suggest competing bindings. One parent injector could have relations with multiple child injectors. In our example, the parent injector binds DeLorean and TimeCircuits, and each child binds a different EnergySource.

They require abstract Injectors. The parent injector in our example wouldn't be able to create an instance of DeLorean, since it doesn't have all of the prerequisite bindings. This is just weird.

They're complex. Guice was born out of making code simpler. Does the conceptual weight of hierarchical injectors justify their inclusion?

Going forward


Today's Guice includes a simplified implementation of hierarchical injectors written by Dan Halem. It doesn't cover the interesting (but complex) case where the parent injector cannot fulfill all of its bindings. I'm studying the use cases, trying to come up with a balance between ease-of-use and power.

For example, one idea is to require users to explicitly call-out bindings that child injectors will provide:
  public void configure() {
bind(EnergySource.class).throughChildInjector();
}

I'd also like to do something similar to AssistedInject's factory interfaces. This way the second injector would be created, used and discarded transparently, so the user never needs to see it. From the user's perspective, this would just be like AssistedInject, but the assisted parameters could be injected anywhere.

If you have suggested use-cases or ideas, I'd love to hear 'em.

Wanted: Guice Injector Graphing

One of the nice new features of Guice 2.0 is the new introspection API. It's the equivalent of java.lang.reflect for Guice - it lets you inspect your application at runtime. Our goal is to make it easy to write rich tools for Guice. A natural use case is visualizing an application. The right graph can reveal the structure of your application. I've opened a feature request for this, Issue 213. I created a proof-of-concept to drum-up excitement for this idea.

Example Graph: Application Code


class DeLorean {
@Inject TimeCircuits timeCircuits;
@Inject FluxCapacitor fluxCapacitor;
@Inject EnergySource energySource;
}

class FluxCapacitor {
@Inject TimeCircuits timeCircuits;
}

class TimeCircuits {
Date whereYouveBeen;
Date whereYouAre;
Date whereYourGoing;
}

interface EnergySource {
String generateOnePointTwentyOneGigawatts();
}

class Plutonium implements EnergySource {
public String generateOnePointTwentyOneGigawatts() {
return "newk-you-ler";
}
}

Example Graph: Guice Configuration


    Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(EnergySource.class).to(Plutonium.class);
bind(FluxCapacitor.class);
bind(DeLorean.class);
}
});


.dot File


My Grapher code takes the above Injector and outputs a .dot file that describes a graph:
digraph injector {
"FluxCapacitor" -> "FluxCapacitor.()" [arrowhead=onormal];
"FluxCapacitor" -> "TimeCircuits" [label=timeCircuits]
"DeLorean" -> "DeLorean.()" [arrowhead=onormal];
"DeLorean" -> "TimeCircuits" [label=timeCircuits]
"DeLorean" -> "FluxCapacitor" [label=fluxCapacitor]
"DeLorean" -> "EnergySource" [label=energySource]
"EnergySource" -> "Plutonium" [arrowhead=onormal];
}


The Rendered Graph


Finally, Graphviz renders the .dot file to a pretty picture:


This graph is a good start, but there's a long way to go. Unfortunately, I don't have the bandwidth to take this project to completion and am seeking a contributor. If you're interested, post a note on the issue. Coding is its own reward!

Integer.class and int.class as Guice Keys

Shortly after fixing arrays, I've found another multiple representations bug. This problem is probably familiar - I'm confusing primitive types (like int) with wrapper types (like Integer).

It's one binding


The critical question: should these tests pass?
    assertEquals(Key.get(int.class), Key.get(Integer.class));
assertEquals(TypeLiteral.get(int.class), TypeLiteral.get(Integer.class));
Currently these are non-equal, so Guice has special cases so that they both work. But some special cases are missing! Consider issue 116:
    Injector injector = Guice.createInjector(new AbstractModule() {
protected void configure() {
bind(int.class).toInstance(1984);
}
});
assertEquals(1984, (int) injector.getInstance(int.class)); /* passes */
assertEquals(1984, (int) injector.getInstance(Integer.class)); /* passes */
assertEquals(1984, (int) injector.getInstance(Key.get(int.class))); /* passes */
assertEquals(1984, (int) injector.getInstance(Key.get(Integer.class))); /* passes */
assertNotNull(injector.getBinding(Key.get(int.class))); /* passes */
assertNotNull(injector.getBinding(Key.get(Integer.class))); /* fails! */

Should Key be fixed?


Yes. I think I'll change Key so that it always uses Integer.class, regardless of whether it was created with int.class or Integer.class. Otherwise, this stuff is just too prone to bugs. For example, our new Binder SPI can return Provider<int>, even though that's not a valid type.

Should TypeLiteral be fixed?


Probably not. I'm leaning towards leaving it as-is. Consider an interface with these methods:
    public boolean remove(Integer value)
public Integer remove(int index);
If I change TypeLiteral, then the "remove" method is ambiguous when I know the TypeLiteral of the parameter type. But as a side effect of being inconsistent with Key, this test will always fail:
  TypeLiteral<Integer> primitive = TypeLiteral.get(int.class);
TypeLiteral<Integer> wrapper = new TypeLiteral<Integer>() {};
assertEquals(Key.get(primitive), Key.get(wrapper));
assertEquals(primitive, wrapper);
I think it's a fair compromise.

Fixing the right thing


By making my changes to Key and TypeLiteral, I can make Guice behave consistently throughout all of its APIs. I won't have to worry about users who bind both int.class and Integer.class. And I should be able to rip out some special cases both Guice and its extensions.

If there's a Guice issue that's you'd like fixed, this is a great time to get your feelings heard. I'm spending a lot of time on the issues list, trying to decide what will make the cut for 2.0.