Atom Feed SITE FEED   ADD TO GOOGLE READER

Considering Guice: Explicit dependencies

I love Guice. So much so that lately I've been trying to push it on my friends for their projects.

But my friends are reluctant. They've become accustomed to manually solving the problems that Guice could solve for them. They enjoy wiring up their apps and implementing singletons, factories and double-checked locking. But most significantly, they don't recognize the limitations of their approach. It's very difficult to convince someone to fix a problem that they don't know they have!

Here's how reluctance is usually voiced:
  • I don't like indirection. I like being able to know exactly where my objects are coming from, and Guice hides that.
  • Guice is great for some applications, but it isn't appropriate for the one that I'm writing.

    In this series, I intend to make the case for Guice. I'll use examples that show a particular feature or benefit. And although many advantages of Guice are quite abstract, but I'll try to be concrete in my examples.

    Explicit Dependencies


    JavaScript toggle: standard version, guice version

    /**
    * Standard version.
    */
    public class RealOrderService implements OrderService {

    private final ReadableDuration MAX_TRAVEL_DURATION
    = Minutes.minutes(15).toStandardDuration();
    private final Distance MAX_TRAVEL_DISTANCE
    = Distance.forKilometers(10);
















    public Set<Reason> validate(Order order) {
    Set<Reason> reasons = new HashSet<Reason>();

    Store store = order.getStore();

    if (order.isDelivery()) {
    Address deliveryAddress = order.getDeliveryAddress();
    Address storeAddress = store.getAddress();
    Route route = GeographyFactory.getInstance()
    .calculateRoute(deliveryAddress, storeAddress);

    AddressBlacklist addressBlacklist = AddressBlacklistFactory.getInstance();
    if (!addressBlacklist.isAddressOk(deliveryAddress)) {
    List<Note> blacklistNotes = addressBlacklist.getNotes(deliveryAddress);
    reasons.add(new Reason(ReasonId.BLACKLISTED_ADDRESS, blacklistNotes));
    }

    Duration travelDuration = GeographyFactory.getInstance()
    .calculateTravelDuration(route, order.getQuotedArrivalTime());
    if (travelDuration.isLongerThan(MAX_TRAVEL_DURATION)) {
    reasons.add(new Reason(ReasonId.TRIP_DURATION_TOO_LONG, travelDuration));
    }

    Distance travelDistance = route.getDistance();
    if (travelDistance.isLongerThan(MAX_TRAVEL_DISTANCE)) {
    reasons.add(new Reason(ReasonId.TRIP_DISTANCE_TOO_LONG, travelDistance));
    }

    List<User> onDuty = WorkScheduleFactory.getInstance().getPeopleOnDuty(
    store, order.getQuotedArrivalTime());
    List<User> deliveryPersons = selectUsersInRole(onDuty, Role.ORDER_DELIVERY);
    if (deliveryPersons.isEmpty()) {
    reasons.add(new Reason(ReasonId.NO_DELIVERY_PERSONS_ON_DUTY));
    }
    }

    ...

    return reasons;
    }

    ...

    }


    The Guice code is longer because it includes a constructor that takes all dependencies. This is awesome! Now I can easily figure out what an implementation class depends on by reading its API.

    For testability


    I'm writing a unit test for RealOrderService.

    With the Guice version, I immediately know what services the test requires. My IDE and compiler will make sure I fulfill them.

    With the standard version, figuring out which factories to prepare is labour-intensive. I have to either read the full source code, or do trial-and-error of running the test. Or I could leave the default factories in place, and my unit test might end up accessing a database or network service.

    For maintainability


    RealOrderService is used in two different applications: a rich client and a webapp. But I forgot about the rich client when I added a new dependency on InventoryControlService.

    With the Guice version, if I have unit test coverage, the bug is caught at compile time. If I don't, it will fail at app startup. Guice has the complete dependency graph so it can fail fast when it discovers a missing dependency.

    With the standard version, if I have unit test coverage, the bug might be caught at test time. If I don't, this bug won't be caught until the code is missing dependency is needed. This might be as late as QA, or even production!

    In conclusion


    By making your dependencies explicit you increase testability, and maintainability of your code. Guice encourages you to declare your dependencies using regular Java constructors.
  • Can you provide any details about the Distance class used in your example? Is it OSS? Can you describe how it holds data internally (km vs miles etc).
    heh. if I know Jesse, I bet he just MADE IT UP for his blog post!