I had long ago resigned myself to the reality that Groovy was likely on its way toward the great garbage collector in the sky. I've had an eye on Kotlin since its inception, and was excited to see it gain a foothold as a first-class language for Android. I've heard tell of it being adopted in organizations where Groovy had previously held favor. And finally, Java itself has been evolving steadily, adding features both useful and baffling. I mean, I still don't understand the purpose of "default implementations" on interfaces, unless the old commandment forbidding multiple inheritance was thrown out the window.
Spring, on the other hand, has only intensified its standing as the go-to JVM add-on, providing everything you could possibly need for about every scenario. You can look back about 5 years on this blog to see me extolling the virtues of the ready-made JPA/rest combination. During that interval, Spring has continued to evolve along with Java, while adding robust support for Kotlin and apparently continuing to support Groovy, which warms my heart.
As I dove in to the coding exercises we'll look at later in this installment, I was very interested in the Spring Initializr project. There have been any number of ways to bootstrap projects over the years, from simply copying an existing project, to running lazybones to the current solution, which seems like a complete no-brainer, especially if you use IntelliJ.
In addition to being able to choose between Maven and Gradle, you can select your language of choice. Sure, Groovy is at the bottom of the list... but it's still there! The real beauty of this process, however, is that it puts all of the available Spring libraries at your fingertips and picks out the necessary dependencies for you. Mind you, this had already become exceedingly easy with Spring Boot, as those projects are mostly packaged as "starters" that aggregate all the necessary odds and ends.
So, diving right into what I hope to accomplish with this installment of the blog: mainly, I wanted to get caught up on where things stand in Spring-land. How easy is it to spin up a simple CRUD-enabled service layer? And secondly, I wanted to compare and contrast the path of least resistance in all three of the Spring Initializr languages: Java, Kotlin and Groovy.
So without further ado, here are links to the examples that I cooked up:
Kotlin
Kotlin is a relatively-new language. When I first took a look at it, it seemed to me like it was an attempt by JetBrains (makers of IntelliJ, WebStorm and other excellent tools) to grab a chunk of Groovy's popularity. The earliest iterations of the language copped most of Groovy's popular features, including the beloved "Elvis Operator." After spending some hands-on time with the language, though, the differences between it and Groovy are many. I could write a lot about the differences, but we'll save that for another time. What you'll notice most about Kotlin is that, by design, it emphasizes null-safety. So if a reference is allowed to be null, it needs to be called out like so: var foo: String?. Additionally, there's an emphasis on thinking about mutability. Much like JavaScript's const/var nomenclature, Kotlin has val/var.
Getting down to brass tacks with the example app I created, there are a few things to highlight. One thing I found nice was how easy it was to mash multiple classes into a single .kt file. Take the repositories for example:
Spring Data repositories can be defined in as little as a single line, as above. Being able to combine them all into a single file makes a lot of sense in this scenario. The same can be said of the shorthand for creating a class with fields:
@Entity
class System(var name: String, @Id @GeneratedValue var id: Long? = null)
In "plain old Java," this would take many more lines to accomplish. In Groovy too, for that matter.
One gotcha that you'll notice is the need for the lateinit var construct. This is necessary for any injected properties and basically tells the compiler and its null safety policing mechanisms to chill out. This isn't necessarily an indictment on Kotlin as a language, but speaks to the fact that they didn't have IoC/Dependency injection in mind when they started putting the language together.
In conclusion, I don't really have too many criticisms around Kotlin as a language. Groovy is much more amenable to developers with a Java background, but Kotlin isn't exactly inaccessible. It just has a tendency to be a little... different at times. Any seasoned Java developer will agree that Maps can be an integral part of coding. The authors of Groovy recognized this and made an excellent shorthand ([foo: 'bar']). Kotlin has a top-level function toMap(..) and a to operator, which is still more succinct than Java (toMap("foo" to "bar")), though Java has made some inroads in that department in recent years. We'll get to that later. Lastly, I couldn't really figure out which way the wind was blowing vis-a-vis unit testing. I did my examples with JUnit, which was easy enough, but kotest looks pretty cool and appears to be a very active project.
Java
What can we say about good ol' Java that hasn't already been said? In terms of sheer hours, I've spent the vast majority of my career writing Java. I maintain that the language really lost its way when it added generics. For my money, Java 1.4 was the pinnacle of the language as it was first envisioned. It was unabashedly object-oriented, and balanced from the perspective of typing. I understand why generics were added, but Sun's solution just didn't really do it for me. With type erasure, a cavalier coder could end up in a heap of trouble. The more recent addition of lambdas didn't bother me as much, though it really brings into question what Java is trying to be as a language. OO? Functional? Or really, just the Frankenstein monster that I sometimes imagine it to be in my mind.
Anyhow, as far as the Java portion of this coding exercise goes, there are a few things I'd like to point out. Since I'd stepped back from primarily doing Java and JVM work, a library by the name of Project Lombok emerged. This has factored mightily into the Spring ecosystem from the looks of it, and I'll admit that it makes writing Java a lot less of an exercise in boilerplate coding. The Groovy fanboy in me would question the need of such a thing when the problems have already been solved, though. I suppose that it would be a way to get around the tedium of writing getters and setters all day in an organization that absolutely insisted upon code being written in Java (they exist). In the Java code example I wrote, I mainly used the @Data annotation, which dynamically creates getters and setters, and adds a custom equals(), toString() and hashCode(). There is a lot more to Project Lombok than this. It's really very cool, and employs some idiomatic trickery to accomplish some nifty things. Bear in mind that you'll need a plugin for your IDE to make it play nicely.
The other thing I'd like to touch on briefly is a feature that stormed in around Java 1.8: double brace expressions. This makes working with things like Maps and Collections a bit less painful. Here's a snippet from one of my tests:
new HashMap<String, String>() {{ put("projection", "fullTitle"); }}
In one line, I was able to accomplish what would have taken multiple statements before. The shorthand here activates something similar to an anonymous inner class, and executes statements within the scope of the instance. It's not as nice as the Groovy and Kotlin examples above, but it's certainly an improvement.
I also tossed in a Java lambda for good measure:
@Bean
ApplicationRunner loadTestData(EsrbRatingRepository esrbRatingRepository,
SystemRepository systemRepository, TitleRepository titleRepository) { return (ApplicationArguments args) -> { EsrbRating rating = esrbRatingRepository.save(new EsrbRating("E", "Everyone"));
System system = systemRepository.save(new System("Atari 2600"));
Title title1 = new Title(system, rating, "California Games");
Title title2 = new Title(system, rating, "Pitfall!");
Title title3 = new Title(system, rating, "Tank Commander");
titleRepository.save(title1);
titleRepository.save(title2);
titleRepository.save(title3);
};
}
This just populates the H2 database with some sample data. As you can see, it creates a bean, which is actually just a lambda that uses the outer scope from the factory method. I'm still a bit iffy on the inclusion of lambdas in Java, when an anonymous Runnable worked just fine before. But they certainly could have done it worse.
Groovy
Ah, good old Groovy. I spent a good many years in Spring/Groovy land, and jumping back in was like sliding on a well-worn glove. I don't have a lot to highlight with this one, though I'd recommend lining it up with the Java project. Even with the Lombok annotations, the superiority of Groovy shines through. I acknowledge that it's purely a matter of opinion, but having started writing Java in the late '90s, I find Groovy to be a very thoughtful evolution of its principles. One thing you'll notice is that I prefer to use a field/method/variable type on the left as opposed to def. This is a purely opinionated move, and I prefer it over Kotlin which uses var/val, and requires looking to the right to determine the type (var id: Long? = null).
I'd also like to point out how great the Spock framework is. It has a minuscule learning curve and makes mocking a snap. I can't say with any certainty how popular it still is, but it's clearly being maintained regularly. The difficulty going forward is that it requires a separate spock-spring dependency to play well with all of Spring's testing apparatus, and I could see Spring starting to evolve faster than the potentially dwindling Groovy/Spock community. I sort of hate to say it, but a huge part of my affinity for Groovy is how nicely Spock plugs into it. Though Spock is also an excellent option for Java codebases as well. I also looked at doing the Kotlin tests with Spock, but to "properly" test the code requires some odd-looking code.
Spring
To wrap things up, I wanted to touch briefly on Spring and some of my observations coming out of this exercise. Above all, I'm really flabbergasted at how awesome the framework continues to be. It truly frees the developer from having to repeatedly churn out boilerplate code for common exercises. spring-data-rest is my favorite example of this.
Spring has really jumped on the HATEOAS/HAL bandwagon, which to me is a bit of a double-edged sword. One of the challenges I immediately encountered in these examples was how to return immediately useful data to the client. For a complex object representation (like most enterprise relational DBs), this means a ton of links. Spring's solution to this is Projections, which allow you to expose a more fleshed-out object. Without a projection, it would be up to the client to follow the links in order to have something fully-hydrated. Even with Projections, there's no straightforward way to configure a default projection. The projection has to be requested by the client as a parameter on the query string. I can think of a handful of ways to hack around this limitation, but why should that be necessary?
Spring has really jumped on the HATEOAS/HAL bandwagon, which to me is a bit of a double-edged sword. One of the challenges I immediately encountered in these examples was how to return immediately useful data to the client. For a complex object representation (like most enterprise relational DBs), this means a ton of links. Spring's solution to this is Projections, which allow you to expose a more fleshed-out object. Without a projection, it would be up to the client to follow the links in order to have something fully-hydrated. Even with Projections, there's no straightforward way to configure a default projection. The projection has to be requested by the client as a parameter on the query string. I can think of a handful of ways to hack around this limitation, but why should that be necessary?
I haven't dug into the topic much, but I'm guessing there are lots of libraries in the JavaScript/native world that take care of navigating links and building full objects for the developer. Even then, it seems to me that one HTTP request is better than a bunch of them. It's worth noting that this isn't a problem for a proper NoSQL/document DB where everything is self-contained. Food for thought.
Anyway, it was nice to be able to do this exercise and to get back to blogging. I say this every time, but I hope to do more soon. Stay tuned.
No comments:
Post a Comment