Slide 1

Slide 1 text

No content

Slide 2

Slide 2 text

MARTIN VARGA | SOFTWARE DEVELOPER | @MARTINTEEVARGA Future of Jira Software Powered by Kotlin

Slide 3

Slide 3 text

No content

Slide 4

Slide 4 text

kotlinlang.org

Slide 5

Slide 5 text

No content

Slide 6

Slide 6 text

Google Pixel 2 Just Black Simply drop your screenshot on to this media placeholder TRELLO MOBILE

Slide 7

Slide 7 text

Jira For cloud and server Confluence For cloud and server OpsGenie Our recent acquisition Not just Trello

Slide 8

Slide 8 text

Not just mobile!

Slide 9

Slide 9 text

FUTURE OF JIRA SOFTWARE POWERED BY KOTLIN

Slide 10

Slide 10 text

JIRA SOFTWARE?

Slide 11

Slide 11 text

Plan, track and release software. JIRA SOFTWARE

Slide 12

Slide 12 text

No content

Slide 13

Slide 13 text

The Story of Jira From the developer’s perspective

Slide 14

Slide 14 text

Server Cloud 1001001 0 0 1 0 0 1 0 0 1 0 0 1 1101010 1010101 0 0 1 0 0 1 1110001001 0011110010 1100101011 01111110001 Single Code Base

Slide 15

Slide 15 text

Server Cloud 1001001 0 0 1 0 0 1 0 0 1 0 0 1 1101010 1010101 0 0 1 0 0 1

Slide 16

Slide 16 text

1101010 1010101 0 0 1 0 0 1 1101010 1010101 0 0 1 0 0 1

Slide 17

Slide 17 text

No content

Slide 18

Slide 18 text

1101010 1010101 0 0 1 0 0 1

Slide 19

Slide 19 text

11010101010101 0010010101001 00101010111010 1010101010010 0101010010010 10101110101010

Slide 20

Slide 20 text

110101010101010010010101 001001010101110101010101 010010010101001001010101 110101010101010010010101 001001010101110101010101 010010010101001001010101 110101010101010010010101 001001010101110101010101 010010010101001001010101 110101010101010010010101 001001010101110101010101

Slide 21

Slide 21 text

No content

Slide 22

Slide 22 text

110101010101010010010101 001001010101110101010101 010010010101001001010101 110101010101010010010101 001001010101110101010101 010010010101001001010101 110101010101010010010101 001001010101110101010101 010010010101001001010101 110101010101010010010101 001001010101110101010101

Slide 23

Slide 23 text

110101010101010010010101 001001010101110101010101 010010010101001001010101 110101010101010010010101 001001010101110101010101 010010010101001001010101 110101010101010010010101 001001010101110101010101 010010010101001001010101 110101010101010010010101 001001010101110101010101

Slide 24

Slide 24 text

110101010101010010010101 001001010101110101010101 010010010101001001010101 110101010101010010010101 001001010101110101010101 010010010101001001010101 110101010101010010010101 001001010101110101010101 010010010101001001010101 110101010101010010010101 001001010101110101010101

Slide 25

Slide 25 text

1101010101010100 1001010100100101 0101110101010101 010010010101001 0010101011101010 1010101001001010 1001001010101110 1 0 1 0 1 0 1010111 001001

Slide 26

Slide 26 text

1101010101 010100100 101010010 0101010111 0 1 0 1 0 1 1011100 1 0 0 1 0 1 1 0 1 0 1 0 1010111 001001

Slide 27

Slide 27 text

1101010 1010101 001001 0 1 0 1 0 1 1011100 1 0 0 1 0 1 1 0 1 0 1 0 1010111 001001 0 1 0 1 0 1 1011100 1 0 0 1 0 1

Slide 28

Slide 28 text

1101010 1010101 001001 0 1 0 1 0 1 1011100 1 0 0 1 0 1 1 0 1 0 1 0 1010111 001001 0 1 0 1 0 1 1011100 1 0 0 1 0 1

Slide 29

Slide 29 text

1101010 1010101 001001 0 1 0 1 0 1 1011100 1 0 0 1 0 1

Slide 30

Slide 30 text

No content

Slide 31

Slide 31 text

How Atlassian builds services Making developers productive with platform as a service

Slide 32

Slide 32 text

PaaS that allows devs to create and deploy a service in matter of minutes. MICROS

Slide 33

Slide 33 text

MICROS

Slide 34

Slide 34 text

name: "animalfacts" compose: animalfacts: image: internal.docker.repo/animalfacts tag: 'PROJECT_VERSION' ports: - 8080:8080 MICROS DESCRIPTOR

Slide 35

Slide 35 text

links: healthcheck: uri: heartbeat port: 8080 source: url: "git@bitbucket.org:atlassianlabs/animal-facts- graphql-gateway-demo.git" notifications: email: "animalfactsowner@atlassian.com" resources: - type: dynamo-db name: facts attributes: ReadWriteCapacityMode: ON_DEMAND

Slide 36

Slide 36 text

Jira Software Tech Stack DynamoDB or PostgreSQL Depends on the application. Project Reactor Mature reactive library. Kotlin For all new services. Spring Boot Custom wrapper that makes monitoring etc. easier.

Slide 37

Slide 37 text

Powered by Kotlin Making developers happy

Slide 38

Slide 38 text

Experience Have we learned Kotlin enough? Was it a success? Compatibility How well is it going to play with our current tech stacks? Staffing Are we going to have enough Kotlin developers available? Adopting Kotlin

Slide 39

Slide 39 text

Velocity Does it make our developers more productive? Industry Is it already adopted by the developers community? Adopting Kotlin

Slide 40

Slide 40 text

Slide 41

Slide 41 text

Experienced with Kotlin. Android Developer Ad

Slide 42

Slide 42 text

Experience or interest in Kotlin development. Software Engineer, Developer Productivity, Jira Cloud Ad

Slide 43

Slide 43 text

We happen to have a variety of languages within our stack including Kotlin, Python, and Ruby. Senior Backend Developer Ad

Slide 44

Slide 44 text

A case for a reactive app A real world example of an app that we built recently

Slide 45

Slide 45 text

LATENCY 250ms 250ms 250ms 750ms

Slide 46

Slide 46 text

LATENCY 10ms 250ms 10ms 10ms 280ms

Slide 47

Slide 47 text

Operators Useful for modifying and combining sources. Allows composition of libraries that add resilience, tracing etc. Async Calls a service, then does mostly nothing and waits. Perfect fit. Project reactor Clean code No callback hell.

Slide 48

Slide 48 text

val currentUser = "martinId".toMono() MONO

Slide 49

Slide 49 text

val recentUsers = listOf("alexId", "bobbieId", "charlieId", "drewId").toFlux() FLUX

Slide 50

Slide 50 text

val currentUser = “martinId”.toMono() val recentUsers = listOf("alexId", "bobbieId", "charlieId", "drewId").toFlux() currentUser.concatWith(recentUsers) .map { userService.getFirstName(it) } .take(3) .subscribe { println(it) } COMPOSITION & CLEAN CODE

Slide 51

Slide 51 text

Martin Alex Bobbie COMPOSITION & CLEAN CODE

Slide 52

Slide 52 text

currentUser.concatWith(recentUsers) .map { userService.getFirstName(it) } .timeout(Duration.ofMillis(2500)) .onErrorReturn("Recent user") .take(3) .subscribe { println(it) } COMPOSITION & CLEAN CODE

Slide 53

Slide 53 text

Martin Recent User Bobbie COMPOSITION & CLEAN CODE

Slide 54

Slide 54 text

ThreadLocal value = new ThreadLocal<>(); value.set(42); … Integer integer = value.get(); REACTOR GOTCHAS

Slide 55

Slide 55 text

currentUser.concatWith(recentUsers) .subscriberContext { sc -> sc.put("key", value) } .flatMap { userId -> Mono.subscriberContext().map { sc -> userService.getFullName(userId, sc.get("key")) } } .subscribe { println(it) } SUBSCRIBER CONTEXT

Slide 56

Slide 56 text

Get exactly what you want Query multiple sources at once, specify what you want. Strongly typed schema Schema is a living documentation. Mature web clients Clients like Apollo let us remove dependency on Redux in ReactJs web apps. GraphQL

Slide 57

Slide 57 text

type Query { board(id: ID): Board } type Board { id: ID name: String issues: [Issue] } type Issue { id: ID key: String } SCHEMA

Slide 58

Slide 58 text

POST /jsw/graphql query { board(id: 42) { name issues { key } } } QUERY

Slide 59

Slide 59 text

{ "data": { "board": { "name": "Software board” "issues": [ { "key": "KEY-1" } ] } } } RESPONSE

Slide 60

Slide 60 text

Project Reactor + GraphQL Java OUR SOLUTION

Slide 61

Slide 61 text

DataFetching Environment Subscriber Context PROJECT REACTOR GRAPHQL-JAVA

Slide 62

Slide 62 text

Let’s write some code GraphQL gateway, Jira Software style

Slide 63

Slide 63 text

GET https://catfact.ninja/fact { "fact": "The leopard is the most widespread of all big cats.", "length": 51 } CAT FACTS API

Slide 64

Slide 64 text

GET https://some-random-api.ml/facts/dog { "fact": "A dogs' first sense to develop is touch." } DOG FACTS API

Slide 65

Slide 65 text

Cat Facts Dog Facts

Slide 66

Slide 66 text

WHY NOT BOTH?

Slide 67

Slide 67 text

POST https://animalfactsgateway.com/grapqhl query { dog { fact length } cat { fact length } } ANIMAL FACTS GRAPHQL API

Slide 68

Slide 68 text

{ "data": { "dog": { "fact": "Dog fact", "length": 8 }, "cat": { "fact": "Cat fact", "length": 8 } } }

Slide 69

Slide 69 text

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile plugins { id("org.springframework.boot") version "2.1.6.RELEASE" id("io.spring.dependency-management") version "1.0.7.RELEASE" kotlin("jvm") version "1.2.71" kotlin("plugin.spring") version "1.2.71" } PROJECT DESCRIPTOR

Slide 70

Slide 70 text

dependencies { implementation("org.springframework.boot:spring-boot- starter-webflux") implementation("com.fasterxml.jackson.module:jackson- module-kotlin") implementation("org.jetbrains.kotlin:kotlin-reflect") implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8") implementation("com.graphql-java:graphql-java:11.0") } tasks.withType { kotlinOptions { freeCompilerArgs = listOf("-Xjsr305=strict") jvmTarget = "1.8" } }

Slide 71

Slide 71 text

package com.atlassian.jsw.factsdemo import o.s.b.autoconfigure.SpringBootApplication import o.s.b.runApplication @SpringBootApplication class DemoApplication fun main(args: Array) { runApplication(*args) } APPLICATION

Slide 72

Slide 72 text

type Query { cat: Fact dog: Fact } type Fact { fact: String length: Int } GRAPHQL SCHEMA

Slide 73

Slide 73 text

interface FactFetcher: DataFetcher> FETCHERS public interface DataFetcher { T get(DataFetchingEnvironment env) throws Exception; }

Slide 74

Slide 74 text

data class Fact(val fact: String, val length: Int) FACT CLASS

Slide 75

Slide 75 text

@Component class CatFactFetcher(val webClient: WebClient) : FactFetcher { override fun get(environment: DataFetchingEnvironment): CompletionStage = webClient.get().uri("https://catfact.ninja/fact") .retrieve() .bodyToMono(CatFact::class.java) .map { Fact(it.fact, it.length) } .toFuture() } data class CatFact(val fact: String, val length: Int) CAT FACTS FETCHER

Slide 76

Slide 76 text

@Component class DogFactFetcher(val webClient: WebClient) : FactFetcher { override fun get(environment: DataFetchingEnvironment): CompletionStage = webClient.get().uri("https://.../facts/dog") .retrieve() .bodyToMono(DogFact::class.java) .map { Fact(it.fact, it.fact.length) } .toFuture() } data class DogFact(val fact: String) DOG FACTS FETCHER

Slide 77

Slide 77 text

@Configuration class GraphqlConfig { @Bean fun schema(wiring: RuntimeWiring): GraphQLSchema = SchemaGenerator().makeExecutableSchema( parseSchema("/schema.graphqls"), wiring ) } GRAPHQL CONFIGURATION

Slide 78

Slide 78 text

@Configuration class GraphqlConfig { @Bean fun factsWiring( dogFactFetcher: DogFactFetcher, catFactFetcher: CatFactFetcher ): RuntimeWiring = RuntimeWiring.newRuntimeWiring() .type( newTypeWiring("Query") .dataFetcher("dog", dogFactFetcher) .dataFetcher("cat", catFactFetcher) ) .build() }

Slide 79

Slide 79 text

override fun processQuery( query: String ): Mono = Mono.fromFuture( GraphQL .newGraphQL(graphQLSchema) .build() .executeAsync( ExecutionInput.newExecutionInput() .query(query) .build() ) ) SERVICE METHOD

Slide 80

Slide 80 text

@PostMapping( path = ["/graphql"], consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE] ) fun graphql( @RequestBody body: Map ): Mono> { val query: String = body["query"] as String? ?: "{}" return graphQLService.processQuery(query) .map { it.toSpecification() } } REACTIVE API

Slide 81

Slide 81 text

{ "data": { "dog": { "fact": "“Him” and “Her” were the names of President Lyndon Johnson's two beagles.", "length": 73 }, "cat": { "fact": "Jaguars are the only big cats that don't roar.", "length": 46 } } } RESULT

Slide 82

Slide 82 text

Resilience Protect the downstream services from the upstream.

Slide 83

Slide 83 text

250ms 380ms 380ms

Slide 84

Slide 84 text

CAT FACTS BROKEN 250ms 30s 30s

Slide 85

Slide 85 text

{ "data": { "dog": {…}, "cat": null }, "errors": [ { "message": "Exception while fetching data (/cat) : Operation timed out", "path": [ "cat" ] } ] }

Slide 86

Slide 86 text

TIMEOUT 250ms 5000ms 5000ms

Slide 87

Slide 87 text

class CatFactFetcher( val webClient: WebClient ) : FactFetcher { override fun get(environment: DataFetchingEnvironment): CompletionStage = webClient.get().uri("https://catfact.ninja/fact") .retrieve() .bodyToMono(CatFact::class.java) .map { … } .timeout(Duration.ofMillis(5000)) .toFuture() } CAT FACTS WITH TIMEOUT

Slide 88

Slide 88 text

{ "data": { "dog": {…}, "cat": null }, "errors": [ { "message": "Exception while fetching data (/cat) : Did not observe any item or terminal signal within 5000ms in 'map' (and no fallback has been configured)", "path": [ “cat" ] } ] }

Slide 89

Slide 89 text

CIRCUIT BREAKER 250ms 0ms 250ms

Slide 90

Slide 90 text

CIRCUIT BREAKER Closed Open Half-open

Slide 91

Slide 91 text

class CatFactFetcher( val webClient: WebClient, val catBreaker: CircuitBreaker ) : FactFetcher { override fun get(environment: DataFetchingEnvironment): CompletionStage = webClient.get()
 … .timeout(Duration.ofMillis(2500)) .transform( CircuitBreakerOperator.of(catBreaker) ) .toFuture() } CAT FACTS FETCHER WITH CIRCUIT BREAKER

Slide 92

Slide 92 text

@Bean fun catCircuitBreaker(): CircuitBreaker { val circuitBreakerConfig = CircuitBreakerConfig.custom() .ringBufferSizeInClosedState(3) .ringBufferSizeInHalfOpenState(3) .waitDurationInOpenState(Duration.ofMillis(5000)) .failureRateThreshold(0.5f) .build() return circuitBreakerRegistry.circuitBreaker( "CatCCB", circuitBreakerConfig ) }

Slide 93

Slide 93 text

{ "data": { "dog": {…}, "cat": null }, "errors": [ { "message": "Exception while fetching data (/cat) : CircuitBreaker 'CatCCB' is OPEN and does not permit further calls", "path": [ "cat" ] } ] }

Slide 94

Slide 94 text

CIRCUIT BREAKER 250ms 0ms 250ms But we might miss some cat facts.

Slide 95

Slide 95 text

Takeaways Even if you forget everything else I said, please try…

Slide 96

Slide 96 text

GraphQL For APIs consumed from the front-end. Resilience To prevent propagating errors downstream. Reactive For I/O heavy apps. Takeaways

Slide 97

Slide 97 text

Beyond Future Because you were going to ask about coroutines…

Slide 98

Slide 98 text

GraphQL Java There are already pure Kotlin libraries. Spring boot has a set of starters too. Coroutines Support in Spring. New utilities for Project reactor. Alternatives

Slide 99

Slide 99 text

fun processQuery(query: String): Map @PostMapping( path = ["/graphql"], consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE] ) fun graphqlCoroutines( @RequestBody body: Map ): Mono> = mono(Unconfined) { val query: String = body["query"] as String? ?: "{}" graphQLService.processQuery(query) } REACTOR WITH COROUTINES

Slide 100

Slide 100 text

Resources Write your own reactive and resilient GraphQL gateway in Kotlin

Slide 101

Slide 101 text

1 2 3 Animal Facts GraphQL Gateway https://bitbucket.org/atlassianlabs/animal-facts-graphql-gateway-demo/src/ master/ graphql-java https://www.graphql-java.com/ Resilience4j https://github.com/resilience4j/resilience4j Resources / Code

Slide 102

Slide 102 text

4 GraphiQL https://github.com/graphql/graphiql Resources / Code 5 Coroutines-reactor https://github.com/Kotlin/kotlinx.coroutines/tree/master/reactive/kotlinx-coroutines- reactor

Slide 103

Slide 103 text

1 2 3 Kotlin at Trello Trello tech blog:
 https://tech.trello.com/kotlin-at-trello/ Engineering stateless, high-availability cloud services at Atlassian Atlassian Engineering Blog: https://www.atlassian.com/blog/technology/aws-scaling-multi-region-low-latency- service More about Micros Slightly outdated, but still relevant.
 https://www.youtube.com/watch?v=dc2nqzgqH24 Resources / Further information

Slide 104

Slide 104 text

1 Why not both? https://knowyourmeme.com/memes/why-not-both-why-dont-we-have-both Resources / Memes

Slide 105

Slide 105 text

MARTIN VARGA | SOFTWARE DEVELOPER | @MARTINTEEVARGA Future of Jira Software Powered by Kotlin

Slide 106

Slide 106 text

No content