Scalpel 101

May 26th, 2009

Scalpel is a mechanism to consistently handle interactions with external libraries. It promotes looser coupling, and makes your code more robust and easier to test.

An edge is the term we use to describe the thin boundary between your code and the external libraries you depend on. In most code bases, the edges are often areas that contain the most inconsistent and fragile code.

In part this is due to the fact that more often than not, each external library has a different approach and philosophy on how to deal with exceptions, nulls, statics and interfaces. This makes your code more difficult to test as it forces you to deal with every approach separately. Using Scalpel is an excellent way to combat this.


Scalpel Approach
We think a good external library API has the following characteristics:

  • No checked exceptions that we cannot realistically recover from.
  • No static methods.
  • Interfaces, interfaces, interfaces.
  • Only the methods you need are exposed.

We feel these characteristics help promote code consistency and looser coupling. The way we have implemented Scalpel enables us to enforce these characteristics onto all external libraries, no matter how their APIs have been defined.

Scalpel does away with the little shims containing reams of redundant boilerplate code at the boundaries of your system. More importantly, Scalpel allows you to make all external library API usage consistent. This turns out to be even more important than whether or not you agree with our ideas about what constitutes a good external library API. Consistency! Yay!


A Simple Example

Okay, here is a simple scenario where we might use the Scalpel. Say we are required to hook into a 3rd party API to get some crucial information like the most popular beverage from our local cafe. They have provided an API that looks like this…

package org.mylocal.coffeeshop;

public class CoffeeShopApi extends DodgyApi {
    public String getMostPopularBeverage() throws WorldHasEndedException {
       // some dodgy code occurs here, until...
       return "Flat White";
    }

    public static String getShopName() {
       return "Mumma Selecta";
    }

    public void methodNoOneWouldEverCall() throws WorldHasEndedException {
       // do something terrible to the calling system...
    }
}

Check out things like this for more details, but we think this API is not so good because:

  • It throws checked exceptions.
  • It is a class and not an interface.
  • It exposes methods we don’t need, or even want.

Scalpel solves these issues by enabling you to create an interface to CoffeeShopApi where you only expose the methods you need. It also converts checked exceptions into runtime exceptions automatically. There are good reasons for preferring runtime exceptions that are explained elsewhere.

Here is the edge interface that you would create for the CoffeeShopApi

package edge.org.mylocal.coffeeshop;

public interface CoffeeShopApi extends Edge {
    String getMostPopularBeverage();
}

Wherever you use the (lovely) CoffeeShopApi edge interface through your code, Scalpel can automatically create an implementation of the interface that calls an instance of the real (not so good) implementation of the CoffeeShopApi.

The advantage is that by just defining an edge interface, exposing the external methods you want to use, you can use the CoffeeShopApi in your code in a consistent manner. Being an edge, it is an interface, so mocking it in your tests is simple (no cglib required). If you are using the Spider, you can declare it in your classes as you would any other dependency, like this…

package scalpel.coffeeshop.news;

import edge.org.mylocal.coffeeshop.CoffeeShopApi;

public class DefaultCoffeeShopNews implements CoffeeShopNews {
    CoffeeShopApi shop;
    Speakers speakers;

    public void broadcast() {
        String bevvy = shop.getMostPopularBeverage();
        speakers.speak("The most popular beverage at the local coffee shop is " + bevvy);
    }
}

NOTE: For this to work, your Spider should use a BoostWeb, for the reason that BoostWeb registers an EdgeFactory automatically for you.

If you want to use static methods in your external library, you need to declare another edge interface with a Static suffix. This is cool. Don’t sweat it. So, if we wanted to make a call the static getShopName method in the CoffeeShopApi, we create a static edge interface like this…

package edge.org.mylocal.coffeeshop;

public interface CoffeeShopApiStatic extends Edge {
    public String getShopName();
}

You would now use it like this…

package scalpel.coffeeshop.news;

import edge.org.mylocal.coffeeshop.CoffeeShopApi;
import edge.org.mylocal.coffeeshop.CoffeeShopApiStatic;

public class DefaultCoffeeShopNews implements CoffeeShopNews {
    CoffeeShopApiStatic shopStatic;
    CoffeeShopApi shop;
    Speakers speakers;

    public void broadcast() {
        String bevvy = shop.getMostPopularBeverage();
        String name = shopStatic.getShopName();
        speakers.speak("The most popular beverage at " + name + " is " + bevvy);
    }
}

You don’t have to use the Spider field injection to use the Scalpel. You can use an object called Edges to explicitly create edges from interfaces or classes, or use it to wrap edges around object instances, like this…

    ...
    public void createShops() {
        Edges edges = edges();
        CoffeeShopApi shop = edges.nu(CoffeeShopApi.class); // edgify
        CoffeeShopApi anotherShop = edges.ref(CoffeeShopApi.class, new CoffeeShopApi()); // edge wrapper
        CoffeeShopApiStatic shopStatic = edges.statik(CoffeeShopApiStatic.class); // static edgify
    }

    public Edges edges() {
        Egg egg = new SpiderEgg(BoostWeb.class, ScalpelWeb.class);
        Spider spider = egg.hatch();
        return spider.resolve(Edges.class);
    }
    ...

An added feature of the Scalpel (which is not shown here) is that argument types going into and coming out of edge interfaces can themselves be automatically “edged” or “unedged” as required. This handy feature means you can define your edge interface method arguments using either the real or edged classes without having to call Unedgable#unedge.

NOTE: If you ever need to expose the real instance that underlies the implementation of an edge, make your edge interface extend Unedgable and call unedge.


Summary

  • Scalpel automatically creates implementations of edges. All you need to do is define the edge interface.
  • An edge must extend the marker Edge.
  • Only selected methods need be exposed, removing unwanted noise from API.
  • Checked exceptions get turned into unchecked exceptions.
  • Static calls can also be edged by creating an edge interface with a Static suffix.
  • If you are using Spider, just declare the edge interface in your class like you would any other dependency.
  • If you are using not using Spider for injection, you can still use Scalpel via the Edges object.

THE END