Share
Embed
Future of Jira Software Powered by Kotlin - JavaDay Istanbul 2020
Martin T. Varga
September 11, 2020
Transcript
107 slides
-
MARTIN VARGA | SOFTWARE DEVELOPER | @MARTINTEEVARGA Future of Jira
Software Powered by Kotlin -
Google Pixel 2 Just Black Simply drop your screenshot on
to this media placeholder TRELLO MOBILE -
Jira For cloud and server Confluence For cloud and server
OpsGenie Alerting and On-call management Not just Trello -
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 -
Server Cloud 1001001 0 0 1 0 0 1 0
0 1 0 0 1 1101010 1010101 0 0 1 0 0 1 -
1101010 1010101 0 0 1 0 0 1 1101010 1010101
0 0 1 0 0 1 -
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 -
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 -
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 -
1101010 1010101 001001 0 1 0 1 0 1 1011100
1 0 0 1 0 1 -
PaaS that allows devs to create and deploy a service
in matter of minutes. MICROS -
links: healthcheck: uri: heartbeat port: 8080 source: url: "[email protected]:atlassianlabs/animal-facts- graphql-gateway-demo.git"
notifications: email: "[email protected]" resources: - type: dynamo-db name: facts attributes: ReadWriteCapacityMode: ON_DEMAND -
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. -
final Integer length = bob != null && bob.department !=
null && bob.department.text != null ? bob.department.text.length() : null; WHY KOTLIN? https://www.kotlinvsjava.com/ -
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 -
Velocity Does it make our developers more productive? Industry Is
it already adopted by the developers community? Adopting Kotlin -
We happen to have a variety of languages within our
stack including Kotlin, Python, and Ruby. Senior Backend Developer Ad -
A case for a reactive app A real world example
of an app that we built -
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. -
val currentBoard = "5".toMono() val recentBoards = listOf("0", "1", "2").toFlux()
currentBoard.concatWith(recentBoards) COMPOSITION & CLEAN CODE 5 0 1 2 0 1 2 5 -
val currentBoard = "5".toMono() val recentBoards = listOf("0", "1", "2").toFlux()
currentBoard.concatWith(recentBoards) .map { boardService.getName(it) } .take(3) .subscribe { println(it) } COMPOSITION & CLEAN CODE -
currentBoard.concatWith(recentBoards) .map { boardService.getName(it) } .timeout(Duration.ofMillis(2500)) .onErrorReturn("Recent board”) .take(3) .subscribe
{ println(it) } COMPOSITION & CLEAN CODE -
val currentBoard = currentBoardServiceMono() val recentBoards = recentBoardsServiceFlux() return currentBoard.concatWith(recentBoards)
.map { boardService.getName(it) } .timeout(Duration.ofMillis(2500)) .onErrorReturn("Recent board”) .take(3) REAL WORLD EXAMPLE -
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 -
type Query { board(id: ID): Board } type Board {
id: ID name: String issues: [Issue] } type Issue { id: ID key: String } SCHEMA -
POST /jsw/graphql query { board(id: 42) { name issues {
key } } } QUERY -
{ "data": { "board": { "name": "Software board” "issues": [
{ "key": "KEY-1" } ] } } } RESPONSE -
GET https://catfact.ninja/fact { "fact": "The leopard is the most widespread
of all big cats.", "length": 51 } CAT FACTS API -
GET https://some-random-api.ml/facts/dog { "fact": "A dogs' first sense to develop
is touch." } DOG FACTS API -
POST https://animalfactsgateway.com/grapqhl query { dog { fact length } cat
{ fact length } } ANIMAL FACTS GRAPHQL API -
{ "data": { "dog": { "fact": "Dog fact", "length": 8
}, "cat": { "fact": "Cat fact", "length": 8 } } } -
package com.atlassian.jsw.factsdemo import o.s.b.autoconfigure.SpringBootApplication import o.s.b.runApplication @SpringBootApplication class DemoApplication fun
main(args: Array<String>) { runApplication<DemoApplication>(*args) } APPLICATION -
type Query { cat: Fact dog: Fact } type Fact
{ fact: String length: Int } GRAPHQL SCHEMA -
@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 -
@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 -
@Configuration class GraphqlConfig { @Bean fun schema(wiring: RuntimeWiring): GraphQLSchema =
SchemaGenerator().makeExecutableSchema( parseSchema(“/graphql/schema.graphqls"), wiring ) } GRAPHQL CONFIGURATION -
@Configuration class GraphqlConfig { @Bean fun factsWiring( dogFactFetcher: DogFactFetcher, catFactFetcher:
CatFactFetcher ): RuntimeWiring = RuntimeWiring.newRuntimeWiring() .type( newTypeWiring("Query") .dataFetcher("dog", dogFactFetcher) .dataFetcher("cat", catFactFetcher) ) .build() } -
override fun processQuery( query: String ): Mono<ExecutionResult> = Mono.fromFuture( GraphQL
.newGraphQL(graphQLSchema) .build() .executeAsync( ExecutionInput.newExecutionInput() .query(query) .build() ) ) SERVICE METHOD -
@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 -
{ "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 -
{ "data": { "dog": {…}, "cat": null }, "errors": [
{ "message": "Exception while fetching data (/cat) : Operation timed out", "path": [ "cat" ] } ] } -
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 -
{ "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" ] } ] } -
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 -
@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 ) } -
{ "data": { "dog": {…}, "cat": null }, "errors": [
{ "message": "Exception while fetching data (/cat) : CircuitBreaker 'CatCCB' is OPEN and does not permit further calls", "path": [ "cat" ] } ] } -
GraphQL For APIs consumed from the front-end. Resilience To prevent
propagating errors downstream. Reactive For I/O heavy apps. Takeaways -
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 -
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 -
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 -
1 Why not both? https://knowyourmeme.com/memes/why-not-both-why-dont-we-have-both Resources / Other 2 We
are hiring! Jira Software is looking for engineers, Java and Kotlin http://go.atlassian.com/jsw-hiring -
MARTIN VARGA | SOFTWARE DEVELOPER | @MARTINTEEVARGA Future of Jira
Software Powered by Kotlin