Kotlin, Spring, Kafka, JUnit 5

This post will offer some useful and specific information on JUnit 5, RegisterExtensions, and Kotlin. If you’re here just for those skip forward to the Solution section.

Satisfying Knowledge

I enjoy woodworking. An aspect about it I like is that mostly when you learn things, you understand why they are true. As an example, for light grainy woods, to avoid splitting, it helps to slightly blunt the tips of nails before driving them in. You want the nail to actually crush the wood a bit at first, and not immediately act like a wedge and cleave the woods grain. It make the technique easy to remember, obvious when to apply, and was satisfying to learn.

Unsatisfying Knowledge

I’ve been working with Kafka in a Kotlin service built in Spring. I’d tested the Kafka related code employing an embedded Kafka. Spring has some automagic support for that but it hadn’t fit our needs out of the box so we’d added some set up and tear down. I was happy with the coverage, but I’d duplicated the set up and tear down code a number of times in different tests which I wanted to fix. A JUnit 5 RegisterExtension fit the task and so I moved the code into one, employing that I could remove the duplicated code. Success. Well sort of … the tests still ran and passed, however the build itself failed (!?). The final shut down of the JVM was now throwing an exception. Using an extension had apparently changed some internal behavior. A daemon thread internal to Kafka was trying to access a log folder in a temporary area, but the temporary area had been previously removed. I could move the log area aside and avoid the exception, but then things just hung after the final test. I doggedly worked at it until I solved it, but the knowledge of the solution is unsatisfying, because I can say with certainty what works but I can’t tell you why.

The Solution

JUnit 5 provides nice extension mechanisms for creating reusable set up and tear down code that can be applied to collections of tests. The examples usually read about as follows:

public class TestWithExtensionTest {
    @RegisterExtension
    static SomeExtension ext = new SomeExtension();
 }

Now, Kotlin shed the static feature, but there is a way to achieve them under the cover, and so you’ll see Kotlin examples parallel the above as follow:

class TestWithExtensionTest {
   companion object {
      @JvmField
      @RegisterExtension
      val ext: SomeExtension = SomeExtension()
   }
}

And that works. Mostly. In my case it only sort of worked. The extension ran and my tests worked, but the JVM running the tests exited with an exception. I spent wasted time trying to address the specific exception. In the end, the extension worked perfectly, unchanged, by simply using it as follows:

@ExtendWith(SomeExtension::class)
class TestWithExtensionTest {
}

By applying the extension with the annotation instead, the problem went away. It’s a cleaner approach too.

Why changing how I registered the extension solved the problem… I can’t say. Not satisfying.

Advertisements

Anti-Pattern: The Hand That Mocked Them…

I’ve tested my code thoroughly since I coded. As I came across good testing tools and techniques I incorporated them. One tactic I was hesitant to embrace was mocking. Initially the tools were crude and I just kept writing my own fakes and stubs, I trusted hand rolled solutions more. But as I came up against frameworks employing insanely complex approaches to things (looking at you Spring), I threw in the towel and used mocks to try and isolate just the bits I was trying to test. So, yes I use mocking, but I always do it begrudgingly. I feel that if a testing scenario requires excessive mocking it indicates a design failing somewhere and refactoring is in order.

The Anti-Pattern

The mocking anti-pattern that I’ve seen repeatedly is developers employing mocking as a response to complexity they aren’t able, or don’t want, to sort out. Instead of selectively mocking some clear interaction, and insuring the mocked interactions reflect reality they simply mock a top level method and return a large heap of type unsafe stubbed data. Often you’ll see this around REST services or persistence mechanisms. Mock a single service signature and return a map of lists, of maps of lists of strings etc. What was achieved? Well, depending on the static analysis tools employed and the quality of your co-workers, you may be able to put a check in the tested box. What was actually achieved? You’ve created a facade, possibly thick enough, to avoid detecting method signature changes, probably sufficient to hide data structure changes and certainly sufficient to obscure data type and content changes. Maybe you’ve insured that future code changes will not be tested at all since your tests will go on passing forever! It may take several production bugs before it’s detected and the layers of paint might be so think and dry by then it gets put on the ever growing TODO list of tech debt.

Avoiding The Anti-Pattern

TLDR: Don’t over mock.

But Really…

Ok:

  • The tough love answer is, if it’s your code stack, employ strategies like Test Driven Development, testing the pieces in manageable chunks as you go. Make sure the underpinning code is tested and don’t heap on the business logic in the last layer of code. With practice you’ll find that (a) your design will be better for it and (b) that the further out in the onion you work, the less testing you actually need.
  • Don’t shoot for black box tests, think gray box at a minimum. Make sure you get in under the hood some.
  • Don’t mock up elaborate results ad-hoc. Can you get the result data through some means that will change as the code/data changes? Can you independently verify that the data you are using reflects reality? Is every case of an enumeration included? Use some sort of fakers, not constants, to create leaf types so that bounds cases are found.
  • Bind types and signatures to reality. Write typesafe code and employ the types and interfaces in the mocks. Refactoring should immediately impact your mocked methods and data.

Kotlin Logging in One Line

I’ve recently moved into a role at a site that’s been using Kotlin in its microservices for over a year now. The folks that wrote the code were new to Kotlin, and not predominantly from a Java background. The code isn’t bad, but it’s certainly not idiomatic. It’s been good for me, looking at all the different perspectives and trying to pick a path forward.

Logging

One place there was some chaos was in logging. No surprise there since the Java ecosystem itself is a mess on logging. So I set out to find an idiomatic Kotlin solution. I looked around, tried a few, and in the end settled on the following single line of code as a solution:

Using that extension, and SLF4J’s API yielded a really nice solution where grabbing a logger was as simple as:

val logger = getLogger<SomeClassNameHere>()

It wasn’t everything, but for one line it:

  • Simplified the code
  • Normalized the names
  • Forced an opinionated selection of an API
  • Provided bridges to the chaos in the existing code

Done in one.

If you want to grab this as a jar you can look at it here.

Tech Debt is Not Legacy Code

I’ve experienced a disturbing trend in recent years, people using the term “legacy code” as a way to justify technical debt. A broad definition of legacy code is just an application, system, or source code that is not from the currently active/vital sources.

When I started in technology, right through the not so distant past, the term legacy code was applied to older stuff. Maybe it was a third party system that was still running in some corner because it kept working and no cry for replacement had ever come. Maybe it was an in house product that for whatever reason was untouchable: no budget, lost knowledge, unique dated interface requirements, to big to replace, etc. Basically legacy code was old stuff that you wouldn’t have by choice but couldn’t practically avoid.

There are obvious implications around support and legacy code. The tech might be obscure, the knowledge might lost. If you personally are responsible for keeping it running, you can’t simply stop supporting it, but legacy code was a bogeyman label you could invoke to justify substandard support: “That’s legacy code, so we can’t realistically make it support internationalization. Oh right ok…”

Things now move the pace of internet time, and vendors drop support of products that are only a few years old. Entire technologies come and go in a couple years now. People now accept that something chronologically new could legitimately be a legacy system. It’s real.

But there’s a disturbing trend that goes as follows. Create something sloppily, without architecture, tests, documentation etc. Ignore technical debt. When the chickens come home to roost, slap the label “legacy code” on it and, presto, it’s all ok. The fact that it’s only a year old and all the same folks are here now, no worries.

There are times where the choice to make an iteration of something little more than a proof of concept, or somewhat disposable, are consciously made. Rapidity and learning specifically chosen over longevity. But when that prototype starts to have issues because it’s wasn’t built to standard, please don’t invoke the term legacy code and act as if everything is right in the world. Accept the repercussions of the choices made. Own the fact that the system wasn’t produced to standard and so the maintenance/enhancements are harder. It’s not legacy code, it just wasn’t created to last. Why is the distinction important? Because if you label it legacy code and say that’s why it’s having issues, you’re ignoring what actually caused the situation and you’re just going to repeat it. Worse you’re going to do it, without it even being a calculated choice. The craftsman and quality ethic needed to create products with longevity won’t ever happen if you follow the pattern of: sloppy work, wait a year, label legacy.

Everyone Has a Trigger Right?

If you’ve read past the first sentence of anything I’ve written then it’s safe to say you aren’t a grammar or spelling fascist. I’ve no excuse for my writing, I’m well educated and come from a literary family of writers and editors. No excuse. That said, even I have some presentation triggers, surface things I can’t look past. Little things that will stop me from giving the content any chance at all. My loss.

Today I read an article on Kotlin, its immutables, and their savour, Vavr. Maybe Kotlin’s immutables are broken, maybe the article would have enlightened me as to why… except:

  • All the links in the article had lost their surrounding whitespace sotheywhereslammedintothetext.
  • The illustrations in the article looked like they were generated with the Un*x plot command.
  • The article pitched a solution, Vavr, before it really described the problem.

So a paragraph in I’d started scanning rather than reading, and quickly wasn’t willing to even scan. So I bailed and headed over the Vavr’s site to see what it was a solution to. Since the site was glossy hype I headed straight for the repo to see the code:

  • Built with Maven
  • Includes a class named “$”
  • The copyright included an ASCII banner
  • The grammar of the comments was worse than even I could tolerate
  • @Annotations all over the damn place

So yeah… I blew a half hour, and wasted everyone’s time writing this rant, and having invested that, I can’t start to tell you what’s horribly wrong with Kotlin’s immutables, or what’s so awesome about Vavr. No excuses. My loss.

The New Economy

My first job out of college, back in the 1980’s, I started as a temp at a Fortune 500 company, and after a couple of months they offered to hire me full time. But they refused to give me an offer letter with the particulars, and my father had told me to get an offer letter, so after some heated discussions I left and took a job, at a competitor that was willing to give me an offer letter. Don’t get me wrong, I’m not overly rigid or obsessive compulsive, but I try to be responsible, and weigh risks against rewards. Why am I relating this? Because times have changed.

A Job from a Nigerian Prince?

Over the years I’ve worked all kinds of ways: part-time, contractor, self employed, employee… but now I may be working for a Nigerian Prince. I’ve just ended a traditional employee gig to start a new job. I think there’s a new job… I mean YES there’s a new job starting Monday.

For the new job, I’ll be working full time from home. I’ve never physically met the people employing me. The interviews we all video. I know the company name, and product I’m supposedly working on, but don’t have any tangible proof the people that hired me work there other than the some of their email domains match. The contracts were all done by online forms. Initially I’ll be working through an agency at an hourly rate. Never met anyone from that agency either. Spoken with them by phone. Nice people. They asked me for a laptop spec of my choice, which they say is being shipped to me.

Why would the guy that walked away from a fortune 500 company because they wouldn’t give him an offer letter make this leap of faith? I had a job. I’m not desperate.

Well…

  • I’m tired of relocating to urban areas I don’t much like chasing jobs that aren’t all that special.
  • The promised work I believe is the most exciting I’ve ever been offered.
  • The pay is noticeably better.
  • No commute.
  • Work from home.
  • The people I’ve dealt with so far are great, and they too are in the same situation so I can’t imagine this will be a corporate culture where optics are more valued then results.

So yeah, either I’m about to embark on an exciting and great opportunity… or I’ve fallen for a Nigerian Prince scam. I’ll know which when I cash the first check I guess. Times have changed.

Kotlin Circular Dependency Bug

Ok, so this is bad code, but Kotlin needs to either reject it on compilation or do the right thing, not happily compile it and do the wrong thing.

It’s a circular dependency between two enums, like I said, bad code, but lets just say you had some buttons, and some were safe to hit and others required caution. You used enums to model it:

enum class Button(val caution: CautionLevel) {
    POWER(CautionLevel.LOW),
    RESET(CautionLevel.LOW),
    VOLTAGE(CautionLevel.HIGH)
}

enum class CautionLevel(val buttons: List<Button>) {
    HIGH(listOf(Button.VOLTAGE)),
    LOW(listOf(Button.POWER, Button.RESET))
}

fun main(args: Array<String>) {
   println("\nButtons w/ their caution level:")
   Button.values().asSequence()
     .forEach { 
       println("\tButton: $it Caution: ${it.caution}") 
     }
   println("\nCaution Levels w/ associated buttons:")
   CautionLevel.values().asSequence()
     .forEach { 
       println("\tCaution Level: $it Associated Buttons: ${it.buttons}") 
     }
}

If you compile that Kotlin has no issues. And if you ran it you’d expect to see output listing the buttons and their caution levels, and then the caution levels and the associated button yeah?

What you get it:


Buttons w/ their caution level:
	Button: POWER Caution: LOW
	Button: RESET Caution: LOW
	Button: VOLTAGE Caution: HIGH

Caution Levels w/ associated buttons:
	Caution Level: HIGH Associated Buttons: [null]
	Caution Level: LOW Associated Buttons: [null, null]

Hmmmm…. all the buttons listed are null. Turns out the bug has been reported in other forms but I still feel like I found something.