about:drewcsillag

Musings of a programmer, musician, photographer, and Christian.

Java Server Best Practices

When writing Java servers (or servlets that run in a container), I’ve learned a few things that have saved me a bunch of trouble during development, as well as later on.

Dependency Injection

  • Injection: if something is singleton (request, global or any other scope), the only things injected to it should also be singleton – otherwise you get implicit singletons of things the singleton holds, and may not be what you want – and generally harder to reason about. Better to have the singleton instances be leaves in the tree of injected things.

  • Injection: if not singleton, ctor’s should only be assignments/precondition checks. Keeps them cheap to produce. Things like caches and other persistent-ish state should be singleton on their own and injected into the things that need them, rather than the object holding the cache being singleton. Also keeps things easier to reason about.

Mutability

  • Immutability is a good thing! The hype is not misplaced. Uses Google Guava’s Immutable classes. Note that ImmutableBlah.copyOf(anImmutableObjOfSameType) doesn’t actually copy, but returns the copied object.

  • Avoid having mutable data escaping a function. It can be just fine to use mutation inside when it increases clarity like when building complicated structures where you may have to reference the contents of it while still building it. But return an Immutable copy or an Unmodifiable wrapper of the object. But remember if you use an Unmodifiable version, that if the underlying object can still be mutated, the contents can still change. Personally, FWIW, I stick to the Immutable collections.

  • Use final for all variables and method parameters, where practicable (i.e. in a normal for (int i = 0.... loop, i cannot be final). Single assignment is good. Then lack of a final becomes a strong signal that the variable is going to be changed – it would be nice if variables were final by default though.

  • Immutablility can make something that might’ve been a nightmare much simpler if/when you need to parallelize things. Knowing you can trust the world to not change under you is a very comforting thing.

Java Language Things

  • Anonymous blocks can be good. Some methods really are easier to read if they are long (no excuses however for those that need not be) and this can help to control scope and make things easier for the reader. For example:
1
2
3
4
5
6
7
8
9
10
11
...some long honking method....
final ThingIActuallyWant thing;
final OtherThingIWant other;
{
   final Foo fooer = makeFoo();
   ...
   ...
   thing = makeThing(fooer, other, intermediates, that, areNo, longerNeeded);
   other = ....
}
... method continues....

In this example, the only things that escape the scope is thing and other. Also, with it declared before the block like this, it’s a signal to the reader what the intent is. It also can sometimes eventually lead to method extraction.

  • Follow the advice on flow control

  • Choose Optional<T> over null. Optional makes you have to explicitly deal with the case where the object isn’t there (which is what you want). Optional.map can be really quite nice when performing a series of operations against an Optional without having to manually do isPresent() checks.

  • Avoid inlining intermediate value expressions e.g.

1
2
final FooValue foo = some_expr;
final BarValue bar = computeSomeThing(foo);

could be inlined to:

1
final BarValue bar = computeSomeThing(some_expr);

The former allows you to easily hit it in the debugger, the latter not so much. Also when exceptions are reported the former will give you a better idea at where it failed.

Related: if Java cannot inference the type of a lambda, don’t cast inline, just make a new variable with the proper type. It just reads easier for future you (see below).

  • Use enums instead of booleans. The name of the enum tells you more specifically what something means. Even if it’s just a two valued enum, it can often save you the trouble of finding the method signature to figure out what the boolean means.

  • For constructors with lots of arguments, strongly consider a builder. Java unfortunately doesn’t have keyword args, which would make it simpler to understand, so builder-pattern it is. BTW: both eclipse and IntelliJ have plugins or built-in features available for generating builders.

  • Avoid the

1
2
3
4
5
final SomeValue value = someFunction()
if (canReturn(value)) {
    return getReturnValue(value);
}
... use value below ...

go-like pattern if it becomes pervasive, especially if there are multiple levels of function calls that have to do this. Create a return value Exception that you can catch at the top level and extract the return value from. If Java had proper continuations that would avoid the problem, but alas, not yet.

  • Lambdas and method references are a good thing. Use them.

Threading

  • Guava Listen[ing|able] varieties of futures and executors are good things. Use them. The first time you use Futures.allAsList(listOfFutures).get() (amongst other similar things), or Futures.transform you’ll thank me. Also see Spotify’s Trickle Library.

  • Give your executors names. Tracing through logs is made much simpler when the entries have their thread names logged with them.

Less Magic, Mo’ betta

Example: slf4j – if you’ve ever had to spelunk around to figure out which underlying log library is being used, slf4j is a refreshing change. No autodetection, just include the backing jar file.

Magic is just something hard to debug through later when it doesn’t work. This is why I will only choose things like Spring, Guice, Hibernate, JPA, Apache Common Logging and similar things if they are forced on me.

Murphy’s Law

Write code with the idea that you’ll have to deal with things when they break. Something that “looks nice” may be a real pain to debug. A number of the above tips relate to this point. “Elegant” code can be a real bear to debug through – it’s usually just not worth it.

Keep Future-you in Mind

Present-you may fully understand how everything you need to know works. Future you may not. Code for Future-you and Future-you will thank you. If not for Future-you, for Future-somebody-else.

Putting some of the bits together

Now, just sit back and thing about what this might look like without some of the above things I mentioned…

Here, splitQuery returns an object with two Optional fields containing queries to run. So for both the this and that queries, the .map business submits callables to the executor to execute the queries (the do[This|That]Query methods) and then adds the futures for those to the futures list, which then is all joined up at the end.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
final ThisAndThatQuery queries = splitQuery(request);
final List<ListenableFuture<QueryResponse>> futures = Lists.newArrayList();

// yay java.util.Optional, lambdas and method references!
queries.getThisQuery()
    .map(query -> queryExecutor.submit(() -> doThisQuery(query)))
    .map(futures::add);

queries.getThatQuery()
    .map(query -> queryExecutor.submit(() -> doThatQuery(query)))
    .map(futures::add);

try {
  final List<QueryResponse> responses = Futures.allAsList(futures).get();
  return joinResponses(responses);
} catch (final InterruptedException | ExecutionException e) {
  logger.error("error caught while collecting responses from futures", e);
  return new QueryResponse(QueryResponse.Status.INTERNAL_ERROR,
      "Internal error processing request", QueryResponse.EMPTY_RESULTS);
}

Comments