$30 off During Our Annual Pro Sale. View Details »

Microservices with Spring Boot and Kotlin

Microservices with Spring Boot and Kotlin

Christoph Leiter

April 07, 2017
Tweet

More Decks by Christoph Leiter

Other Decks in Programming

Transcript

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

    View Slide

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

    View Slide

  3. 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

    View Slide

  4. starjack

    View Slide

  5. starjack
    A platform for buying ski tickets online:
    5

    View Slide

  6. 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

    View Slide

  7. 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

    View Slide

  8. 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

    View Slide

  9. starjack V
    Logical view on AWS
    9

    View Slide

  10. starjack VI
    Deployment view on AWS
    10

    View Slide

  11. The Basics

    View Slide

  12. Using Spring Initializr
    12

    View Slide

  13. Using Spring Initializr II
    13

    View Slide

  14. 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

    View Slide

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

    View Slide

  16. 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

    View Slide

  17. 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

    View Slide

  18. 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

    View Slide

  19. 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

    View Slide

  20. Kotlin Compiler Plugins II

    kotlin -maven -plugin
    org.jetbrains.kotlin
    ${kotlin.version}


    jpa
    spring
    compilerPlugins >
    configuration >
    20

    View Slide

  21. Kotlin Compiler Plugins III


    org.jetbrains.kotlin
    kotlin -maven -allopen
    ${kotlin.version}


    org.jetbrains.kotlin
    kotlin -maven -noarg
    ${kotlin.version}

    dependencies >

    21

    View Slide

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

    View Slide

  23. 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

    View Slide

  24. 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

    View Slide

  25. JPA

    View Slide

  26. 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

    View Slide

  27. 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

    View Slide

  28. 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

    View Slide

  29. 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

    View Slide

  30. 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

    View Slide

  31. 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

    View Slide

  32. Making Requests

    View Slide

  33. 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

    View Slide

  34. 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

    View Slide

  35. 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

    View Slide

  36. 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

    View Slide

  37. 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

    View Slide

  38. 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

    View Slide

  39. 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

    View Slide

  40. 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

    View Slide

  41. 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

    View Slide

  42. 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

    View Slide

  43. 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

    View Slide

  44. 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

    View Slide

  45. Testing

    View Slide

  46. 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

    View Slide

  47. 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

    View Slide

  48. 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

    View Slide

  49. 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

    View Slide

  50. 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

    View Slide

  51. 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

    View Slide

  52. 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

    View Slide

  53. 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

    View Slide

  54. 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

    View Slide

  55. Bonus

    View Slide

  56. 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

    View Slide

  57. System.exit(0)
    Questions?
    57

    View Slide

  58. 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

    View Slide

  59. 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

    View Slide