Slide 1

Slide 1 text

Mi roservices Christoph Leiter Kotlin Vienna Meetup – 2017-01-31

Slide 2

Slide 2 text

Agenda 1 Introduction 2 starjack 3 The Basics 4 JPA 5 Making Requests 6 Testing 2

Slide 3

Slide 3 text

Introduction Enthusiastic Kotlin user since beginning of 2016 Spring Boot simplifies many things and reduces boilerplate Perfect match for Kotlin The Spring people like Kotlin as well and will add more support in Spring 5.0 and in Spring Boot 2.0 My latest project combines Kotlin and Spring Boot in a Microservices architecture 3

Slide 4

Slide 4 text

starjack

Slide 5

Slide 5 text

starjack A platform for buying ski tickets online: 5

Slide 6

Slide 6 text

starjack II Customers sign up and order their personal keycard Tickets of various lift operators can be booked and are available within seconds No more standing in line for a ticket 6

Slide 7

Slide 7 text

starjack III Talks to multiple services: Two different backend systems for getting and ordering tickets (SOAP) Payment Service Provider (SOAP) Service to produce keycards (REST) External e-mail provider (REST) External billing service to create invoices (REST) Google Maps (REST) Weather service (REST) We’re using microservices to isolate services from each other and to handle complexity. 7

Slide 8

Slide 8 text

starjack IV Backend is 100% written in Kotlin Everything is automated Complete infrastructure setup is done with Terraform EC2 instance and database setup is done with Ansible Services are packaged as docker containers, deployments are triggered via Ansible, scheduled by Nomad, and discovered with Consul EC2 instances are stateless, state is only in the DB and in S3 Each service is deployed once per availability zone, everything is redundant Service-to-Service communication via REST or asynchronously via SQS 8

Slide 9

Slide 9 text

starjack V Logical view on AWS 9

Slide 10

Slide 10 text

starjack VI Deployment view on AWS 10

Slide 11

Slide 11 text

The Basics

Slide 12

Slide 12 text

Using Spring Initializr 12

Slide 13

Slide 13 text

Using Spring Initializr II 13

Slide 14

Slide 14 text

Starting Your Application The main method must be static: @SpringBootApplication open class BootApplication { companion object { @JvmStatic fun main(args: Array ) { SpringApplication .run( BootApplication :: class.java , *args ) } } } 14

Slide 15

Slide 15 text

Starting Your Application II Use the IntelliJ run configuration for Spring Boot 15

Slide 16

Slide 16 text

Defining Beans You can define a bean with some properties like in Java: @SpringBootApplication open class BootApplication { @Bean open fun myBean (): MyBean { val myBean = MyBean () myBean.x = "foo" myBean.y = 12 return myBean } } Do we need an instance variable? 16

Slide 17

Slide 17 text

Defining Beans II Let’s use apply: @SpringBootApplication open class BootApplication { @Bean open fun myBean (): MyBean { return MyBean (). apply { x = "foo" y = 12 } } } Now that it’s just one expression... 17

Slide 18

Slide 18 text

Defining Beans III Just use a single expression function: @SpringBootApplication open class BootApplication { @Bean open fun myBean () = MyBean (). apply { x = "foo" y = 12 } } All these open keywords are a bit annoying though... 18

Slide 19

Slide 19 text

Kotlin Compiler Plugins Starting with 1.0.6 Kotlin has compiler plugins which help with framework integration kotlin-allopen Makes classes and all methods automatically open if marked with an annotation. Great for Spring. kotlin-noarg Creates a synthetic default constructor for classes if marked with an annotation. Great for JPA. Annotations are customizable, even supporting meta annotations. Very easy support for Spring and JPA with spring and jpa plugins. 19

Slide 20

Slide 20 text

Kotlin Compiler Plugins II kotlin -maven -plugin org.jetbrains.kotlin ${kotlin.version} jpa spring 20

Slide 21

Slide 21 text

Kotlin Compiler Plugins III org.jetbrains.kotlin kotlin -maven -allopen ${kotlin.version} org.jetbrains.kotlin kotlin -maven -noarg ${kotlin.version} 21

Slide 22

Slide 22 text

Defining Beans IV No more open! @SpringBootApplication class BootApplication { @Bean fun myBean () = MyBean (). apply { x = "foo" y = 12 } } 22

Slide 23

Slide 23 text

Reading Properties Configuration is often externalized to make it customizable: service.url=http :// example.com/endpoint Injecting configuration properties works just like in Java: class Service( @Value("\${service.url}") val url: String ) Note that you have to escape the $ because it’s used by Kotlin for string interpolation. The preferred way now is to use @ConfigurationProperties. 23

Slide 24

Slide 24 text

Reading Properties II Using @ConfigurationProperties: @Component @ConfigurationProperties ("service") class ServiceProperties { lateinit var url: String } or with the Jackson Kotlin module: @Component @ConfigurationProperties ("service") data class ServiceProperties ( val url: String ) You can even use the standard Java validation annotations. 24

Slide 25

Slide 25 text

JPA

Slide 26

Slide 26 text

JPA My recommendations: Use spring-data-jpa, reduces tons of boilerplate Use flyway or liquibase to define your schema Set jpa.hibernate.ddl-auto to validate to ensure your database schema matches your entities Don’t create a complex completely navigable domain model, use aggregates (Domain Driven Design) instead 26

Slide 27

Slide 27 text

Defining Entities Defining a bean: @Entity class Customer { @Id val id: UUID = UUID.randomUUID () lateinit var firstname: String lateinit var lastname: String } How can we handle properties better? It’s not a good option to make everything nullable and it’s not even possible for basic types. Every lateinit is a NullPointerException waiting to happen. kotlin-noarg makes it easy to use data classes instead. 27

Slide 28

Slide 28 text

Defining Entities II Now with using a data class: @Entity data class Customer( @Id val id: UUID = UUID.randomUUID (), var firstname: String , var lastname: String ) You can specify sensible default values for your properties. Hibernate will invoke the synthetic default (no-arg) constructor and set the values. Automatic generation of toString, equals, and hashCode plus copy function. 28

Slide 29

Slide 29 text

spring-data-jpa Repositories Instead of implementing all the basic CRUD operations yourself you can just define a single interface: interface CustomerRepository : CrudRepository You automatically get methods like findOne(id), save(entity), delete(entity), etc. Code is created dynamically by spring-data-jpa. Tip: Consider defining your own KCrudRepository interface to add nullability information. 29

Slide 30

Slide 30 text

Custom JPA Queries You can use @Query to define custom queries in spring-data-jpa Repositories interface CustomerRepository : CrudRepository { @Query("SELECT c FROM " + "Customer c " + "WHERE LOWER(c.firstname) LIKE '%' ||" + "|| LOWER (: search) || '%'") fun search( @Param("search") search: String ): List } But that’s how it’s done in Java... 30

Slide 31

Slide 31 text

Custom JPA Queries II We can use Kotlin multi line strings to improve readability: @Query(""" SELECT c FROM Customer c WHERE LOWER(c.firstname) LIKE '%' || LOWER (: search) || '%' """) fun search( @Param("search") search: String ): List Now that’s much better. 31

Slide 32

Slide 32 text

Making Requests

Slide 33

Slide 33 text

Defining DTOs Jackson needs to be able to instantiate your DTOs. By default it would just use the default constructor and you need to use lateinit. class CreateUserCommand { lateinit var email: String lateinit var firstname: String lateinit var lastname: String lateinit var signupCode: String? = null } Fortunately there’s a better option. 33

Slide 34

Slide 34 text

Kotlin Module for Jackson Introducing jackson-module-kotlin: Allows to use primary constructor to create instances Can also use secondary constructors with @JsonCreator Automatically understands nullable and non-nullable types Supports Kotlin data types like Pair, Triple, IntRange, etc. 34

Slide 35

Slide 35 text

Kotlin Module for Jackson II Register Kotlin module for ObjectMapper used by Spring: @Bean fun kotlinModule () = KotlinModule () Add it to instances of your ObjectMapper: val om = ObjectMapper () . registerModule (KotlinModule ()) Tip: Subclass ObjectMapper and configure it like you need it in your project. 35

Slide 36

Slide 36 text

Defining DTOs II Jackson will call the constructor automatically data class CreateUserCommand ( val email: String , val firstname: String val lastname: String , val signupCode: String? } Now DTOs can even be immutable! Let’s add some validations. 36

Slide 37

Slide 37 text

Defining DTOs III Annotations need to be prefixed with get: so they are put on the getters data class CreateUserCommand ( @get:Email val email: String , @get:Length(max = 32) val firstname: String , @get:Length(max = 32) val lastname: String , val signupCode: String? } No need for @NotNull, it’s already in the type system. 37

Slide 38

Slide 38 text

Setting Basic Auth There’s no easy way to set the authorization header for HTTP basic auth. Let’s add an extension function: fun HttpHeaders.setBasicAuth ( username: String , password: String ) { val auth = String( Base64.getEncoder (). encode( "$username:$password".toByteArray () ) ) set(AUTHORIZATION , "Basic $auth") } val headers = HttpHeaders (). apply { setBasicAuth("user", "password") } 38

Slide 39

Slide 39 text

Authorized requests In a microservices environment you often have to pass user authorization from service to service. val rest = RestTemplate () val headers = HttpHeaders (). apply { set(AUTHORIZATION , auth) } val entity = HttpEntity(body , headers) val response = rest. postForObject ( url , entity , Response :: class.java) This gets boring very soon. 39

Slide 40

Slide 40 text

Authorized requests II Let’s create an extension function which helps fun RestTemplate .withUser( auth: String , block: RestTemplate .() -> T ): T { val ic = ClientHttpRequestInterceptor { request , body , execution -> request.headers.set( AUTHORIZATION , auth) execution.execute(request , body) } interceptors.add(ic) try { return block () } finally { interceptors.remove(ic) } } 40

Slide 41

Slide 41 text

Authorized requests III Usage: val rest = RestTemplate () val response = rest.withUser(auth) { postForObject( url , entity , Response :: class.java) } Make sure not to share instances of such a RestTemplate or store the authorization in a ThreadLocal! 41

Slide 42

Slide 42 text

SOAP Requests SOAP requests can be complex and often consist of deeply nested structures Let’s create a request to create a contact: Main request object is a CreateContactRequest Consists of the property contact which holds a B2CContact instance Contact has a list of address ids 42

Slide 43

Slide 43 text

SOAP Requests II val request = CreateContactRequest () val contact = B2CContact () contact.firstName = cmd.firstname contact.lastName = cmd.lastname contact.title = cmd.title contact.birthDate = cmd.birthday.toCalendar () contact.gender = cmd.gender.name.toGender () contact.email = cmd.email val addressIds = AddressIds () addressIds.addressId += cmd.addressId request.contact = contact The linear structure is not easy to grasp. Easy to forget to link an item. Can you spot the mistake? And that’s a trivial example... Can we do better? You guessed right: Let’s use apply! 43

Slide 44

Slide 44 text

SOAP Requests III val request = CreateContactRequest (). apply { contact = B2CContact (). apply { firstName = cmd.firstname lastName = cmd.lastname title = cmd.title birthDate = cmd.birthday.toCalendar () gender = cmd.gender.name.toGender () email = cmd.email addressIds = AddressIds (). apply { addressId += cmd.addressId } } } Code structure exactly reflects XML structure. Beautiful. 44

Slide 45

Slide 45 text

Testing

Slide 46

Slide 46 text

Test Method Names TestMethodsTendToGetLongAndTheyAreHardToRead: @Test fun emailAdressesCanNotBeUsedTwice () {} This was my workaround in Java: @Test fun email_adresses_can_not_be_used_twice () {} But Kotlin just allows methods with spaces in the name: @Test fun `email adresses can not be used twice `() {} Please, don’t use this in production code. ; ) 46

Slide 47

Slide 47 text

JSON in tests As for @Query JSON/XML strings in tests are good use case for multi line strings: val userJson = """ { "firstname ": "Max", "lastname ": "Mustermann", "roles ": [ "customer" ] } """ val user = ObjectMapper () .readValue(userJson , User :: class.java) Tip: You can even tell IntelliJ it’s a JSON string by prepending it with // language=JSON. This enables syntax checking and highlighting. 47

Slide 48

Slide 48 text

Factory Methods for Tests fun testUser( fname: String , lname: String , email: String ) = User(fname , lname , email) fun testUser () = testUser("max", "muster", "[email protected]") fun testUser(fname: String) = testUser(fname , "muster", "[email protected]") fun testUser(fname: String , lname: String) = testUser(fname , lname , "[email protected]") Often seen in Java codebases. The amount of methods explodes really fast and it’s hard to maintain. Get’s even more fun when argument types collide, how do you define a method to just set the lastname? 48

Slide 49

Slide 49 text

Factory Methods for Tests II In Kotlin we can use default argument values and named arguments instead: fun testUser( firstname: String = "Max", lastname: String = "Mustermann" email: String = "max. mustermann@example .com" ) = User(firstname , lastname , email) @Test fun `my test `() { val user = testUser( lastname = "Muster", email = " maxmuster@example .com" ) } Uses default values if not explicitly defined. No more typing boilerplate and easy to extend. 49

Slide 50

Slide 50 text

Mockito Mockito has a very unfortunate method name conflict with when: Mockito.`when `(service.foo ()). thenReturn("bar") Escaping of method names isn’t pretty. 50

Slide 51

Slide 51 text

Mockito II The mockito-kotlin module has some neat methods: whenever(service.foo ()). thenReturn("bar") Also has alternative methods for e.g. anyObject() which don’t cause problems in Kotlin (those methods return null and Mockito fails). 51

Slide 52

Slide 52 text

Mockito III Instead of the default mocking syntax, which is a little bit verbose: val service = Mockito.mock(Service :: class.java) Allows some more compact syntax leveraging reified generics val service = mock () or val service: Service = mock () Allows easier definition of method stubs within mock, check their wiki. 52

Slide 53

Slide 53 text

Using JUnit Rules JUnit rules work just as in Java but JUnit won’t see the annotation on the property. Hence you have to add get: to specify the use-site target: @get:Rule val myRule = MyRule () The alternative is to use the @JvmField annotation. 53

Slide 54

Slide 54 text

Injecting Spring Services For integration tests it’s handy to inject Spring services: @SpringBootTest @RunWith(SpringRunner :: class) class MyIntegrationTest { @Autowired lateinit var myService: MyService } Spring will process those annotations and set your services. Use lateinit only for fields set by a framework. 54

Slide 55

Slide 55 text

Bonus

Slide 56

Slide 56 text

Use dcevm Dynamic Code Evolution VM Allows to make structural changes (e.g. adding methods) to classes and dynamically reloads them Patches your JVM installation - make sure to use a dedicated installation Specify patched JVM in IntelliJ, run your project in debug mode, press Ctrl+F9 and changes will reload Not perfect but saves a lot of time 56

Slide 57

Slide 57 text

System.exit(0) Questions? 57

Slide 58

Slide 58 text

finally We’re looking for more developers to build awesome stuff :) Part or full time From Student to Senior Developer Java knowledge, willing to learn Kotlin ;) Experience with frontend development is welcome Talk to me or contact me at [email protected] 58

Slide 59

Slide 59 text

References Introducing Kotlin Support in Spring Framework 5.0: https://spring.io/blog/2017/01/04/ introducing-kotlin-support-in-spring-framework-5-0 Improve Kotlin Support in Spring Boot https://github.com/spring-projects/spring-boot/issues/5537 starjack: https://starjack.at Terraform: https://terraform.io Ansible: https://ansible.com Nomad: https://nomadproject.io Consul: https://consul.io Kotlin 1.0.6 is here: https: //blog.jetbrains.com/kotlin/2016/12/kotlin-1-0-6-is-here/ Mockito-Kotlin: https://github.com/nhaarman/mockito-kotlin jackson-module-kotlin: https://github.com/FasterXML/jackson-module-kotlin dcevm: https://dcevm.github.io/ 59