Posts Tagged Java
Trickle – asynchronous Java made easier
Posted by Petter Måhlén in Software Development on February 3, 2014
At Spotify, we’re doing more and more Java (Spotify started out as a Python-mostly shop, but performance requirements are changing that), and we’re doing more and more complex, asynchronous Java stuff. By ‘complex, asynchronous’, I mean things along the veins of:
- Call a search engine for a list of albums matching a certain query string.
- Call a search engine for a list of tracks matching the same query string.
- When the tracks list is available, call a service to find out how many times they were played.
- Combine the results of the three service calls into some data structure and return it.
This is easy to do synchronously, but if you want performance, you don’t want to waste threads on blocking on service calls that take several tens of milliseconds. And if you do it asynchronously, you end up with code like this:
ListenableFuture<List<Track>> tracks = search.searchForTracks(query); final ListenableFuture<List<DecoratedTrack>> decorated = Futures.transform(tracks, new AsyncFunction<List<Track>, List<DecoratedTrack>>() { @Override public ListenableFuture<List<DecoratedTrack>> apply(List<Track> tracks) { return decorationService.decorate(tracks); } }); final ListenableFuture<List<Album>> albums = search.searchForAlbums(query); ListenableFuture<List<Object>> allDoneSignal = Futures.<Object>allAsList(decorated, albums); return Futures.transform(allDoneSignal, new Function<List<Object>, SomeResult>() { @Override public SomeResult apply(List<Object> dummy) { return new SomeResult( Futures.getUnchecked(decorated), Futures.getUnchecked(albums)); } });
It’s not exactly clear what’s going on. To me, some of the problems with the above code are:
- There’s a lot of noise due to Java syntax; it’s really hard to see which bits of the code do something useful.
- There’s a lot of concurrency management trivia in the way of understanding which service calls relate to which. The overall flow of data is very hard to understand.
- The pattern of using Futures.allAsList() to get a future that is a signal that ‘all the underlying futures are done’ is non-obvious, adding conceptual weight.
- The final transform doesn’t actually transform its inputs. To preserve type safety, we have to reach out to the ‘final’ decoratedTracks and albums futures.
- It’s easy for application code to accidentally make the code blocking by doing a ‘get’ on some future that isn’t yet completed, or by forgetting to add a future to a newly added service call to the ‘doneSignal’ list.
We’ve created a project called Trickle to reduce the level of ‘async pain’. With Trickle, the code above could be written like so:
// constant definitions private static final Input<String> QUERY = Input.named("query"); // one-time setup code, like a constructor or something private void wireUpGraph() { Func1<String, List<Track>> searchTracksFunc = new Func1<String, List<Track>>() { @Override public ListenableFuture<List<Track>> run(String query) { return search.searchForTracks(query); } }; Func1<List<Track>, List<DecoratedTrack>> decorateTracksFunc = new Func1<List<Track>, List<DecoratedTrack>>() { @Override public ListenableFuture<List<DecoratedTrack>> run(List<Track> tracks) { return decorationService.decorate(tracks); } }; Func1<String, List<Album>> searchAlbumsFunc = new Func1<String, List<Album>>() { @Override public ListenableFuture<List<Album>> run(String query) { return search.searchForAlbums(query); } }; Func2<List<DecoratedTrack>, List<Album>, SomeResult> combine = new Func2<List<DecoratedTrack>, List<Album>, SomeResult>() { @Override public ListenableFuture run(List<DecoratedTrack> decorated, List<Album> albums) { return Futures.immediateFuture(new SomeResult(decorated, albums)); } }; Graph<List<Track>> searchTracks = Trickle.call(searchTracksFunc).with(QUERY); Graph<List<DecoratedTrack>> decorateTracks = Trickle.call(decorateTracksFunc).with(searchTracks); Graph<List<Album>> albums = Trickle.call(searchAlbumsFunc).with(QUERY); this.searchGraph = Trickle.call(combine).with(decorateTracks, albums); } // actual invocation method public ListenableFuture search(String query) { return this.searchGraph.bind(QUERY, query).run(); }
The code is not shorter, but there are some interesting differences:
- The dependencies between different calls are shown much more clearly.
- The individual steps are more clearly separated out, making them more easily testable.
- Each step (node in Trickle lingo) is only invoked when the previous ones are completed, so you never get a Future as an input to business logic. This makes it very hard for application code to block concurrency.
- It’s forward-compatible with lambdas, meaning it’ll look a lot nicer with Java 8.
For more examples and information about how it works, take a look at the README and wiki, because this post is about why, not how, I think you should use Trickle.
Why Trickle?
A danger that you always run as an engineer is that you fall in love with your own ideas and pursue something because you invented it rather than because it’s actually a good thing. Some of us had created systems for managing asynchronous call graphs at previous jobs, and while they had very large limitations (at least the ones I created did), it was clear that they made things quite a lot easier in the contexts where they were used. But even with that experience, we were not sure Trickle was a good idea. So once we had an API that felt like it was worth trying out, we sent out an email to the backend guild at Spotify, asking for volunteers to compare it with the two best similar frameworks we had been able to find on the interwebs (we ruled out Disruptor and Akka because we felt that introducing them would be an unreasonably large change to our existing ecosystem). The comparison was done by implementing a particular call graph, and we then asked people to fill out a questionnaire measuring a) how easy it was to get started, b) how much the framework got out of the way allowing you to focus on the core business logic, and c) how clean the resulting code was. Nine people took the survey, and the results were pretty interesting (1 is worst, 5 is best):
Technology | Getting going | Focus on core | Cleanness |
---|---|---|---|
ListenableFutures | 4.0 | 3.6 | 2.7 |
RxJava | 2.8 | 3.7 | 3.1 |
Trickle | 3.9 | 3.8 | 4.4 |
The most common comment regarding ListenableFutures (5/9 said this) was: “I already knew it, so it was of course easy to get started”. The most common comment about Trickle (6/9) was “no documentation” – three of the people who said that also said “but it was still very easy to get going”. So Trickle, without documentation, was almost as easy to get going with as raw Futures that most of the people already knew, and it was a clear winner in code cleanness. Given that we considered the cleanness of the resulting code to be the most important criterion, it felt like we were onto something.
Since getting that feedback, we’ve iterated a couple of times more on the API, and to ensure that it is production quality, we’re using it in the service discovery system that our group owns (and which can bring down almost the entire Spotify backend if it fails). We’ve also added some documentation, but not too much – we want to make sure that the API is simple enough that you can use it without needing to read any documentation at all. As you can tell from the links above, we also open-sourced it. Finally, we did a micro benchmark to ensure we hadn’t done anything that would introduce crazy performance limitations. All micro benchmarks are liars, but the results look like we’re in a reasonable zone compared to the others:
Benchmark | Mode | Samples | Mean | Mean error | Units |
---|---|---|---|---|---|
c.s.t.Benchmark.benchmarkExecutorGuava | thrpt | 5 | 68.778 | 4.066 | ops/ms |
c.s.t.Benchmark.benchmarkExecutorRx | thrpt | 5 | 20.242 | 0.710 | ops/ms |
c.s.t.Benchmark.benchmarkExecutorTrickle | thrpt | 5 | 52.148 | 1.776 | ops/ms |
c.s.t.Benchmark.benchmarkImmediateGuava | thrpt | 5 | 890.375 | 79.594 | ops/ms |
c.s.t.Benchmark.benchmarkImmediateRx | thrpt | 5 | 312.870 | 8.643 | ops/ms |
c.s.t.Benchmark.benchmarkImmediateTrickle | thrpt | 5 | 168.820 | 13.991 | ops/ms |
Trickle is significantly slower than plain ListenableFutures (especially when the futures aren’t real futures because the result is immediately available; this case is very fast in Guava, whereas Trickle doesn’t do any optimisations for that). This is not a surprise, since Trickle is built on top of ListenableFutures and does more stuff. The key result we wanted out of this was that it shouldn’t be orders of magnitude slower than plain ListenableFutures, and it’s not. If a single thread is capable of doing 52k operations/second in Trickle, that’s more than our use case requires, so at least this test didn’t indicate that we had done something very wrong. I’m skeptical about the RxJava performance results; it’s slowness when using real threads may well be due to some mistake I did when writing the RxJava code.
Trickle, while still immature in particular regarding the exact details of the API, is now at a stage where we feel that it’s ready for general use, so if you’re using Futures.transform() and Futures.allAsList(), chances are great that you can make your code easier to read and work with by using Trickle instead. Or, if you’re doing things synchronously and wish that you could improve the performance of your services, perhaps Trickle could help you get a performance boost!
Keeping Classes Simple using the SRP
Posted by Petter Måhlén in Java on November 3, 2013
The Single Responsibility Principle – that a class should have one and only one reason to change – is, I think, one of the most important principles to follow in order to make high quality and easily maintainable software. It’s also one of the principles that I see abused the most often (by myself as well as others). This post is cross-post of something I wrote for the internal Spotify blog, and talks about a technique to make it easier to adhere to the SRP and thus create simple classes, in the Rich Hickey sense: ‘easy’ is a subjective term describing the effort it takes some particular person to do something, ‘simple’ is an objective term describing the resulting artefact. Simple software is valuable (and complex software is costly and causes frustration), so it is very useful to have good tools that make it easy to create simple things.
I’ll describe a concrete technique that I think of as composing behaviours; I think it’s close to if not identical to the decorator pattern. I’ll be using some code I recently wrote in a library wrapping DNS lookups at Spotify. I needed to create something that would let us do SRV lookups, and if the results were empty or there was another kind of failure when doing the lookup, it should retain the previous data. This is to reduce the risk of service outages caused only by DNS failures. I also wanted to ensure that we had metrics on the time we spent on DNS lookups, the rate of failures, and how common empty responses were.
The most common reaction when faced with a problem description like that is to create a class, (DnsSrvResolver, let’s say) that implements those features. In a sense, this class does a single thing – DNS SRV lookups that are metered and whose results can be kept in case of problems. But I would argue that that ‘thing’ is too big, and it’s better to create multiple classes doing smaller things. Here’s how.
First, an interface (the code in the post is Java, but of course the technique can be applied to code in any programming language):
public interface DnsSrvResolver { List resolve(String fqdn); }
Then, an implementation class that does the actual lookup of SRV records using a standard DNS library:
class XBillDnsSrvResolver implements DnsSrvResolver { private final LookupFactory lookupFactory; XBillDnsSrvResolver(LookupFactory lookupFactory) { this.lookupFactory = lookupFactory; } @Override public List resolve(final String fqdn) { Lookup lookup = lookupFactory.forName(fqdn); Record[] queryResult = lookup.run(); if (lookup.getResult() != Lookup.SUCCESSFUL) { throw new DnsException( String.format("Lookup of '%s' failed with code: %d - %s ", fqdn, lookup.getResult(), lookup.getErrorString())); } return toHostAndPorts(queryResult); } private List toHostAndPorts(Record[] queryResult) { ImmutableList.Builder builder = ImmutableList.builder(); if (queryResult != null) { for (Record record: queryResult) { if (record instanceof SRVRecord) { SRVRecord srvRecord = (SRVRecord) record; builder.add(HostAndPort.fromParts( srvRecord.getTarget().toString(), srvRecord.getPort()) ); } } } return builder.build(); } }
So far, nothing out of the ordinary. Here comes the trick: now, add the metrics-tracking and result-storing behaviours in new classes that delegate the actual DNS lookup to some other, unknown class. Metrics can be done like this:
class MeteredDnsSrvResolver implements DnsSrvResolver { private final DnsSrvResolver delegate; private final Timer timer; private final Counter failureCounter; private final Counter emptyCounter; MeteredDnsSrvResolver(DnsSrvResolver delegate, Timer timer, Counter failureCounter, Counter emptyCounter) { this.delegate = delegate; this.timer = timer; this.failureCounter = failureCounter; this.emptyCounter = emptyCounter; } @Override public List<HostAndPort> resolve(String fqdn) { final TimerContext context = timer.time(); boolean success = false; try { List<HostAndPort> result = delegate.resolve(fqdn); if (result.isEmpty()) { emptyCounter.inc(); } success = true; return result; } finally { context.stop(); if (!success) { failureCounter.inc(); } } } }
And retaining old data like this:
class RetainingDnsSrvResolver implements DnsSrvResolver { private final DnsSrvResolver delegate; private final Map<String, List<HostAndPort>> cache; RetainingDnsSrvResolver(DnsSrvResolver delegate) { this.delegate = delegate; cache = new ConcurrentHashMap<String, List<HostAndPort>>(); } @Override public List<HostAndPort> resolve(final String fqdn) { try { List<HostAndPort> nodes = delegate.resolve(fqdn); if (nodes.isEmpty()) { nodes = firstNonNull(cache.get(fqdn), nodes); } else { cache.put(fqdn, nodes); } return nodes; } catch (Exception e) { if (cache.containsKey(fqdn)) { return cache.get(fqdn); } throw Throwables.propagate(e); } } }
Note how small and simple the classes are. That makes them very easy to test – only the top level one requires complex setup, due to the nature of the dnsjava library. The others are easily tested like so:
@Before public void setUp() throws Exception { delegate = mock(DnsSrvResolver.class); resolver = new RetainingDnsSrvResolver(delegate); nodes1 = nodes("noden1", "noden2"); nodes2 = nodes("noden3", "noden5", "somethingelse"); } @Test public void shouldReturnResultsFromDelegate() throws Exception { when(delegate.resolve(FQDN)).thenReturn(nodes1); assertThat(resolver.resolve(FQDN), equalTo(nodes1)); } @Test public void shouldReturnFreshResultsFromDelegate() throws Exception { when(delegate.resolve(FQDN)) .thenReturn(nodes1) .thenReturn(nodes2); resolver.resolve(FQDN); assertThat(resolver.resolve(FQDN), equalTo(nodes2)); } @Test public void shouldRetainDataIfNewResultEmpty() throws Exception { when(delegate.resolve(FQDN)) .thenReturn(nodes1) .thenReturn(nodes()); resolver.resolve(FQDN); assertThat(resolver.resolve(FQDN), equalTo(nodes1)); } @Test public void shouldRetainDataOnFailure() throws Exception { when(delegate.resolve(FQDN)) .thenReturn(nodes1) .thenThrow(new DnsException("expected")); resolver.resolve(FQDN); assertThat(resolver.resolve(FQDN), equalTo(nodes1)); } // etc..
The fact that the functionality is implemented using several different classes is an implementation detail that shouldn’t leak through to users of the library. (You may have noticed that the implementation classes are package-private.) So to make it easy for clients, I like to provide a simple fluent API, something like this perhaps:
DnsSrvResolver resolver = DnsSrvResolvers.newBuilder() .cachingLookups(true) .metered(true) .retainingDataOnFailures(true) .build();
In summary, the advantages of splitting up behaviours into different classes are:
- Each class becomes really simple, which makes it easy to test them exhaustively. Some of the code above does non-trivial things, but testing it is still easy.
- Since each behaviour lives in a single place only, it’s trivial to, for instance, change the way metrics are recorded, or to replace the underlying DNS library without affecting the rest of the chain.
- Each class becomes more understandable because it is so small. Since behaviours are loosely coupled, the amount of stuff you need to keep ‘in working memory’ to work with a class is minimised.
- Varying behaviour in different situations becomes easier as it becomes a matter of composing different classes rather than having conditional logic in a single class. Basically, only the builder implementation in the API example above needs to worry about the value of ‘metered’. The DnsSrvResolver implementation doesn’t need to do a conditional check on ‘if (metered)’.
Doing this is not completely free, though:
- With more classes, you get more code to search through if you want to find out how, for instance, data is retained. It may be hard to understand the more dynamic run-time object graph that is behind the DnsSrvResolver you’re using. If the whole picture is there statically in code, it’s easier to find it.
- Instead of having complexity within a class, you have (more) complex interactions between instances of different classes. The complexity doesn’t go away entirely, it is moved.
Things that should get you thinking about whether you should split out behaviours into different classes include having too many (> 3-4 or so) collaborators injected into the constructor, or using the word ‘and’ when describing the ‘single’ responsibility of a class. Describing a class as “doing SRV lookups and retaining data and collecting statistics” is a giveaway that you don’t actually think of it as doing one thing.
In my experience, the benefits of using this technique very often outweigh the costs. It is easier to make stable software from a number of small, simple, composable building blocks than to do it using larger, more complex building blocks. Dealing with complexity in how things are put together is easier than trying to compose complex things. Writing code like this doesn’t take longer than writing it in a single class – the only added difficulty is in learning to see when there might be a need to split things out in order to make them simpler.
Fluent APIs in Java
Posted by Petter Måhlén in Java, Software Development on January 15, 2013
In our inventory management system at Shopzilla we’ve got a reporting tool that we’ve built using Wicket. A couple of months ago (this post has languished in draft status for a while), I was going to add a new page and took a look at a previous one to see what I could copy/paste. What I found was this wall of text for creating a table:
List<IColumn<AggregatedFeedRunSummary, String>> columns = Lists.newArrayList(); columns.add(new PropertyColumn<AggregatedFeedRunSummary, String>(new Model("Name"), "merchant.merchantName", "merchant.merchantName")); columns.add(new PropertyColumn<AggregatedFeedRunSummary, String>(new Model("Merchant Id"), "merchant.merchantId", "merchant.merchantId")); columns.add(new PropertyColumn<AggregatedFeedRunSummary, String>(new Model("Feed Id"), "feed.feedId", "feed.feedId")); columns.add(new PropertyColumn<AggregatedFeedRunSummary, String>(new Model("Country"), "merchant.countryCode", "merchant.countryCode")); columns.add(new PropertyColumn<AggregatedFeedRunSummary, String>(new Model("Account Manager"), "merchant.accountManagerName", "merchant.accountManagerName")); columns.add(new PropertyColumn<AggregatedFeedRunSummary, String>(new Model("Tier"), "merchant.keyStatus", "merchant.keyStatus")); columns.add(new PropertyColumn<AggregatedFeedRunSummary, String>(new Model("Revenue Status"), "merchant.revStatus", "merchant.revStatus")); columns.add(new IndexablePropertyColumn<AggregatedFeedRunSummary, String>(new Model("Indexable"), "feed.indexable")); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Proteus Last Run"), PROTEUS, "start_time") .isDate().isLinkTo(RunDetailsPage.class, FEEDRUN_PARAM)); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Feed Changed"), PROTEUS, "feed_changed")); columns.add(new AggregatedFeedRunPropertyColumn(new Model("FQS Last Run"), FQS, "start_time") .isDate().isLinkTo(RunDetailsPage.class, FEEDRUN_PARAM)); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Character encoding"), FQS, "encodingUsed")); columns.add(new CharacterEncodingSourceColumn(new Model("CE Source"), FQS)); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Total Raw Offers"), FQS, "total")); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Invalid"), FQS, "invalid")); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Duplicate"), FQS, "duplicate")); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Valid"), FQS, "valid")); columns.add(new AggregatedFeedRunPropertyColumn(new Model("DI Last Run"), DI, "start_time") .isDate().isLinkTo(RunDetailsPage.class, FEEDRUN_PARAM)); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Total"), DI, "total")); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Created"), DI, "created")); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Updated"), DI, "updated")); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Deleted"), DI, "deleted")); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Unchanged"), DI, "unchanged")); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Resent"), DI, "resent")); columns.add(new AggregatedFeedRunPropertyColumn(new Model("Failed"), DI, "Failed")); columns.add(new DatePropertyColumn<AggregatedFeedRunSummary, String>(new Model("Last DP Snapshot"), "lastSnapshotDate")); columns.add(new PropertyColumn<AggregatedFeedRunSummary, String>(new Model("Viable"), "viable", "viable")); columns.add(new PropertyColumn<AggregatedFeedRunSummary, String>(new Model("Non-viable"), "nonViable", "nonViable")); columns.add(new PropertyColumn<AggregatedFeedRunSummary, String>(new Model("Deleted"), "deleted", "deleted"));
As an aside, calling it a wall of text should in no way be interpreted as criticism of the people who wrote it – on the contrary, I think creating the wall of text was a great engineering decision. More on that later.
The code above is a clear example of what a lot of people really don’t like about Java (me included, though I do like Java as a language overall). The signal-to-noise ratio is horrible, mostly due to all the type parameters. There’s stuff being done to the language to help reduce that, using diamond notation and things, but I think in this case, the problem is not really the language. Saying Java sucks on the basis of the code above is a case of blaming the tools for a mistake that we made ourselves. Here are some things that are wrong with the code above:
- First, there’s a lot of stuff that’s not really relevant. We don’t care about the exact data types that are used as column entries in the Wicket view, but we do care about what the contents of the column are and how they are displayed to the user. So “new AggregatedFeedRunPropertyColumn(new Model(“, etc, is mostly noise except insofar as it tells us where to look for the information we’re after.
- A very similar point is that a lot of the information that is there is not very clear. The fact that the type of the column is a ‘PropertyColumn<AggregatedFeedSummary, String> doesn’t convey much in the way of information unless you’re very familiar with Wicket and the specific data types that we’re using.
- There’s also missing information: as an example, the string “merchant.merchantName” is repeated twice, and it’s not clear why. There is in fact a reason: the first time, it indicates which property value to display in a particular table cell, and the second time, it indicates which property value to use when sorting based on this column.
Using a fluent API, we can express the code above like so:
return FrvColumns.columnsFor(AggregatedFeedRunSummary.class, String.class) .add("Name").sourceProperty("merchant.merchantName").sortable("merchant.merchantName") .add("Merchant Id").sourceProperty("merchant.merchantId").sortable("merchant.merchantId") .add("Feed Id").sourceProperty("feed.feedId").sortable("feed.feedId") .link(FeedRunHistoryPage.class) .param("merchantId").sourceProperty("merchant.merchantId") .param("feedId").sourceProperty("feed.feedId") .add("Country").sourceProperty("merchant.countryCode").sortable("merchant.countryCode") .add("Account Manager").sourceProperty("merchant.accountManagerName").sortable("merchant.accountManagerName") .add("Tier").sourceProperty("merchant.keyStatus").sortable("merchant.keyStatus") .add("Revenue Status").sourceProperty("merchant.revStatus").sortable("merchant.revStatus") .add("Indexable").sourceProperty("feed.indexableAsString") .add("Proteus Last Run").sourceProperty("properties.proteus.start_time").epoch() .link(RunDetailsPage.class) .param(FEEDRUN_PARAM).sourceProperty("feedRunId.id") .param("component").value(PROTEUS.toString()) .add("Feed Changed").sourceProperty("properties.proteus.feed_changed") .add("FQS Last Run").sourceProperty("properties.fqs.start_time").epoch() .link(RunDetailsPage.class) .param(FEEDRUN_PARAM).sourceProperty("feedRunId.id") .param("component").value(FQS.toString()) .add("Character encoding").sourceProperty("encodingInfo.encoding") .add("CE Source").sourceProperty("encodingInfo.source") .add("Total Raw Offers").sourceProperty("properties.fqs.total") .add("Invalid").sourceProperty("properties.fqs.invalid") .add("Duplicate").sourceProperty("properties.fqs.duplicate") .add("Valid").sourceProperty("properties.fqs.valid") .add("DI Last Run").sourceProperty("properties.di.start_time").epoch() .link(RunDetailsPage.class) .param(FEEDRUN_PARAM).sourceProperty("feedRunId.id") .param("component").value(DI.toString()) .add("Total").sourceProperty("properties.di.total") .add("Created").sourceProperty("properties.di.created") .add("Updated").sourceProperty("properties.di.updated") .add("Deleted").sourceProperty("properties.di.deleted") .add("Unchanged").sourceProperty("properties.di.unchanged") .add("Resent").sourceProperty("properties.di.resent") .add("Failed").sourceProperty("properties.di.failed") .add("Last DP Snapshot").sourceProperty("lastSnapshotDate").date() .add("Viable").sourceProperty("viable").sortable("viable") .add("Non-viable").sourceProperty("nonViable").sortable("nonViable") .add("Deleted").sourceProperty("deleted").sortable("deleted") .build();
The key improvements, in my opinion, are:
- Less text. It’s not exactly super-concise, and there’s a lot of repetition of long words like ‘sourceProperty’, and so on. But there’s a lot less text, meaning a lot less noise. And pretty much everything that’s there carries relevant information.
- More clarity in what the various strings represent. I think in both the original and updated versions, most people would guess that the first string is the title of the column. The fluent API version is much clearer about why some of the columns have the same property string repeated – it’s because it indicates that the list should be sortable by that column and which property of the list items should be used for sorting.
- Easier modification – rather than having to figure out what class to use to display a correctly formatted Date or epoch-based timestamp, you just tack on the right transformation method (.date() or .epoch()). Since this is available at your fingertips via command-space, or whatever your favourite IDE uses for completion, it’s very easy to find out what your options are.
- More transparency. The primary example is the AggregatedFeedRunPropertyColumn, which behind the scenes looks for property values in ‘properties.%s.%s’, and adds ‘component=%s’ as a parameter if the thing is a link. This behaviour is visible if you look in the class, but not if you look at the configuration of the columns. With the fluent API, both those behaviours are explicit.
So, in this case a fluent API both reduces noise and adds signal that was missing before. The cost, of course, is that it takes some effort to implement the fluent API: that’s why I think it was a good engineering decision not to do so immediately. The investment in the API will pay off only if there’s enough volume in terms of pages and column definitions that can use it. What’s more, it’s not just hard to know whether or not you should invest in a fluent API up front, it won’t be clear what the API should be until you have a certain volume of pages that use it.
Other big factors that determine whether an investment in a clearer API is going to pay off are the rate of change of the system in question and whether it’s a core component that everyone working on will know well or a more peripheral one that many developers can be expected to touch with only a vague understanding of how it works. In this case, it’s the latter – reporting is the sort of thing that you need to change often to reflect changes in business logic. Also, since you need to report on all features in the system, it’s normally the case that people will need to be able to modify the report views without being experts in how the tool works. That means that ease of understanding is crucial, and that in turn means it’s crucial that it be obvious for people how to make changes so as to avoid having everyone come up with their own solution for the same type of problem.
The standard pattern I tend to use when building a fluent API is to have a Builder class, or in more complex cases like above, a set of them. So in the example above, this is how it starts:
public static <T, S> ColumnsBuilder<T, S> columnsFor(Class rowClass, Class<S> sortClass) { return new ColumnsBuilder<T, S>(); } public static class ColumnsBuilder<T, S> implements IClusterable { private final List<SingleColumnBuilder<T, S>> builders = Lists.newArrayList(); public SingleColumnBuilder<T, S> addColumn(IColumn<T, S> column) { SingleColumnBuilder<T, S> columnBuilder = new SingleColumnBuilder<T, S>(this, column); builders.add(columnBuilder); return columnBuilder; } public SingleColumnBuilder<T, S> add(String label) { SingleColumnBuilder<T, S> columnBuilder = new SingleColumnBuilder<T, S>(this, label); builders.add(columnBuilder); return columnBuilder; } public List<IColumn<T, S>> build() { return ImmutableList.copyOf( Lists.transform(builders, new Function<SingleColumnBuilder<T, S>, IColumn<T, S>>() { @Override public IColumn<T, S> apply(SingleColumnBuilder<T, S> input) { return input.buildColumn(); } })); } }
In this case, the builder hierarchy matches the resulting data structure: The ColumnsBuilder knows how to add a new column to the table (TableBuilder might have been a better name), the SingleColumnBuilder knows how to build a single column(!), and then further down the hiearchy, there are LinkBuilders, ParamBuilders, etc.
I sometimes let a single Builder class implement multiple interfaces, if I want to enforce a particular ‘grammar’ to the API. So, for instance, if I want code to look like this:
ActionChain actions = Actions.first().jump().onto(platform).then().climb().onto(couch).build();
I might go with something like this:
public interface NeedsVerb { public NeedsTarget jump(); public NeedsTarget climb(); } public interface NeedsTarget { public CanBeBuilt onto(Target target); } public interface CanBeBuilt { public NeedsVerb then(); public ActionChain build(); } public class Actions { public NeedsVerb first() { return new Builder(); } static class Builder implements NeedsVerb, NeedsTarget, CanBeBuilt { private final List actions = new LinkedList(); private Verb verb = null; @Override public NeedsTarget jump() { verb = Verb.JUMP; return this; } // .. same for climb() @Override public CanBeBuilt onto(Target target) { actions.add(new Action(verb, target)); return this; } @Override public NeedsVerb then() { verb = null; // whatever, this is just an example ;) return this; } @Override public ActionChain build() { return new ActionChain(actions); } } }
Some points to note:
- I usually don’t bother too much with things like immutability and thread-safety in the Builders. They’re “internal”, and generally only used in single-threaded, initialisation-type contexts. I would worry very much about those things in the Action and ActionChain classes.
- I usually spend more time on consistency checks and error-reporting than in the example above. The point of building a fluent API is usually to make life easier for somebody who is not an expert (and shouldn’t have to be) in the internal workings of the exposed functionality. So making it very easy to set things up correctly and to understand what is wrong when things have not been set up correctly are key.
In summary, fluent APIs are, in my opinion, often a great choice to improve code readability – and this is of course language independent, though particularly noticeable in a verbose language like Java. The main drawback is the up-front implementation overhead, but since developers spend much more time trying to understand old code than write new, that implementation cost is often insignificant compared to the improvement in reading time. This is particularly true when a) the code calling the API changes very often, b) many developers need to use the API, and c) the code behind the API is outside of developers’ main focus. As always, one of the best sources of great code is Guava, so for some first-class fluent API implementations, take a look at for instance FluentIterable and CacheBuilder. I think fluent APIs are underused in ‘normal business logic’; they don’t need to be solely the domain of snazzy library developers.
BDD using Lettuce vs Cucumber-JVM
Posted by Petter Måhlén in Software Development on May 23, 2012
I’ve now been working for more than 8 months on a project where we’ve been using BDD and creating high-level tests written in Gherkin before actually going on to implement the features themselves. It’s the first time I’m using BDD, so it’s been very interesting. The main conclusion is that it’s a very useful thing to do – writing a high level test really helps me determine how things should work, and I think it makes the implementation cleaner. I can’t say I have conclusive evidence that we’ve got fewer regression bugs as a result of our BDD tests, but at least we’ve got comprehensive regression/acceptance tests that are fully automated. Another BDD reflection is that I think it’ll take me some more time before I really feel I understand it; I’m still not sure I get things like how best to structure the testing code from the perspective of making tests very robust to changes in features. That may be a topic for a future blog post, if and when I start feeling I have some insight into that.
So, BDD is a good thing, as far as I am concerned. This post is not about that; it’s about our experiences of using two different frameworks to execute the Gherkin tests: a Python-based one called Lettuce and Cucumber-JVM, using Java for the step definitions. We evaluated both during our project initiation, and made the decision to go with Lettuce. The primary reason was that we’re using Python for a lot of our automated testing company-wide, and it was felt that it would be easier to move from project to project if we kept to the same language. Cucumber-JVM wasn’t actually released yet at the time, but it felt stable (and the code is really well written) and was close to a release, so that didn’t factor heavily in the decision. For me, being essentially a Java-only developer, I felt that was an OK decision. I felt I would personally be more productive using Java than Python, and that Cucumber-JVM integrated better with our Maven-centric builds, but that it would be interesting to have a good excuse learn a new language.
Now, a few months down the line, we’ve made the decision to stop developing new tests using Lettuce and instead use Cucumber-JVM for new features. (More precisely, we will keep doing our big end-to-end tests using Lettuce, since we’ve got some really nice infrastructure code written in Python that helps us manage deployments and stuff, but the single-service tests will be Cucumber-JVM from now on).
The reasons for this decision are as outlined below – note that it’s hard for me having done about 20 times more Java development than Python to be completely objective about the languages as languages. I’ve tried to be explicit about those points that are clearly subjective.
- In my personal opinion, Python as a language lends itself better to ‘small, quick’ types of problems. Throughout the last few months, I’ve repeatedly been impressed by how often ‘just trying something’ in Python leads to the behaviour I was looking for. Also, since it is interpreted, experimenting can be really quick. However, in the case of running BDD tests, you’re actually not working on a ‘small, quick’ type of problem. Frequently, running the tests takes minutes because they require starting/restarting services, setting up databases, etc. That means that mistakes that the Java compiler/IDE would have told you about as you typed them can take a minute or so to discover, and that’s not great at all. I think dynamic languages require quick turnaround to give you any benefits from the reduced amount of typing you have to do to write code in them, and BDD doesn’t feel like a problem that lends itself to quick turnaround times.
- It’s hard to troubleshoot tests that are run via Lettuce. You can’t debug Python, and something (nose or Lettuce itself, I’m not sure) takes over the standard out, which means it’s hard to print out debug output. In our current implementation, we’re getting assertion errors divorced from the debug output, which makes it quite awkward to figure out what was the actual issue.
- We’ve had to spend a lot of effort on getting the necessary infrastructure together that allows us to do things like share code between BDD tests for different services, ensure that different environments run the same libraries, that we don’t have to install too many dependencies locally before being able to run a build on a given machine, etc. So we’re using things like pip to download and install dependencies, virtualised buildouts to ensure that we actually have the right versions of stuff without conflicting with the machine-global Python installation, and an internal cheeseshop for distributing our internal test utilities code. This is stuff that basically comes for free and in a better implementation when using Maven. When I say ‘better implementation’, I’m talking particularly about the dependency management, where Maven’s versioning of libraries feels like it is more mature and works better than Python eggs do.
- Since it’s possible to run Cucumber-JVM tests via JUnit, they slot directly into all the existing infrastructure around build tools and processes. This means that you can immediately view test results from the BDD tests in the same way as your unit tests, which is very useful (although there are still issues). For our Lettuce tests, on the other hand, we’ve had to hand-craft tools to plug in the test execution and result notification into Maven. And while it should be possible, we haven’t gotten around to actually integrating the output of those tools fully into Jenkins, Sonar, etc. This integration again just comes for free with Cucumber-JVM.
- Another advantage of having the tests runnable via JUnit is that it is trivial to run them from inside the IDE, which means you can use a debugger to do troubleshooting, that you can easily select which ones to run, etc.
- Finally, although we have no data to confirm this, it feels like running tests with Cucumber-JVM is faster than running via Lettuce. And that’s important as it makes it less painful to run the BDD tests more frequently. (EDIT: see comment below for some actual data to support this point)
For me, as an experienced Java hand and far from a polyglot programmer, it’s been very interesting to spend a few months with another language. I think I can safely summarise most of the advantages of Cucumber-JVM over Lettuce as coming from “Java vs Python as a platform” rather than “Java vs Python as a language” – with some implementation choices thrown in there and the fact that BDD as a problem doesn’t lend itself to quick turnaround, which makes static analysis more valuable.
In general, BDD has been a great experience so far, as has Cucumber-JVM and to a lesser degree Python. But from my perspective, Lettuce isn’t a great tool for writing BDD tests for Maven-based projects.
Exceptional iterators using Guava
Posted by Petter Måhlén in Software Development on April 23, 2012
Some years ago, I came across a little gem of a blog post about using a class called AbstractIterator that I hadn’t heard of before. I immediately loved it, and started using it, and in general, the APIs defined in the JDK collections classes a lot more frequently. I’m still doing that today, but for the first time, I came across a situation where AbstractIterator (nowadays in Guava, not Google Collections, of course) let me down.
In the system I’m currently working on, we need to process tens of thousands of feeds that are generated ‘a little randomly’, often by people without deep technical skills. This means we cannot know for sure that entries in the feeds will be consistent; we may be able to parse the first four entries, but not the fifth, and so on. The quantity of feeds, combined with the fact that we need to be forgiving of less-than-perfect input means that we need to be able to continue ingesting data even if a few entries are bad.
Ideally, we wanted to write code similar to this:
Iterator iterator = feedParser.iterator(); while (iterator.hasNext()) { try { FeedEntry entry = iterator.next(); // ... process the entry } catch (BadEntryException e) { // .. track the error and continue processing } }
Now, we also wanted to use AbstractIterator as the base class for the parser to use, maybe something like:
class FeedEntryIterator extends AbstractIterator<FeedEntry> { protected FeedEntry computeNext() { if (noMoreInput()) { return endOfData(); } try { return parseInput(); } catch (ParseException e) { throw new BadEntryException(e); } } }
This, however, doesn’t work for two reasons:
- The BadEntryException will be thrown during the execution of hasNext(), since the AbstractIterator class calls the computeNext() method to check if any more data is available, storing the result in an intermediate member field until the next() method is called.
- If the computeNext() method throws an exception, the AbstractIterator is left in a state FAILED, from which it cannot recover.
One option to work around this that I thought of was delegation. Something like this works for many scenarios:
public class ForwardingTransformingIterator<F, T> implements Iterator<T> { private final Iterator<F> source; private final Function<F, T> transformer; public ForwardingThrowingIterator(Iterator<F> source, Function<V, T> transformer) { this.delegate = delegate; this.transformer = transformer; } @Override public boolean hasNext() { return delegate.hasNext(); } @Override public T next() { return transformer.apply(delegate.next()); } }
The transformation function could then safely throw exceptions for entries it fails to parse. There’s even a shorthand for the above code in Guava: Iterators.transform(). The only issue is that the delegate iterator needs to return chunks of data that are the same size as what the parser/transform needs. So if the parser needs to be, for instance, something like a SAX parser, you’re in trouble. When I had gotten this far, I figured the only solution was to modify the AbstractIterator class so that it can deal with exceptions directly. The source is a little too long to include in an already source-heavy post, but there’s a gist here. The essence of the change is to make the base class store exceptions of a particular type that are thrown by computeNext(), and re-throw them instead of returning a value when next() is called. It kind of works (we’re using this implementation right now), but it would be nice to find a better solution.
I’ve created a Guava issue for this, so if you think this would be useful, go ahead and vote for it. And if you have suggestions for how to improve the code, feel free to make them here or at code.google.com!
Don’t use @Test(expected = …)
Posted by Petter Måhlén in Software Development on February 7, 2012
The one feature of JUnit that I really don’t like is the ability to say that a test is expected to throw a particular exception. Here’s an example:
public class MyClass { private final Something something; public MyClass(Something something) { this.something = something; } public void doWhatever(String aString) { something.useString(aString); } } ... public class MyClassTest { @Test(expected = NullPointerException.class) public void shouldThrowNullPointerExceptionWhenStringIsNull() throws Exception { MyClass myClass = new MyClass(null); myClass.doWhatever(null); }
This example is not as bad as they can be, but it illustrates the point. The method call will indeed throw a NullPointerException, but the reason won’t be that the string parameter is null – the NPE is thrown because the ‘something’ member is null. The general problem with the ‘expected’ parameter to the @Test annotation is that you don’t know which particular case led to the exception, and general exceptions such as NullPointerException (or, worse RuntimeException, or perhaps some generic exception you use for wrapping all kinds of checked exception in some code) can be thrown for many reasons.
I very much prefer code along these lines:
@Test public void throwsExceptionWithMeaningfulInformation() throws Exception { IOException expected = new IOException("expected"); when(collaborator).method("some parameter").thenThrow(expected); try { classUnderTest.methodThatCallsCollaborator("some parameter"); fail("expected exception"); } catch (WrappingException e) { assertThat(e.getCause(), equalTo((Throwable) expected)); assertThat(e.getMessage(), containsString("some parameter")); assertThat(e.getMessage(), containsString("something else the code should report")); } }
EDIT: based on Dominic’s comment, I now normally prefer the idiom below – the rest of the post still applies, but using @Rule and ExpectedException is more concise and readable. The only thing missing is an easily accessible Matcher<Throwable> that would allow you to match the cause as well. I’ll probably create one, for internal use, at least.
EDIT: JUnit 4.11-SNAPSHOT now has support for cause matching using ExpectedException. :)
@Rule public ExpectedException thrown = ExpectedException.none(); @Test public void throwsNPEIfSecondParameterIsNull() throws Exception { thrown.expect(NullPointerException.class); thrown.expectMessage("secondParameter"); new ClassThatDisallowsNullForTheSecondParameter("hi", null); }
Both these idioms give you the ability to verify that you’ve in fact triggered the error you wanted to trigger, and it also gives you the ability to ensure that the code is reporting the problem in a good way by checking the message and cause of the thrown exception. Sure, it requires a little more typing than just using the @Test annotation, but you get that time back the first time that exception is actually thrown and somebody needs to figure out what went wrong. Both of the 2 minutes it took to do the extra typing!
Bygg – Executing the Build
Posted by Petter Måhlén in Java on March 15, 2011
This is the third post about Bygg – I’ve been fortunate enough to find some time to do some actual implementation, and I now have a version of the tool that can do the following:
First, read a configuration of plugins to use during the build:
public class PluginConfiguration { public static Plugins plugins() { return new Plugins() { public List plugins() { return Arrays.asList( new ArtifactVersion(ProjectArtifacts.BYGG_TEST_PLUGIN, "1.0-SNAPSHOT"), new ArtifactVersion(ProjectArtifacts.GUICE, "2.0")); } }; } }
Second, use that set of plugins to compile a project configuration:
public class Configuration { public static ByggConfiguration configuration() { return new ByggConfiguration() { public TargetDAG getTargetDAG() { return TargetDAG.DEFAULT .add("plugin") // defines the target name when executing .executor(new ByggTestPlugin()) // indicates what executing the target means .requires("test") // means it won't be run until after "test" .build(); } }; } }
Third, actually execute that build – although in the current version, none of the target executors have an actual implementation, so all they do is create a file with their name and the current time stamp under the target directory. The default build graph that is implemented contains targets that pretend to assemble the classpaths (placeholder for downloading any necessary dependencies) for the main code and the test code, targets that compile the main and test code, a target that runs the tests, and a target that pretends to create a package. As the sample code above hints, I’ve got three projects: Bygg itself, a dummy plugin, and a test project whose build requires the test plugin to be compiled and executed.
Fourth – with no example – cleaning up the target directory. This is the only feature that is fully implemented, being of course a trivial one. On my machine, running a clean in a tiny test project is 4-5 times faster using Bygg than Maven (taking 0.4 to 0.5 seconds of user time as compared to more than 2 for Maven), so thus far, I’m on target with regard to performance improvements. A little side note on cleaning is that I’ve come to the conclusion that clean isn’t a target. You’re never ever going to want to deploy a ‘clean’, or use it for anything. It’s an optional step that might be run before any actual target. To clarify that distinction, you specify targets using their names as command line arguments, but cleaning using -c or –clean:
bygg.sh -c compile plugin
As is immediately obvious, there’s a lot of rough edges here. The ones I know I will want to fix are:
- Using annotations (instead of naming conventions) and generics (for type safety) in the configuration classes – I’m compiling and loading the configuration files using a library called Janino, which has an API that I think is great, but which by default only supports Java 4. There’s a way around it, but it seems a little awkward, so I’m planning on stealing the API design and putting in a front for JavaCompiler instead.
- Updating the returned classes (Plugins and ByggConfiguration), as today they only contain a single element. Either they should be removed, or perhaps they will need to become a little more powerful.
- Changing the names of stuff – TargetDAG especially is not great.
- There’s a lot of noise, much of which is due to Java as a language, but some of which can probably be removed. The Plugins example above is 11 lines long, but only 2 lines contain useful information – and that’s not counting import statements, etc. Of course, since the number of ‘noise lines’ is pretty much constant, with realistically large builds, the signal to noise ratio will improve. Even so, I’d like it to be better.
- I’m pretty sure it’s a good idea to move away from saying “this target requires target X” to define the order of execution towards something more like “this target requires the compiled main sources”. But there may well be reasons why you would want to keep something like “requires” or “before” in there – for instance, you might want to generate a properties file with information collected from version control and CI system before packaging your artifact. Rather than changing the predefined ‘package’ target, you might want to just say ‘run this before package’ and leave the file sitting in the right place in the target directory. I’m not quite sure how best to deal with that case yet – there’s a bit more discussion of this a little later.
Anyway, all that should be done in the light of some better understanding of what is actually needed to build something. So before I continue with improving the API, I want to take a few more steps on the path of execution.
As I’ve mentioned in a previous post, a build configuration in Bygg is a DAG (Directed Acyclic Graph). A nice thing about that is that it opens up the possibility of executing independent paths on the DAG concurrently. Tim pointed out to me that that kind of concurrent execution is an established idea called Dataflow Concurrency. In Java terms, Dataflow Concurrency essentially boils down to communicating all shared mutable state via Futures (returned by Callables executing the various tasks). What’s interesting about the Bygg version of Dataflow Concurrency is that the ‘Dataflow Variables’ can and will be parameters of the Callables executing tasks, rather than being hard-coded as is typical in the Dataflow Concurrency examples I’ve seen. So the graph will exist as a data entity as opposed to being hardwired in the code. This means that deadlock detection is as simple as detecting cycles in the graph – and since there is a requirement that the build configuration must be a DAG, builds will be deadlock free. In general, I think the ability to easily visualise the exact shape of the DAG of a build is a very desirable thing in terms of making builds easily understandable, so that should probably be a priority when continuing to improve the build configuration API.
Another idea I had from the reference to dataflow programming is that the canonical example of dataflow programming is a spreadsheet, where an update in one cell trickles through into updates of other cells that contain formulas that refer to the first one. That example made me change my mind about how different targets should communicate their results to each other. Initially, I had been thinking that most of the data that needs to be passed on from one target to the next should be implicitly located in a well-defined location on disk. So the test compiler would leave the test classes in a known place where the test runner knows to look for them. But that means loading source files into memory to compile them, then writing the class files to disk, then loading them into memory again. That’s a lot of I/O, and I have the feeling that I/O is often one of the things that slows builds down the most. What if there would be a dataflow variable with the classes instead? I haven’t yet looked in detail at the JavaFileManager interface, but it seems to me that it would make it possible to add an in-memory layer in front of the file system (in fact, I think that kind of optimisation is a large part of the reason why it exists). So it could be a nice optimisation to make the compiler store files in memory for test runners, packagers, etc., to pick up without having to do I/O. There would probably have to be a target (or something else, maybe) that writes the class files to disk in parallel with the rest of the execution, since the class files are nice to have as an optimisation for the next build – only recompiling what is necessary. But that write doesn’t necessarily have to slow down the test execution. All that is of course optimisation, so the first version will just use a plain file system-based JavaFileManager implementation. Still, I think it is a good idea to only have a very limited number of targets that directly access the file system, in order to open up for that kind of optimisation. The remainder of the targets should not be aware of the exact structure of the target directory, and what data is stored there.
I’m hoping to soon be able to find some more time to try these ideas out in code. It’ll be interesting to see how hard it is to figure out a good way to combine abstracting away the ‘target’ directory with full freedom for plugins to add stuff to the build package and dataflow concurrency variables.
Bygg – Better Dependency Management
Posted by Petter Måhlén in Java on February 17, 2011
I’ve said before that the one thing that Maven does amazingly well is dependency management. This post is about how to do it better.
Declaring and Defining Dependencies
In Maven, you can declare dependencies using a <dependencyManagement /> section in either the current POM or some parent POM, and then use them – include them into the current build – using a <dependencies /> section. This is very useful because it allows you to define in a single place which version of some dependency should be used for a set of related modules. It looks something like:
<dependencyManagement> <dependencies> <dependency> <groupId>com.shopzilla.site2.service</groupId> <artifactId>common</artifactId> <version>${service.common.version}</version> </dependency> <dependency> <groupId>com.shopzilla.site2.service</groupId> <artifactId>common-web</artifactId> <version>${service.common.version}</version> </dependency> <dependency> <groupId>com.shopzilla.site2.core</groupId> <artifactId>core</artifactId> <version>${core.version}</version> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>com.shopzilla.site2.service</groupId> <artifactId>common</artifactId> </dependency> <dependency> <groupId>com.shopzilla.site2.core</groupId> <artifactId>core</artifactId> </dependency> </dependencies>
To me, there are a couple of problems here:
- Declaring a dependency looks pretty much identical to using one – the only difference is that the declaration is enclosed inside a <dependencyManagement /> section. This makes it hard to know what it is you’re looking at if you have a large number of dependencies – is this declaring a dependency or actually using it?
- It’s perfectly legal to add a <version/> tag in the plain <dependencies /> section, which will happen unless all the developers touching the POM a) understand the distinction between the two <dependencies /> sections, and b) are disciplined enough to maintain it.
For large POMs and POM hierarchies in particular, the way to define shared dependencies becomes not only overly verbose but also hard to keep track of. I think it could be made much easier and nicer in Bygg, something along these lines:
// Core API defined in Bygg proper public interface Artifact { String getGroupId(); String getArtifactId(); } // ---------------------------------------------- // This enum is in some shared code somewhere - note that the pattern of declaring artifacts // using an enum could be used within a module as well if desired. You could use different enums // to define, for instance, sets of artifacts to use when talking to databases, or when developing // a web user interface, or whatever. public enum MyArtifacts implements Artifact { SERVICE_COMMON("com.shopzilla.site2.service", "common"), CORE("com.shopzilla.site2.core", "core"); JUNIT("junit", "junit"); } // ---------------------------------------------- // This can be declared in some shared code as well, probably but not // necessarily in the same place as the enum. Note that the type of // the collection is Collection, indicating that it isn't ordered. public static final Collection ARTIFACT_VERSIONS = ImmutableList.of( new ArtifactVersion(SERVICE_COMMON, properties.get("service.common.version")), new ArtifactVersion(CORE, properties.get("core.version")), new ArtifactVersion(JUNIT, "4.8.1")); // ---------------------------------------------- // In the module to be built, define how the declared dependencies are used. // Here, ordering might be significant (it indicates the order of artifacts on the // classpath - the reason for the 'might' is I'm not sure if this ordering can carry // over into a packaged artifact like a WAR). List moduleDependencies = ImmutableList.of( new Dependency(SERVICE_COMMON, EnumSet.of(MAIN, PACKAGE)), new Dependency(CORE, EnumSet.of(MAIN, PACKAGE)), new Dependency(JUNIT, EnumSet.of(TEST)));
The combination of a Collection of ArtifactVersions and a List of Dependency:s is then used by the classpath assembler target to produce an actual classpath for use in compiling, running, etc. Although the example code shows the Dependency:s as a plain List, I kind of think that there may be value in not having the actual dependencies be something else. Wrapping the list in an intelligent object that gives you filtering options, etc., could possibly be useful, but it’s a bit premature to decide about that until there’s code that makes it concrete.
The main ideas in the example above are:
- Using enums for the artifact identifiers (groupId + artifactId) gives a more succinct and harder-to-misspell way to refer to artifacts in the rest of the build configuration. Since you’re editing this code in the IDE, finding out exactly what the artifact identifier means (groupId + artifactId) is as easy as clicking on it while pressing the right key.
- If the build configuration is done using regular Java code, shared configuration items can trivially be made available as Maven artifacts. That makes it easy to for instance have different predefined groups of related artifacts, and opens up for composed rather than inherited shared configurations. Very nice!
- In the last bit, where the artifacts are actually used in the build, there is an outline of something I think might be useful. I’ve used Maven heavily for years, and something I’ve never quite learned how they work is the scopes. It’s basically a list of strings (compile, test, provided, runtime) that describe where a certain artifact should be used. So ‘compile’ means that the artifact in question will be used when compiling the main source code, when running tests, when executing the artifact being built and (if it is a WAR), when creating the final package. I think it would be far simpler to have a set of flags indicating which classpaths the artifact should be included in. So MAIN means ‘when compiling and running the main source’, TEST is ditto for the test source, and PACKAGE is ‘include in the final package’, and so on. No need to memorise what some scope means, you can just look at the set of flags.
Another idea that I think would be useful is adding an optional Repository setting for an Artifact. With Maven, you can add repositories in addition to the default one (Maven Central at Ibiblio). You can have as many as you like, which is great for some artifacts that aren’t included in Maven Central. However, adding repositories means slowing down your build by a wide margin, as Maven will check each repository defined in the build for updates to each snapshot version of an artifact. Whenever I add a repository, I do that to get access to a specific artifact. Utilising that fact by having two kinds of repositories – global and artifact-specific, maybe – should be simple and represent a performance improvement.
Better Transitive Conflict Resolution
Maven allows you to not have to worry about transitive dependencies required by libraries you include. This is, as I’ve argued before, an incredibly powerful feature. But the thing is, sometimes you do need to worry about those transitive dependencies: when they introduce binary incompatibilities. A typical example is something I ran into the other day, where a top-level project used version 3.0.3 of some Spring jars (such as ‘spring-web’), while some shared libraries eventually included another version of Spring (the ‘spring’ artifact, version 2.5.6). Both of these jars contain a definition of org.springframework.web.context.ConfigurableWebApplicationContext, and they are incompatible. This leads to runtime problems (in other words, the problem is visible too late; it should be detected at build time), and the only way to figure that out is to recognise the symptoms of the problem as a “likely binary version mismatch”, then use mvn dependency:analyze to figure out possible candidates and add exclude rules like this to your POM:
<dependencyManagement> <dependencies> <dependency> <groupId>com.shopzilla.site2.service</groupId> <artifactId>common</artifactId> <version>${service.common.version}</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> </exclusion> <exclusion> <groupId>com.google.collections</groupId> <artifactId>google-collections</artifactId> </exclusion> </exclusions> </dependency> <dependency> <groupId>com.shopzilla.site2.core</groupId> <artifactId>core</artifactId> <version>${core.version}</version> <exclusions> <exclusion> <groupId>org.springframework</groupId> <artifactId>spring</artifactId> </exclusion> <exclusion> <groupId>com.google.collections</groupId> <artifactId>google-collections</artifactId> </exclusion> </exclusions> </dependency> </dependencies> </dependencyManagement>
As you can tell from the example (just a small part of the POM) that I pasted in, I had a similar problem with google-collections. The top level project uses Guava, so binary incompatible versions included by the dependencies needed to be excluded. The problems here are:
- It’s painful to figure out what libraries cause the conflicts – sometimes, you know or can easily guess (like the fact that different versions of Spring packages can clash), but other times you need to know something a little less obvious (like the fact that Guava has superseded google-collections, something not immediately clear from the names). The tool could just tell you that you have binary incompatibilities on your classpath (I actually submitted a patch to the Maven dependency plugin to fix that, but it’s been stalled for 6 months).
- Once you’ve figured out what causes the problem, it’s a pain to get rid of all the places it comes from. The main tool at hand is the dependency plugin, and the way to figure out where dependencies come from is mvn dependency:tree. This lets you know a single source of a particular dependency. So for me, I wanted to find out where the spring jar came from – that meant running mvn dependency:tree, adding an exclude, running it again to find where else the spring jar was included, adding another exclude, and so on. This could be so much easier. And since it could be easier, it should be.
- What’s more, the problems are sometimes environment-dependent, so you’re not guaranteed that they will show up on your development machine. I’m not sure about the exact reasons, but I believe that there are differences in the order in which different class loaders load classes in a WAR. This might mean that the only place you can test if a particular problem is solved or not is your CI server, or some other environment, which again adds pain to the process.
- The configuration is rather verbose and you need to introduce duplicates, which makes your build files harder to understand at a glance.
Apart from considering binary incompatibilities to be errors (and reporting on exactly where they are found), here’s how I think exclusions should work in Bygg:
dependencies.exclude().group("com.google.collections").artifact("google-collections") .exclude().group("org.springframework").artifact("spring.*").version(except("3.0.3"));
Key points above are:
- Making excludes a global thing, not a per-dependency thing. As soon as I’ve identified that I don’t want spring.jar version 2.5.6 in my project, I know I don’t want it from anywhere at all. I don’t care where it comes from, I just don’t want it there! I suppose there is a case for saying “I trust the core library to include google-collections for me, but not the common-web one”, so maybe global excludes aren’t enough. But they would certainly have helped me tremendously a lot of the times I’ve had to use Maven exclusions, and I can’t think of a case where I’ve actually wanted specifically to have an artifact-specific exclusion.
- Defining exlusion filters using a fluent API that includes regular expressions. With Spring in particular, you want to make sure that all your jars have the same version. It would be great to be able to say that you don’t want anything other than that.
Build Java with Java?!
I’ve gone through different phases when thinking about using Java to configure builds rather than XML. First, I thought “it’s great, because it allows you to more easily use the standard debugger for the builds and thereby resolve Maven’s huge documentation problem”. But then I realised that the debugging is enabled by ensuring that the IDE has access to the source code of the build tool and plugins that execute the build, and that how you configure it is irrelevant. So then I thought that using Java to configure is pretty good anyway, because it means developers won’t need to learn a new language (as with Buildr or Raven), and that IDE integration is a lot easier. The IDE you use for your regular Java programming wouldn’t need to be taught anything specific to deal with some more Java code. I’ve now come to the conclusion that DSL-style configuration APIs, and even more, using the standard engineering principles for sharing and reusing code for build configurations is another powerful argument in favour of using Java in the build configuration. So I’ve gone from “Java configuration is key”, to “Java configuration is OK, but not important” to “Java configuration is powerful”.
Composition vs Inheritance
Posted by Petter Måhlén in Java on August 20, 2010
One of the questions I often ask when interviewing candidate programmers is “what is the difference between inheritance and composition”. Some people don’t know, and they’re obviously out right then and there. Most people explain that “inheritance represents an is-a relationship and composition a has-a”, which is correct but insufficient. Very few people have a good answer to the question that I’m really asking: what impact does the choice of one or the other have on your software design? The last few years it seems there has been a trend towards feeling that inheritance is over-used and composition to a corresponding degree under-used. I agree, and will try to show some concrete reasons why that is the case. (As a side note, the main trigger for this blog post is that about a year ago, I introduced a class hierarchy as a part of a refactoring story, and it was immediately obvious that it was an awkward solution. However, it (and the other changes) represented a big improvement over what went before, and there wasn’t time to fix it back then. It’s bugged me ever since, and thanks to our gradual refactoring, we’re getting closer to the time when we can put something better in place, so I started thinking about it again.)
One guy I interviewed the other day had a pretty good attempt at describing when one should choose one or the other: it depends on which is closest to the real world situation that you’re trying to model. That has the ring of truth to it, but I’m not sure it is enough. As an example, take the classically hierarchical model of the animal kingdom: Humans and Chimpanzees are both Primates, and, skipping a bunch of steps, they are Animals just like Kangaroos and Tigers. This is clearly a set of is-a relationships, so we could model it like so:
public class Animal {} public class Primate extends Animal {} public class Human extends Primate {} public class Chimpanzee extends Primate {} public class Kangaroo extends Animal {} public class Tiger extends Animal {}
Now, let’s add some information about their various body types and how they walk. Both Humans and Chimpanzees have two arms and two legs, and Tigers have four legs. So we could add two legs and two arms at the Primate level, and four legs at the Tiger level. But then it turns out that Kangaroos also have two legs and two arms, so this solution doesn’t work without duplication. Perhaps a more general concept involving limbs is needed at the animal level? There would still be duplication of the ‘legs + arms’ classification in both Kangaroo and Primate, not to mention what would happen if we introduce a RattleSnake into our Eden (or an EarthWorm if you prefer an animal that has never had limbs at any stage of its evolutionary history).
A similar problem is shown by introducing a walk() method. Humans do a bipedal walk, Chimpanzees and Tigers walk on all fours, and Kangaroos hop using their rear feet and tail. Where do we locate the logic for movement? All the animals in our sample hierarchy can walk on all fours (although humans tend to do so only when young or inebriated), so having a quadrupedWalk() method at the Animal level that is called by the walk() methods of Tiger and Chimpanzee makes sense. At least until we add a Chicken or Halibut, because they will now have access to a type of locomotion they shouldn’t.
The root problem here is of course that the model is bad even though it seems like a natural one. If you think about it, body types and locomotion are not defined by the various creatures’ positions in the taxonomy – there are both mammals and fish that swim, and both fish, lizards and snakes can be legless. I think it is natural for us to think in terms of templates and inherited properties from a more general class (Douglas Hofstadter said something very similar, I think in Gödel, Escher, Bach). If that kind of thinking is a human tendency, that could explain why it seems so common to initially think that a hierarchical model is the best one. It is often a mistake to assume that the presence of an is-a relationship in the real world means that inheritance is right for your object model.
But even if, unlike the example above, a hierarchical model is a suitable one to describe a certain real-world scenario, there are issues. A big one, in my opinion, is that the classes that make up a hierarchical model are very tightly coupled. You cannot instantiate a class without also instantiating all of its superclasses. This makes them harder to test – you can never ‘mock’ a superclass, so each test of a leaf in the hierarchy requires you to do all the setup for all the classes above. In the example above, you might have to set up the limbs of your RattleSnake and the data needed for the quadrupedWalk() method of the Halibut before you could test them.
With inheritance, the smallest testable unit is larger than it is when composition is used and you increase code duplication in your tests at least. In this way, inheritance is similar to use of static methods – it removes the option to inject different behaviour when testing. The more code you try to reuse from the superclass/es, the more redundant test code you will typically get, and all that redundancy will slow you down when you modify the code in the future. Or, worse, it will make you not test your code at all because it’s such a pain.
There is an alternative model, where each of the beings above have-a BodyType and possibly also have-a Locomotion that knows how to move that body. Probably, each of the animals could be differently configured instances of the same Animal class.
public interface BodyType {} public TwoArmsTwoLegs implements BodyType {} public FourLegs implements BodyType {} public interface Locomotion<B extends BodyType> { void walk(B body); } public class BipedWalk implements Locomotion<TwoArmsTwoLegs> { public void walk(TwoArmsTwoLegs body) {} } public class Slither implements Locomotion<NoLimbs> { public void walk(NoLimbs body) {} } public class Animal { BodyType body; Locomotion locomotion; } Animal human = new Animal(new TwoArmsTwoLegs(), new BipedWalk());
This way, you can very easily test the bodies, the locomotions, and the animals in isolation of each other. You may or may not want to do some automated functional testing of the way that you wired up your Human to ensure that it behaves like you want it to. I think composition is almost universally preferable to inheritance for code reuse – I’ve tried to think of cases where the only natural model is a hierarchical one and you can’t use composition instead, but it seems to be beyond me.
In addition to the arguments here, there’s the well-known fact that inheritance breaks encapsulation by exposing subclasses to implementation details in the superclass. This makes the use of subclassing as a means to integrate with a library a fragile solution, or at least one that limits the future evolution of the library.
So, there it is – that summarises what I think are the differences between composition and inheritance, and why one should tend to prefer composition over inheritance. So, if you’re reading this because you googled me before an interview, this is one answer you should get right!
Finding Duplicate Class Definitions Using Maven
Posted by Petter Måhlén in Software Development on August 5, 2010
If you have a largish set of internal libraries with a complex dependency graph, chances are you’ll be including different versions of the same class via different paths. The exact version of the class that gets loaded seems to depend on the combination of JVM, class loader and operating system that happens to be used at the time. This can cause builds to fail on some systems but not others and is quite annoying. When this has been happening to me, it’s usually been for one of two reasons:
- We’ve been restructuring our internal artifacts, and something was moved from artifact A to B, only the project in question is still on a version of artifact A that is “pre-removal”. This often leads to binary incompatibilities if the class has evolved since being moved to artifact B.
- Two artifacts in the dependency graph have dependencies on artifacts that, while actually different as artifacts, contain class files for the same class. This can typically happen with libraries that provide jar distributions that include all dependencies, or where there are distributions that are partial or full.
On a couple of previous occasions, when trying to figure out how duplicate class definitions made it into projects I’ve been working on, I’ve gone through a laborious manual process to list class names defined in jars, and see which ones are repeated in more than one. I thought that a better option might be to see if that functionality could be added into the Maven dependency plugin.
My original idea was to add a new goal, something like ‘dependency:duplicate-classes’, but when looking a little more closely at the source code of the dependency plugin, I found that the dependency:analyze goal had all the information needed to figure out which classes are defined more than once. So I decided to make a version of the maven-dependency-plugin where it is possible to detect duplicate class definitions using ‘mvn dependency:analyze’.
The easiest way to run the updated plugin is like this:
mvn dependency:analyze -DcheckDuplicateClasses
The output if duplicate classes are found is something like:
[WARNING] Duplicate class definitions found: [WARNING] com.shopzilla.common.data.ObjectFactory defined in: [WARNING] com.shopzilla.site.url.c14n:model:jar:1.4:compile [WARNING] com.shopzilla.common.data:data-model-schema:jar:1.23:compile [WARNING] com.shopzilla.site.category.CategoryProvider defined in: [WARNING] com.shopzilla.site2.sasClient:sas-client-core:jar:5.47:compile [WARNING] com.shopzilla.site2.service:common-web:jar:5.50:compile
If you would like to try the updated plugin on your project, here’s how to do it:
- Get the forked code for the dependency analyzer goal from http://github.com/pettermahlen/maven-dependency-analyzer-fork and install it in your local Maven repo by running ‘mvn install’. (It appears that for some people, the unit tests fail during this process – I’ve not been able to reproduce this, and it’s not the tests that I wrote, so in this case my recommendation would be to simply use -DskipTests=true to ignore them).
- Get the forked code for the dependency plugin from http://github.com/pettermahlen/maven-dependency-plugin-fork and install it in your local Maven repo by running ‘mvn install’.
- Update your pom.xml file to use the forked version of the dependency plugin (it’s probably also possible to use the plugin registry, but I’ve not tested that):
<build> <pluginManagement> <plugins> <plugin> <artifactId>maven-dependency-plugin</artifactId> <version>2.2.PM-SNAPSHOT</version> </plugin> </plugins> </pluginManagement> </build>
I’ve filed a JIRA ticket to get this feature included into the dependency plugin – if you think it would be useful, it might be a good idea to vote for it. Also, if you have any feedback about the feature, feel free to comment here!