Future of Jira Software Powered by Kotlin

Future of Jira Software Powered by Kotlin

Atlassian is on a decomposition journey. We split our monoliths in microservices and we build our microservices in Kotlin. Our services are reactive and resilient and working on them is kool.

This is a story of Jira Software services that we built in Kotlin with Spring Boot and Project Reactor, our success in interoperability with Java and how we solved issues when crossing the boundary between the reactive and non-reactive world.

11ebf5b478b3da4d1b9d8cc719abba8a?s=128

Martin T. Varga

August 24, 2019
Tweet

Transcript

  1. 1.
  2. 3.
  3. 5.
  4. 6.

    Google Pixel 2 Just Black Simply drop your screenshot on

    to this media placeholder TRELLO MOBILE
  5. 7.

    Jira For cloud and server Confluence For cloud and server

    OpsGenie Our recent acquisition Not just Trello
  6. 12.
  7. 14.

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

    Server Cloud 1001001 0 0 1 0 0 1 0

    0 1 0 0 1 1101010 1010101 0 0 1 0 0 1
  9. 17.
  10. 21.
  11. 26.

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

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

    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
  14. 30.
  15. 32.
  16. 33.
  17. 35.

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

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

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

    Velocity Does it make our developers more productive? Industry Is

    it already adopted by the developers community? Adopting Kotlin
  21. 40.

  22. 43.

    We happen to have a variety of languages within our

    stack including Kotlin, Python, and Ruby. Senior Backend Developer Ad
  23. 44.

    A case for a reactive app A real world example

    of an app that we built recently
  24. 47.

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

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

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

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

    type Query { board(id: ID): Board } type Board {

    id: ID name: String issues: [Issue] } type Issue { id: ID key: String } SCHEMA
  29. 59.
  30. 63.
  31. 68.

    { "data": { "dog": { "fact": "Dog fact", "length": 8

    }, "cat": { "fact": "Cat fact", "length": 8 } } }
  32. 72.

    type Query { cat: Fact dog: Fact } type Fact

    { fact: String length: Int } GRAPHQL SCHEMA
  33. 75.

    @Component class CatFactFetcher(val webClient: WebClient) : FactFetcher { override fun

    get(environment: DataFetchingEnvironment): CompletionStage<Fact?> = 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
  34. 76.

    @Component class DogFactFetcher(val webClient: WebClient) : FactFetcher { override fun

    get(environment: DataFetchingEnvironment): CompletionStage<Fact?> = 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
  35. 77.

    @Configuration class GraphqlConfig { @Bean fun schema(wiring: RuntimeWiring): GraphQLSchema =

    SchemaGenerator().makeExecutableSchema( parseSchema("/schema.graphqls"), wiring ) } GRAPHQL CONFIGURATION
  36. 78.

    @Configuration class GraphqlConfig { @Bean fun factsWiring( dogFactFetcher: DogFactFetcher, catFactFetcher:

    CatFactFetcher ): RuntimeWiring = RuntimeWiring.newRuntimeWiring() .type( newTypeWiring("Query") .dataFetcher("dog", dogFactFetcher) .dataFetcher("cat", catFactFetcher) ) .build() }
  37. 79.

    override fun processQuery( query: String ): Mono<ExecutionResult> = Mono.fromFuture( GraphQL

    .newGraphQL(graphQLSchema) .build() .executeAsync( ExecutionInput.newExecutionInput() .query(query) .build() ) ) SERVICE METHOD
  38. 80.

    @PostMapping( path = ["/graphql"], consumes = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE]

    ) fun graphql( @RequestBody body: Map<String, Any> ): Mono<Map<String, Any>> { val query: String = body["query"] as String? ?: "{}" return graphQLService.processQuery(query) .map { it.toSpecification() } } REACTIVE API
  39. 81.

    { "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
  40. 85.

    { "data": { "dog": {…}, "cat": null }, "errors": [

    { "message": "Exception while fetching data (/cat) : Operation timed out", "path": [ "cat" ] } ] }
  41. 87.

    class CatFactFetcher( val webClient: WebClient ) : FactFetcher { override

    fun get(environment: DataFetchingEnvironment): CompletionStage<Fact?> = webClient.get().uri("https://catfact.ninja/fact") .retrieve() .bodyToMono(CatFact::class.java) .map { … } .timeout(Duration.ofMillis(5000)) .toFuture() } CAT FACTS WITH TIMEOUT
  42. 88.

    { "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" ] } ] }
  43. 91.

    class CatFactFetcher( val webClient: WebClient, val catBreaker: CircuitBreaker ) :

    FactFetcher { override fun get(environment: DataFetchingEnvironment): CompletionStage<Fact?> = webClient.get()
 … .timeout(Duration.ofMillis(2500)) .transform( CircuitBreakerOperator.of(catBreaker) ) .toFuture() } CAT FACTS FETCHER WITH CIRCUIT BREAKER
  44. 92.

    @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 ) }
  45. 93.

    { "data": { "dog": {…}, "cat": null }, "errors": [

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

    GraphQL For APIs consumed from the front-end. Resilience To prevent

    propagating errors downstream. Reactive For I/O heavy apps. Takeaways
  47. 98.

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

    fun processQuery(query: String): Map<String, Any> @PostMapping( path = ["/graphql"], consumes

    = [MediaType.APPLICATION_JSON_VALUE], produces = [MediaType.APPLICATION_JSON_VALUE] ) fun graphqlCoroutines( @RequestBody body: Map<String, Any> ): Mono<Map<String, Any>> = mono(Unconfined) { val query: String = body["query"] as String? ?: "{}" graphQLService.processQuery(query) } REACTOR WITH COROUTINES
  49. 101.

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

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