Slide 1

Slide 1 text

Microservices with Lovis Möller @lovisbrot [email protected] Kotlin and Ktor

Slide 2

Slide 2 text

Microservices — still a thing? https://www.amazon.de/Building-Microservices-Designing-Fine-Grained-Systems/dp/1492034029/

Slide 3

Slide 3 text

It depends.

Slide 4

Slide 4 text

Quickly adapt to changes of a fast-moving market.

Slide 5

Slide 5 text

Quickly adapt to changes of a fast-moving market. Works best with multiple small teams.

Slide 6

Slide 6 text

Quickly adapt to changes of a fast-moving market. Works best with multiple small teams. Not cheap.

Slide 7

Slide 7 text

Ok! Microservices it is. Let’s do it with Kotlin and Ktor.

Slide 8

Slide 8 text

• Consice, Safe, Pragmatic and tool-friendly • Created by JetBrains • Runs on the JVM, multi-platform and native. • 100% interoperable with Java • Open Source Kotlin

Slide 9

Slide 9 text

• Framework for Webservers (and -clients) • Written in Kotlin • Coroutines, Extension functions, internal DSLs • Open Source • https://ktor.io/quickstart/ Ktor

Slide 10

Slide 10 text

fun main() { embeddedServer(Netty, port = 8080) { routing { get("/") { call.respondText("Hello World!") } } }.start(wait = true) }

Slide 11

Slide 11 text

fun main() { embeddedServer(Netty, port = 8080) { routing { get("/") { call.respondText("Hello World!") } } }.start(wait = true) }

Slide 12

Slide 12 text

fun main() { embeddedServer(Netty, port = 8080) { routing { get("/") { call.respondText("Hello World!") } } }.start(wait = true) }

Slide 13

Slide 13 text

fun main() { embeddedServer(Netty, port = 8080) { routing { get("/") { call.respondText("Hello World!") } } }.start(wait = true) } Named Arguments

Slide 14

Slide 14 text

fun main() { embeddedServer(Netty, port = 8080) { routing { get("/") { call.respondText("Hello World!") } } }.start(wait = true) }

Slide 15

Slide 15 text

fun main() { embeddedServer(Netty, port = 8080) { routing { get("/") { call.respondText("Hello World!") } } }.start(wait = true) }

Slide 16

Slide 16 text

fun main() { embeddedServer( Netty, port = 8080, module = Application mainModule ).start(wait = true) }

Slide 17

Slide 17 text

fun main() { embeddedServer( Netty, port = 8080, module = Application module ).start(wait = true) }

Slide 18

Slide 18 text

fun Application.module() { routing { get("/") { call.respondText("Hello World!") } } }

Slide 19

Slide 19 text

fun Application.module() { routing { get("/") { call.respondText("Hello World!") } } } Extension Function

Slide 20

Slide 20 text

fun Application.module() { routing { get("/") { call.respondText("Hello World!") } } }

Slide 21

Slide 21 text

fun Application.module() { this.routing { get("/") { call.respondText("Hello World!") } } }

Slide 22

Slide 22 text

fun Application.module() { this.routing { get("/") { call.respondText("Hello World!") } } }

Slide 23

Slide 23 text

fun Application.module() { this.routing { get("/") { call.respondText("Hello World!") } } } this == Application

Slide 24

Slide 24 text

fun String.shuffle(): String { return this.toCharArray() .toList() .shuffled() .joinToString("") }

Slide 25

Slide 25 text

fun String.shuffle(): String { return this.toCharArray() .toList() .shuffled() .joinToString("") }

Slide 26

Slide 26 text

fun String.shuffle(): String { return this.toCharArray() .toList() .shuffled() .joinToString("") }

Slide 27

Slide 27 text

fun String.shuffle(): String { return this.toCharArray() .toList() .shuffled() .joinToString("") }

Slide 28

Slide 28 text

fun String.shuffle(): String { return this.toCharArray() .toList() .shuffled() .joinToString("") }

Slide 29

Slide 29 text

fun String.shuffle(): String { return this.toCharArray() .toList() .shuffled() .joinToString("") }

Slide 30

Slide 30 text

fun shuffle(): String { return this.toCharArray() .toList() .shuffled() .joinToString("") } Error!

Slide 31

Slide 31 text

fun String.shuffle(): String { return this.toCharArray() .toList() .shuffled() .joinToString("") }

Slide 32

Slide 32 text

fun String.shuffle(): String { return this.toCharArray() .toList() .shuffled() .joinToString("") }

Slide 33

Slide 33 text

fun String.shuffle(): String { return this.toCharArray() .toList() .shuffled() .joinToString("") }

Slide 34

Slide 34 text

fun String.shuffle(): String { return this.toCharArray() .toList() .shuffled() .joinToString("") }

Slide 35

Slide 35 text

fun String.shuffle(): String { return this.toCharArray() .toList() .shuffled() .joinToString("") } val shuffled = "techcamp".shuffle() println(shuffled)

Slide 36

Slide 36 text

fun String.shuffle(): String { return this.toCharArray() .toList() .shuffled() .joinToString("") } $ mactceph val shuffled = "techcamp".shuffle() println(shuffled)

Slide 37

Slide 37 text

Microservices Microservice ≈ Bounded Context Domain for this talk: Cat food configurator https://github.com/lmller/ktor-microservices-sample Sample code at

Slide 38

Slide 38 text

Cat food configurator Configure Food Add to cart Checkout Ingredient-warehouse maintenance Shipment Customer Self-Care

Slide 39

Slide 39 text

Cat food configurator Configure Food Add to cart Checkout Shipment Customer Warehouse employee Backoffice employee Ingredient-warehouse maintenance Customer Self-Care

Slide 40

Slide 40 text

Cat food configurator Configure Food Add to cart Checkout Shipment Customer Warehouse employee Backoffice employee Ingredient-warehouse maintenance Customer Self-Care Potential Users

Slide 41

Slide 41 text

Cat food configurator Configure Food Add to cart Checkout Shipment Ingredient-warehouse maintenance Customer Self-Care Potential Communication

Slide 42

Slide 42 text

Cat food configurator Configure Food Add to cart Checkout Shipment Ingredient-warehouse maintenance Customer Self-Care

Slide 43

Slide 43 text

No content

Slide 44

Slide 44 text

fun Application.module() { val db = WarehouseDB() routing { get("/stock") { call.respond(db.stock.map {(name,quantity)  StockDto(name, quantity) }) } } } Warehouse

Slide 45

Slide 45 text

fun Application.module() { val db = WarehouseDB() routing { get("/stock") { call.respond(db.stock.map {(name,quantity)  StockDto(name, quantity) }) } } } Warehouse

Slide 46

Slide 46 text

fun Application.module() { val db = WarehouseDB() routing { get("/stock") { call.respond(db.stock.map {(name,quantity)  StockDto(name, quantity) }) } } } Warehouse

Slide 47

Slide 47 text

fun Application.module() { val db = WarehouseDB() routing { get("/stock") { call.respond(db.stock.map {(name,quantity)  StockDto(name, quantity) }) } } } Warehouse

Slide 48

Slide 48 text

fun Application.module() { val db = WarehouseDB() routing { get("/stock") { call.respond(db.stock.map {(name,quantity)  StockDto(name, quantity) }) } } } 500 InternalServerError Warehouse

Slide 49

Slide 49 text

fun Application.module() { val db = WarehouseDB() routing { get("/stock") { call.respond(db.stock.map {(name,quantity)  StockDto(name, quantity) }) } } } Warehouse

Slide 50

Slide 50 text

fun Application.module() {  routing { get("/stock") { call.respond(warehouse.stock.map { (k, v)  StockDto(k, v) }) }  Warehouse

Slide 51

Slide 51 text

fun Application.module() { install(ContentNegotiation) { jackson { enable(SerializationFeature.INDENT_OUTPUT) } }  routing { get("/stock") { call.respond(warehouse.stock.map { (k, v)  StockDto(k, v) }) }  Warehouse

Slide 52

Slide 52 text

fun Application.module() { install(ContentNegotiation) { jackson { enable(SerializationFeature.INDENT_OUTPUT) } }  routing { get("/stock") { call.respond(warehouse.stock.map { (k, v)  StockDto(k, v) }) }  Warehouse

Slide 53

Slide 53 text

fun Application.module() { install(ContentNegotiation) { jackson { enable(SerializationFeature.INDENT_OUTPUT) } }  routing { get("/stock") { call.respond(warehouse.stock.map { (k, v)  StockDto(k, v) }) }  [ { "name" : "Beef","quantity" : 100 }, { "name" : "Lamb","quantity" : 50 }, { "name" : "Fish","quantity" : 0 } ] Warehouse

Slide 54

Slide 54 text

get(" ") { }

Slide 55

Slide 55 text

get(" ") { } post(" ") { }

Slide 56

Slide 56 text

get(" ") { } post(" ") { } put(" ") { }

Slide 57

Slide 57 text

get(" ") { } post(" ") { } put(" ") { } patch(" ") { }

Slide 58

Slide 58 text

get(" ") { } post(" ") { } put(" ") { } patch(" ") { } delete(" ") { }

Slide 59

Slide 59 text

get(" ") { } post(" ") { } put(" ") { } patch(" ") { } delete(" ") { } head(" ") { }

Slide 60

Slide 60 text

get(" ") { } post(" ") { } put(" ") { } patch(" ") { } delete(" ") { } head(" ") { } options(" ") { }

Slide 61

Slide 61 text

get("/foo/bar") { } get("/foo/baz") { }

Slide 62

Slide 62 text

get("/foo/bar") { } get("/foo/baz") { } route("/foo") { get("/bar") { } get("/baz") { } }

Slide 63

Slide 63 text

get("/foo/bar") { } get("/foo/baz") { } route("/foo") { get("/bar") { } get("/baz") { } } https://ktor.io/servers/features/routing.html#routing-tree

Slide 64

Slide 64 text

fun Application.module() { install(ContentNegotiation) { … } val db = WarehouseDB() routing { get("/stock") { call.respond(db.stock.map {(name,quantity)  StockDto(name, quantity) }) } } } Warehouse

Slide 65

Slide 65 text

fun Application.module() { install(ContentNegotiation) { … } val db = WarehouseDB() routing { get("/stock") { call.respond(db.stock.map {(name,quantity)  StockDto(name, quantity) }) } } } Warehouse

Slide 66

Slide 66 text

private object StockTable : UUIDTable() { val itemName = varchar("itemName", 128).uniqueIndex() val quantity = integer("quantity") } Warehouse

Slide 67

Slide 67 text

private object StockTable : UUIDTable() { val itemName = varchar("itemName", 128).uniqueIndex() val quantity = integer("quantity") } Warehouse

Slide 68

Slide 68 text

private object StockTable : UUIDTable() { val itemName = varchar("itemName", 128).uniqueIndex() val quantity = integer("quantity") } Warehouse

Slide 69

Slide 69 text

private object StockTable : UUIDTable() { val itemName = varchar("itemName", 128).uniqueIndex() val quantity = integer("quantity") }  insert { column -> column[itemName] = "Fish" column[quantity] = 100 } Warehouse

Slide 70

Slide 70 text

private object StockTable : UUIDTable() { val itemName = varchar("itemName", 128).uniqueIndex() val quantity = integer("quantity") }  insert { column -> column[itemName] = "Fish" column[quantity] = 100 }  StockTable.selectAll() .map { it[StockTable.itemName] to it[StockTable.quantity] } .toMap() Warehouse

Slide 71

Slide 71 text

Warehouse private object StockTable : UUIDTable() { val itemName = varchar("itemName", 128).uniqueIndex() val quantity = integer("quantity") }  insert { column -> column[itemName] = "Fish" column[quantity] = 100 }  StockTable.selectAll() .map { it[StockTable.itemName] to it[StockTable.quantity] } .toMap() https://github.com/JetBrains/Exposed

Slide 72

Slide 72 text

fun Application.module() { install(ContentNegotiation) { … } routing { get("/stock") { … } put("/stock/{item}") { } } Warehouse

Slide 73

Slide 73 text

fun Application.module() { install(ContentNegotiation) { … } routing { get("/stock") { … } put("/stock/{item}") { val itemName = call.parameters.getOrFail("item") val stock = call.receive() db.update(itemName, stock.quantity ?: 0) call.respond(OK) } } Warehouse

Slide 74

Slide 74 text

fun Application.module() { install(ContentNegotiation) { … } routing { get("/stock") { … } put("/stock/{item}") { val itemName = call.parameters.getOrFail("item") val stock = call.receive() db.update(itemName, stock.quantity ?: 0) call.respond(OK) } } Warehouse

Slide 75

Slide 75 text

fun Application.module() { install(ContentNegotiation) { … } routing { get("/stock") { … } put("/stock/{item}") { val itemName = call.parameters.getOrFail("item") val stock = call.receive() db.update(itemName, stock.quantity ?: 0) call.respond(OK) } } Warehouse

Slide 76

Slide 76 text

fun Application.module() { install(ContentNegotiation) { … } routing { get("/stock") { … } put("/stock/{item}") { val itemName = call.parameters.getOrFail("item") val stock = call.receive() db.update(itemName, stock.quantity ?: 0) call.respond(OK) } } Warehouse

Slide 77

Slide 77 text

BASIC AUTH

Slide 78

Slide 78 text

fun Application.module() { install(ContentNegotiation) { … } install(Authentication) { } Warehouse

Slide 79

Slide 79 text

fun Application.module() { install(ContentNegotiation) { … } install(Authentication) { basic { validate { (user, pw)  if (user  "not-a-hacker" && pw  "supersecure!") UserIdPrincipal(user) else null } } } Warehouse

Slide 80

Slide 80 text

fun Application.module() { install(ContentNegotiation) { … } install(Authorization) { … } routing { get("/stock") { … } authenticate { put("/stock/{item}") { … } } } } Warehouse

Slide 81

Slide 81 text

No content

Slide 82

Slide 82 text

fun Application.module() { install(FreeMarker) { templateLoader = ClassTemplateLoader( ) } Shop

Slide 83

Slide 83 text

fun Application.module() { install(FreeMarker) { templateLoader = ClassTemplateLoader( ) } routing { get("/") { val items = warehouseService.getStock() call.respond(FreeMarkerContent( "index.ftl", mapOf("items" to items) ) }
 } Shop

Slide 84

Slide 84 text

fun Application.module() { install(FreeMarker) { templateLoader = ClassTemplateLoader( ) } routing { get("/") { val items = warehouseService.getStock() call.respond(FreeMarkerContent( "index.ftl", mapOf("items" to items) ) }
 } Shop

Slide 85

Slide 85 text

Items

    <#list items as item>
  • ${item.name} - ${item.price}
Shop

Slide 86

Slide 86 text

fun Application.module() { install(FreeMarker) { templateLoader = ClassTemplateLoader( ) } routing { get("/") { val items = warehouseService.getStock() call.respond(FreeMarkerContent( "index.ftl", mapOf("items" to items) ) }
 } Shop

Slide 87

Slide 87 text

Shop - Warehouse Api suspend fun getStock(): List { return httpClient.get

Slide 88

Slide 88 text

Shop - Warehouse Api suspend fun getStock(): List { return httpClient.get

Slide 89

Slide 89 text

suspend fun getStock(): List { return httpClient.get

Slide 90

Slide 90 text

Shop - ShoppingCart & Orders Api suspend fun addToCart(item: Item) { httpClient.put(shoppingCartUrl) { body = JsonContent(item) } }

Slide 91

Slide 91 text

suspend fun addToCart(item: Item) { httpClient.put(shoppingCartUrl) { body = JsonContent(item) } } Shop - ShoppingCart & Orders Api

Slide 92

Slide 92 text

suspend fun addToCart(item: Item) { httpClient.put(shoppingCartUrl) { body = JsonContent(item) } } private val shoppingCartUrl by lazy { application.environment.config .property("orders.shoppingcartUrl").getString() } Shop - ShoppingCart & Orders Api

Slide 93

Slide 93 text

No content

Slide 94

Slide 94 text

Shopping Cart & Orders Service routing { put("/shoppingcart/items") { } }

Slide 95

Slide 95 text

routing { put("/shoppingcart/items") { val session = call.getSession() val itemToAdd = call.receive() call.sessions.set( session.copy(shoppingCart = session.shoppingCart + itemToAdd) ) call.respond(Created, "Added ${itemToAdd.name} to cart.") } } Shopping Cart & Orders Service

Slide 96

Slide 96 text

routing { put("/shoppingcart/items") { val session = call.sessions.get() val itemToAdd = call.receive() call.sessions.set( session.copy(shoppingCart = session.shoppingCart + itemToAdd) ) call.respond(Created, "Added ${itemToAdd.name} to cart.") } } Shopping Cart & Orders Service

Slide 97

Slide 97 text

routing { put("/shoppingcart/items") { val session = call.sessions.get() val itemToAdd = call.receive() call.sessions.set( session.copy(shoppingCart = session.shoppingCart + itemToAdd) ) call.respond(Created, "Added ${itemToAdd.name} to cart.") } } Shopping Cart & Orders Service

Slide 98

Slide 98 text

routing { put("/shoppingcart/items") { val session = call.sessions.get() val itemToAdd = call.receive() call.sessions.set( session.copy(shoppingCart = session.shoppingCart + itemToAdd) ) call.respond(Created, "Added ${itemToAdd.name} to cart.") } } ⚠ Sessions not RESTful Shopping Cart & Orders Service

Slide 99

Slide 99 text

install(Sessions) { cookie("CART", storage = SessionStorageMemory()) { cookie.path = "/" serializer = jacksonSessionSerializer() } } Shopping Cart & Orders Service

Slide 100

Slide 100 text

install(Sessions) { cookie("CART", storage = SessionStorageMemory()) { cookie.path = "/" serializer = jacksonSessionSerializer() } } data class UserSession(val name: String, val shoppingCart: List) Shopping Cart & Orders Service

Slide 101

Slide 101 text

install(Sessions) { cookie("CART", storage = SessionStorageMemory()) { cookie.path = "/" serializer = jacksonSessionSerializer() } } data class UserSession(val name: String, val shoppingCart: List) Shopping Cart & Orders Service

Slide 102

Slide 102 text

install(Sessions) { cookie("CART", storage = SessionStorageMemory()) { cookie.path = "/" serializer = jacksonSessionSerializer() } } data class UserSession(val name: String, val shoppingCart: List) toString() hashCode() equals() copy() Shopping Cart & Orders Service

Slide 103

Slide 103 text

Microservices with Ktor - a thing?

Slide 104

Slide 104 text

Ktor Not covered • Resilience • Monitoring & Metrics • Testing • Real Security

Slide 105

Slide 105 text

Ktor Not covered • Resilience • Monitoring & Metrics • Testing • Real Security • Under heavy development • Documentation needs improvement • Community is very helpful

Slide 106

Slide 106 text

Ktor Not covered • Resilience • Monitoring & Metrics • Testing • Real Security • Under heavy development • Documentation needs improvement • Community is very helpful https://ktor.io/quickstart/ http://slack.kotlinlang.org/ https://kotlinlang.org/

Slide 107

Slide 107 text

Lovis Möller @lovisbrot [email protected] meetup.com/Kotlin-User-Group-Hamburg Images from https://unsplash.com/