piątek, 26 stycznia 2018

Type safe Java DTOs in Angular with Maven

Introduction

New Angular, as opposed to "old" Angular.JS, uses TypeScript (https://www.typescriptlang.org/). Among others, TS usage allows for a static type checking, a language feature that can prove very useful on the backend / frontend border (namely - REST API level). With a TypeScript interface / class definition for each and every DTO (Java class sent thru API), errors in usage are easily discovered at the compilation stage, well before the code reaches testing.

But how to get TS definitions for all DTOs easily, automatically, without boring process of converting Java to TypeScript? Well, if your build uses good old Maven, your're lucky ;-) There is a plugin that properly configured will output .d.ts file for all of the DTOs. Let me show you how to configure the plugin for a simple Angular application.

Maven configuration

First, we need to configure the plugin in the pom.xml of your application. Fragment may look as follows:
It simply binds the plugin's generate goal to process-classes phase, configuring by the way that:
  • Java enumerations will be mapped to TypeScript string enum (requires TypeScript 2.4)
  • plugin will consider all classes conforming to com.example.**.**Dto
  • namespace in which all of the types will be stored is yournamespace
  • output will be stored in the src/main/webapp/app/dto-definitions.d.ts file (standard Maven web app layout)
  • the output will be global (in a declared namespace, not separate module)

Angular usage

OK, so we have a .ts.d file generated, how to use it now in the Angular application? First, you need to make TypeScript compiler aware of the typings. This differs between frontend builds. In my example, I use grunt with grunt-ts plugin for TypeScript compilation. Therefore, example looks as follows:
Now, once TS compiler is aware of the namespace and contained types (interfaces actually as per configuration), rest is simple. any type can be imported in a following way:
import MyClassDto = yournamespace.MyClassDto;

Tips and tricks

Duplicated and nested Java classes

The example is really minimal and simplified. There are a lot of corner cases that will require additional configuration (or black magic ;-) ) to resolve. My favourite ones are nested classes and duplicated class names.
The first one is problematic as "$" can't be part of the class name in the TypeScript - so Outer$Nester as per Java standard won't work. What can we do? Well, my solution is to rename the class to take simple form of OuterNested.
The second one is problematic as we store all classes in one module - so duplicates from different Java packages will land in the same module - and that's a name clash. What to do then? Well, I simply join duplicated class name with last part of the package declaration. Maybe it doesn't look perfect, but in limited number of duplicated cases is just works ;-)
But how to enable such conversions for the plugin? Let's just configure a customTypeNamingFunction that transforms the name as it comes from Java into TypeScript name. Simple example may look as follows:

Fully-qualified TypeScript names

What about configuring the plugin to output Java types as fully qualified TypeScript types? After all, namespace is similar to package in this regard. Well, don't do that. Why? Because of the type ordering. Plugin and compiler will understand classes in a single namespace - that is order classes referencing other classes. But this does not work for separate packages. If you have (and you certainly do) a Java class that references another Java class in other package, the plugin may output namespace with referenced classes later than the namespace with class referring. As a result, TypeScript compilation will fail as the required type will not be found. Sorry, TS ain't Java ;-) 

Brak komentarzy: