In this post I’d like to begin the process of translating certain Go structures and concepts into idiomatic Java by laying out some of the guiding principles I anticipate using.
But before I do that, I wanted to insert a colorized version of my previous post’s sequence diagram. In this version of it, I’ve color coded the threads explicitly created by invoking the
Run method on
sharedIndexInformer. You’ll note that there are at least six of them, and that doesn’t include any private implementation-level threads that are used by, say, a Kubernetes client or any other machinery buried in the
Once again, you’ll have to squint (thanks, WordPress!) but I’ve also created a full-size Dropbox image as well.
My hope is that color-coding these threads will help us as we figure out the best way to translate the overall
tools/cache package to Java.
OK, with that out of the way, let’s look at some linguistic elements and put some Java-related stakes in the ground.
First of all, as I look to put together a controller framework in Java that does what the
tools/cache package componentry intends, I will be standardizing on the excellent if sparsely documented fabric8 Kubernetes client. One of the reasons I mention it up front here is because the way that it does listing and watching actually usurps some of the
ListerWatcher functionality (covered in part 1). It also makes use of Java generic types, which is one of the things about Java that I intend to exploit in building this library.
On the “right side” of the problem domain, we’ll squint hard and re-express a
ResourceEventHandler in terms of CDI events.
Translating Go Channels
The Go code makes liberal use of Go channels. Go channels are (unless explicitly jazzed up otherwise) effectively unbuffered synchronous blocking queues of zero length that block senders until there are receivers, and block receivers until there are senders. For the most part, we can substitute the careful use of a
SynchronousQueue for these throughout.
The Go code also makes a lot of use of “stop channels”, which are mechanisms for telling a blocked thread to stop blocking and clean up. Java’s idiomatic analog is thread interruption, so we’ll look to see where we can use that instead.
Functional versus Object-Oriented Programming
A more controversial decision is when to ape the Go code and use functions and functional programming everywhere, and when to make use of intelligent object-oriented approaches. This is much harder to get right, but I believe that there is room for both sorts of approaches. We’ll lean heavily on use cases here: if one-time fundamental customization is needed, often it’s easier and simpler to subclass, baking the one-time customization into the subclass itself. If many different kinds of users can use the same class in many different ways, then employing a strategy-like pattern where functions are supplied at construction time may make more sense.
There are several places in the Go code where items are retrieved by key, or checked for equality semantics. There may be cases in the Java code where we can lean on
hashCode implementations for this sort of thing.
Simplification and Composition
Finally, we’ll be looking hard at the compositional nature of this whole project: while it might be “more correct” to have lots of tiny functional Lego bricks that can be composed into different pieces to form One Of Many True Controller Frameworks™, it may be simpler to start with fewer concepts, or at least present a simplified façade to the end user. I know from plowing through this codebase and reading comments in the Kubernetes Slack channel for months if not years now that understanding this package puts a serious cognitive load on most developers. It would be nice to trim that down somewhat for fellow Java developers if we can.
What I’d like to end up with is an event-driven programming model for writing Kubernetes controllers in Java, where a developer simply declares interest in Kubernetes resource notifications—events describing lifecycle changes in Kubernetes objects—and can then react to them without having to explicitly “get” or construct anything, or worry about what thread they’re on, and so on. I envision we can take the whole
tools/cache package machinery we’ve just explored over these last several blog posts and put most of it behind a standard CDI portable extension or the equivalent.