It’s Sunday and all the other kids are out in the sunshine being healthy. I’m sitting indoors porting an old web application to dlang, golang and Spark on Java 8. Life? I’ve heard of it.

Introduction

I had an old web application that implemented a REST/JSON service for provably fair number generation I wrote a year ago in Java 7 with Jersey and Guice; you know, the standard stuff. So in order to avoid the sun I thought I’d rewrite it in a couple of other languages. I was curious on how it would be to write a web service in each of these languages. However, I’m not going to all Italian and as such I’ve ignored static files, authentication, etc etc and aimed for the core: I’m faking one singe method that takes the following JSON on a PUT:

{ "range" : { "offset" : 0, "length" : 10 } } 1 2 3 4 5 6 { "range" : { "offset" : 0 , "length" : 10 } }

It then creates and returns a “ticket” looking something like this:

{ "id": "a8536877-bb25-4a46-8128-b829c82b2e43", "expires": "2014-07-06 17:56:28.246186702 +0000 UTC", "secret": "1be2e452b46d7a0d9656bbb1f768e8248eba1b75baed65f5d99eafa948899a6a", "requestRange": { "Offset": 0, "Length": 10 } } 1 2 3 4 5 6 7 8 9 10 { "id" : "a8536877-bb25-4a46-8128-b829c82b2e43" , "expires" : "2014-07-06 17:56:28.246186702 +0000 UTC" , "secret" : "1be2e452b46d7a0d9656bbb1f768e8248eba1b75baed65f5d99eafa948899a6a" , "requestRange" : { "Offset" : 0 , "Length" : 10 } }

Since I’m not interested in replacing the existing service just yet I’m going to fake the “secret” with an SHA-256 hash over the integer list “0123”, but apart from this I want it fairly correct. And here’s what I’ll be looking for:

Development speed, ease of use and tool support

JSON encoding of request and response

DI support (if applicable)

I’m going to use UUID, SHA-256 digests and date manipulation along the way.

D

Of course D have been around for a while, but I haven’t looked at it since, like, forever. I decided to use vibe.d for the web framework, poodnis for DI and DDT as editor.

Coding in D was familiar for an old Java hack. Only there’s templates and property support. The editor support is OK and integrates with the compiler nicely. Auto completion could be better, but works some 50% of the time, so not too bad.

But ho! here be black magic. You quickly realize how much a large, active community helps the documentation. And D does have a community, but as far as I can see nowhere near Java or even Go. Both poodnis and vibe.d had spots which weren’t documented and impossible to google. Usually I’d rely on the crowd to find out how to proceed, but with vibe.d it wasn’t that easy.

This is how the supporting SHA-256 service type looks:

interface HashService { string hash(int[] list); string hash(int[] sequence, int serverSeed); } class SHA265Service : HashService { string hash(int[] sequence) { return finish("", sequence); } string hash(int[] sequence, int serverSeed) { string s = to!string(serverSeed); return finish(s, sequence); } private { string finish(string s, int[] sequence) { foreach (int i; sequence) { s ~= to!string(i); } return hashToString(SHA256(s)); } } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 interface HashService { string hash ( int [ ] list ) ; string hash ( int [ ] sequence , int serverSeed ) ; } class SHA265Service : HashService { string hash ( int [ ] sequence ) { return finish ( "" , sequence ) ; } string hash ( int [ ] sequence , int serverSeed ) { string s = to ! string ( serverSeed ) ; return finish ( s , sequence ) ; } private { string finish ( string s , int [ ] sequence ) { foreach ( int i ; sequence ) { s ~ = to ! string ( i ) ; } return hashToString ( SHA256 ( s ) ) ; } } }

Not too bad!

Note though that SHA-256 is not included in D. What?! So in order to have it in I had to manually copy some public domain code from the net. That felt a bit dirty for something as standard. Also, my specification is that the “secret” should be an SHA-256 over UTF-8 byte sequence, and we’re dodging it a bit since D’s strings are actually arrays of UTF-8 chars so their byte representation will be correct.

(Update 7 July: Andrea Fontana tells me SHA-256 is due for D 2.0066, currently in beta).

Here then is how the main application looks (sans import statements):

shared static this() { // register all DI binding auto container = Container.getInstance(); container.register!(TimeProvider, DefaultTime).singleInstance(); container.register!(IDProvider, UUIDProvider).singleInstance(); container.register!(HashService, SHA265Service).singleInstance(); container.register!(SequenceResource).singleInstance(); // GOTCHA 1 // get the main resource from DI container auto resource = container.resolve!(SequenceResource); // create HTTP server settings auto settings = new HTTPServerSettings; settings.port = 8080; settings.bindAddresses = ["::1", "127.0.0.1"]; // create route, interface and start auto router = new URLRouter; registerRestInterface!FairService(router, resource); // GOTCHA 2 listenHTTP(settings, router); } interface FairService { @path("create") @method(HTTPMethod.PUT) Ticket create(TicketRequest req); } class SequenceResource : FairService { @Autowire TimeProvider timeProvider; @Autowire IDProvider idProvider; @Autowire HashService hashService; Ticket create(TicketRequest req) { Ticket ticket = new Ticket; ticket.id = idProvider.create; SysTime sysTime = timeProvider.time; sysTime.roll!"days"(7); ticket.expires = sysTime.toISOExtString; ticket.requestRange = req.range; ticket.secret = hashService.hash([0, 1, 2, 3]); return ticket; } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 shared static this ( ) { // register all DI binding auto container = Container . getInstance ( ) ; container . register ! ( TimeProvider , DefaultTime ) . singleInstance ( ) ; container . register ! ( IDProvider , UUIDProvider ) . singleInstance ( ) ; container . register ! ( HashService , SHA265Service ) . singleInstance ( ) ; container . register ! ( SequenceResource ) . singleInstance ( ) ; // GOTCHA 1 // get the main resource from DI container auto resource = container . resolve ! ( SequenceResource ) ; // create HTTP server settings auto settings = new HTTPServerSettings ; settings . port = 8080 ; settings . bindAddresses = [ "::1" , "127.0.0.1" ] ; // create route, interface and start auto router = new URLRouter ; registerRestInterface ! FairService ( router , resource ) ; // GOTCHA 2 listenHTTP ( settings , router ) ; } interface FairService { @ path ( "create" ) @ method ( HTTPMethod . PUT ) Ticket create ( TicketRequest req ) ; } class SequenceResource : FairService { @ Autowire TimeProvider timeProvider ; @ Autowire IDProvider idProvider ; @ Autowire HashService hashService ; Ticket create ( TicketRequest req ) { Ticket ticket = new Ticket ; ticket . id = idProvider . create ; SysTime sysTime = timeProvider . time ; sysTime . roll ! "days" ( 7 ) ; ticket . expires = sysTime . toISOExtString ; ticket . requestRange = req . range ; ticket . secret = hashService . hash ( [ 0 , 1 , 2 , 3 ] ) ; return ticket ; } }

Note the two GOTCHA’s above. The first is poodnis which for some reason refused to inject members into the sequence resource if you registered it via its interface declaration. Strange. The second is how you need to register the REST interface in vibe.d in order to get the routing to work.

All in all I’m happy with this implementation if it weren’t for the lacklustre documentation. D is a very powerful language, the compilation time is OK, the DUB package manager feels just like home for an old Java programmer, and the execution speed is blistering. However, the language itself feels a bit old and a bit too much like C++. That’s not strange considering its age, but you can’t help thinking that time has left D a bit behind.

Go

In this trio of language Go is obviously the stand-out, and possibly also the hippest. I used the Martini framework and Sublime 2 with the GoSublime package. No DI for this test.

Many seem to adore Sublime and after working a couple of hours I understand, it’s a really good editor and the Go support is good. However I had some problems figuring out how Go wanted me to compile and/or install my various packages. At the end I did a separate build+install on each file in a package, and still I wasted quite some time wondering what the hell was happening when all I actually need was a build+install. I’m too used to incremental compilation I guess.

Let’s a have a look at the same hash service, but now in Go:

type HashService interface { Hash(list []int) string HashWithSeed(list []int, serverSeed int) string } type SHA256Service struct{} func (s *SHA256Service) Hash(list []int) string { return finishHash("", list) } func (s *SHA256Service) HashWithSeed(list []int, serverSeed int) string { return finishHash(strconv.Itoa(serverSeed), list) } func finishHash(seed string, list []int) string { for _, v := range list { seed += strconv.Itoa(v) } bytes := sha256.Sum256([]byte(seed)) return hex.EncodeToString(bytes[0:32]) } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 type HashService interface { Hash ( list [ ] int ) string HashWithSeed ( list [ ] int , serverSeed int ) string } type SHA256Service struct { } func ( s * SHA256Service ) Hash ( list [ ] int ) string { return finishHash ( "" , list ) } func ( s * SHA256Service ) HashWithSeed ( list [ ] int , serverSeed int ) string { return finishHash ( strconv . Itoa ( serverSeed ) , list ) } func finishHash ( seed string , list [ ] int ) string { for _ , v : = range list { seed += strconv . Itoa ( v ) } bytes : = sha256 . Sum256 ( [ ] byte ( seed ) ) return hex . EncodeToString ( bytes [ 0 : 32 ] ) }

A bit different. I’m sure I could have done something smarter with the interface, the reason it’s there in all three languages is because of unit testing, using mocks is standard and even though my unit testing was limited I wanted the interfaces in place anyway.

Actually, I liked the unit test facilities of Go, worked nicely. However: every language on earth is using some kind of “assert” in the testing framework, but in Go you’re forced to write “if” statements:

import "testing" func TestFinishHash(t *testing.T) { check := finishHash("0", []int{1, 2}) if check != "bf6aaaab7c143ca12ae448c69fb72bb4cf1b29154b9086a927a0a91ae334cdf7" { t.Error("Erronous hash: " + check) } } 1 2 3 4 5 6 7 8 import "testing" func TestFinishHash ( t * testing . T ) { check : = finishHash ( "0" , [ ] int { 1 , 2 } ) if check != "bf6aaaab7c143ca12ae448c69fb72bb4cf1b29154b9086a927a0a91ae334cdf7" { t . Error ( "Erronous hash: " + check ) } }

Why Go? Why?!

Let’s have a look at the main application then, shall we?

package main import "github.com/go-martini/martini" import "github.com/martini-contrib/binding" import "github.com/martini-contrib/render" import "pf" func main() { timer := pf.DefaultTime{} ids := pf.DefaultIDProvider{} hash := pf.SHA256Service{} // only pointers match interfaces app := App{&timer, &ids, &hash} // create martini m := martini.Classic() // the renderer will have output JSON m.Use(render.Renderer()) // the binder will handle input JSON m.Put("/create", binding.Bind(pf.TicketRequest{}), func(req pf.TicketRequest, render render.Render) { t := app.Create(req) render.JSON(200, t) }) m.Run() } type App struct { Timer pf.TimeProvider IDs pf.IDProvider Hasher pf.HashService } func (a *App) Create(req pf.TicketRequest) pf.Ticket { t := pf.Ticket{} t.RequestRange = req.Range t.Expires = a.Timer.Time().String() t.Id = a.IDs.Create() t.Secret = a.Hasher.Hash([]int{0, 1, 2, 3}) return t } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 package main import "github.com/go-martini/martini" import "github.com/martini-contrib/binding" import "github.com/martini-contrib/render" import "pf" func main ( ) { timer : = pf . DefaultTime { } ids : = pf . DefaultIDProvider { } hash : = pf . SHA256Service { } // only pointers match interfaces app : = App { & timer , & ids , & hash } // create martini m : = martini . Classic ( ) // the renderer will have output JSON m . Use ( render . Renderer ( ) ) // the binder will handle input JSON m . Put ( "/create" , binding . Bind ( pf . TicketRequest { } ) , func ( req pf . TicketRequest , render render . Render ) { t : = app . Create ( req ) render . JSON ( 200 , t ) } ) m . Run ( ) } type App struct { Timer pf . TimeProvider IDs pf . IDProvider Hasher pf . HashService } func ( a * App ) Create ( req pf . TicketRequest ) pf . Ticket { t : = pf . Ticket { } t . RequestRange = req . Range t . Expires = a . Timer . Time ( ) . String ( ) t . Id = a . IDs . Create ( ) t . Secret = a . Hasher . Hash ( [ ] int { 0 , 1 , 2 , 3 } ) return t }

This was actually rather pleasant as soon as I started getting the hang on interfaces and pointers, and exported properties. However, trying to figure out how the strings work was a bit of shit. In Go apparently “string” is an alias for a byte array. And I can hear you thinking now, and what you’re thinking is “so that will be bytes according to a specific character set encoding” but you’re wrong: it’s characters in any character encoding. Any at all. And you’re free to mix it if you want – it’s only a byte array after all. But that’s not entirely true either, string literals are always UTF-8. Why? Because a Go source file is required to be in UTF-8, that’s why.

Fine, maybe that above assumptions Go makes about strings are just as valid as any other language. But trying to figure it out took me well over an hour. Maybe my google skills are sub-prime, but WTF…

All in all I enjoyed working in Go. And the stand-out point is compilation and execution speed: no waiting around here. I can’t help thinking the language itself is a bit ugly, but hey, I’m sure I could get used to it.

Spark / Java 8

Back to Java to for the final; this time using Spark and Java 8. I decided to go with Dagger for DI and Eclipse as editor. Look, we know the tool support is going to be awesome, and it’s not like Java is slow when you strip away the big frameworks.

I’ll spare you the hash service this time and I’ll go straight to the server:

@Singleton public class Server { @Inject TimeService timeService; @Inject HashService hashService; @Inject UuidProvider idService; public static void main(String[] args) { ObjectGraph graph = ObjectGraph.create(new Module()); graph.get(Server.class).run(); } private void run() { // main route Spark.put("/create", (req, res) -> { TicketRequest tr = fromJson(TicketRequest.class, req.body()); Ticket ticket = new Ticket(); ticket.setSecret(hashService.hash(Arrays.asList(new Integer[] { 0, 1, 2, 3 }))); DateTime time = timeService.now().plusDays(7); ticket.setExpires(time.toString()); ticket.setRequestRange(tr.getRange()); ticket.setId(idService.create()); return ticket; }, new JsonTransformer()); // set content type on respone Spark.after((req, res) -> { res.type("application/json"); }); } private static <T> T fromJson(Class<T> cl, String s) { return new Gson().fromJson(s, cl); } } 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 @ Singleton public class Server { @ Inject TimeService timeService ; @ Inject HashService hashService ; @ Inject UuidProvider idService ; public static void main ( String [ ] args ) { ObjectGraph graph = ObjectGraph . create ( new Module ( ) ) ; graph . get ( Server . class ) . run ( ) ; } private void run ( ) { // main route Spark . put ( "/create" , ( req , res ) -> { TicketRequest tr = fromJson ( TicketRequest . class , req . body ( ) ) ; Ticket ticket = new Ticket ( ) ; ticket . setSecret ( hashService . hash ( Arrays . asList ( new Integer [ ] { 0 , 1 , 2 , 3 } ) ) ) ; DateTime time = timeService . now ( ) . plusDays ( 7 ) ; ticket . setExpires ( time . toString ( ) ) ; ticket . setRequestRange ( tr . getRange ( ) ) ; ticket . setId ( idService . create ( ) ) ; return ticket ; } , new JsonTransformer ( ) ) ; // set content type on respone Spark . after ( ( req , res ) -> { res . type ( "application/json" ) ; } ) ; } private static < T > T fromJson ( Class < T > cl , String s ) { return new Gson ( ) . fromJson ( s , cl ) ; } }

Pretty neat. The inclusion of Java 8 closures in Spark 2.0 keeps the code tidy. Me like. But there’s two stand-out points when switching back from D and Go to Java:

The good: Tool support. Modern Java IDE’s are hands down awesome. Incremental compilation, short cut refactoring, code generation and navigation, and spot on auto completion and correction.

The bad: Compilation speed. When you do have to compile from the command line you can’t believe your eyes: several seconds to compile?! It feels like an eternety after Go. I was using Gradle with the daemon running, but still…

Point #2 above was painful as I first used Dagger and the code generation it needs to run had to be done via the command line. Dagger also added a bit of verbosity, so I switched to Guice instead: less boiler plate, less code and more features.

And then there’s the dreaded Java verbosity: it is painful when you switch back. Why Oracle hasn’t added property support to Java by now is beyond me. But you know what? The tooling support to the rescue! You see, that’s what Project Lombok is for. Here’s a legal Java bean with getters and setters and hashCode and equals and toString:

@Data public class Ticket { private String id; private String expires; private String secret; private Range requestRange; } 1 2 3 4 5 6 7 @ Data public class Ticket { private String id ; private String expires ; private String secret ; private Range requestRange ; }

See that @Data annotation? It auto generates the boilerplate for you. Awesome? Oh yes!

Conclusion

So will I be switching from Java 7 and Jersey now? Yes indeed. Will I abandon Java? No, probably not. I really enjoyed working with both D and Go, but at the end of the day Java’s tooling support beats them both, at least for me. And as long as the incremental compilation works in an IDE.

Unless if course Java would be significantly slower when executing? Ah, no, not really: http://www.techempower.com/benchmarks/

Java do eat a lot of memory, so for high performance services that need a small footprint or soft real-time characteristics I’d might go with Go or D. But until then curiosity might make me write hobby projects or tests in Go, but my production code will probably stay Java.

Now I’ll just have to do the same with NodeJS, Erland,and Scala, but unfortunately I have run out of Sunday.