Slide 1

Slide 1 text

withContextͬͯ εϨου੾Γସ͑Ҏ֎ʹ΋ ࢖͑Δͬͯ஌ͬͯͨʁ 2024/06/22 (Sat) Kotlin Fest 2024 T45K @getupmax

Slide 2

Slide 2 text

ࢿྉ͸ͪ͜Β https://x.com/getupmax

Slide 3

Slide 3 text

Kotlin Coroutine ࢖ͬͯ·͔͢ʁ 🙋

Slide 4

Slide 4 text

Coroutine͓͞Β͍ ~ AndroidΛ୊ࡐͱͯ͠ ~ https://developer.android.com/kotlin/coroutines

Slide 5

Slide 5 text

UIεϨου্Ͱ ͕͔͔࣌ؒΔॲཧΛͦͷ··ॻ͘ Coroutine͓͞Β͍ https://developer.android.com/kotlin/coroutines#executing-in-a-background-thread

Slide 6

Slide 6 text

UIεϨου্Ͱ ͕͔͔࣌ؒΔॲཧΛͦͷ··ॻ͘ ↓ ϑϦʔζ͢Δ Coroutine͓͞Β͍ https://developer.android.com/kotlin/coroutines#executing-in-a-background-thread

Slide 7

Slide 7 text

class LoginRepository(private val responseParser: LoginResponseParser) { private const val loginUrl = "https://example.com/login" // ݱࡏͷεϨουΛϒϩοΫ͢ΔɺωοτϫʔΫϦΫΤετΛߦ͏ؔ਺ // ͭ·ΓɺϩάΠϯ͕׬ྃ͢Δ·ͰΞϓϦ͕ϑϦʔζ͢Δ fun makeLoginRequest(jsonBody: String): Result { val url = URL(loginUrl) (url.openConnection() as? HttpURLConnection)?.run { ... } return ... } } https://developer.android.com/kotlin/coroutines#executing-in-a-background-thread

Slide 8

Slide 8 text

APIݺͼग़͠ΛIOεϨουͰ΍Γ͍ͨ Coroutine͓͞Β͍

Slide 9

Slide 9 text

APIݺͼग़͠ΛIOεϨουͰ΍Γ͍ͨ ↓ withContext(Dispatchers.IO)ͰғΉ Coroutine͓͞Β͍

Slide 10

Slide 10 text

class LoginRepository(...) { ... suspend fun makeLoginRequest(jsonBody: String): Result { // APIݺͼग़͠ΛIO DispatcherͰ࣮ߦ͢Δ return withContext(Dispatchers.IO) { val url = URL(loginUrl) (url.openConnection() as? HttpURLConnection)?.run { ... } return ... } } } https://developer.android.com/kotlin/coroutines#use-coroutines-for-main-safety

Slide 11

Slide 11 text

class LoginViewModel(private val loginRepository: LoginRepository) : ViewModel() { fun login(username: String, token: String) { viewModelScope.launch { val jsonBody = "{ username: \"$username\", token: \"$token\"}" // makeLoginRequestͷ࣮ߦ͸IOεϨουͰߦΘΕΔ // ͦͷؒɺUIεϨου͸ϒϩοΫ͞Εͳ͍ͷͰɺϢʔβૢ࡞Λड͚෇͚ΒΕΔ val result = loginRepository.makeLoginRequest(jsonBody) // ݁ՌΛUIεϨουͰॲཧ͢Δ when (result) { is Result.Success -> ... else -> ... } } } }

Slide 12

Slide 12 text

ϩάΠϯதʹΞϓϦ͕ ϑϦʔζ͠ͳ͘ͳͬͨ 🎉 Coroutine͓͞Β͍

Slide 13

Slide 13 text

withContext(Dispatchers.XXX) ? Coroutine͓͞Β͍ ωοτϫʔΫॲཧ ܭࢉྔଟ͍ॲཧ UIपΓͷॲཧ Dispatchers.IO Dispatchers.Default Dispatchers.Main → → →

Slide 14

Slide 14 text

withContext͸ εϨου੾Γସ͑༻ʁ🤔

Slide 15

Slide 15 text

🙅

Slide 16

Slide 16 text

withContext͸ CoroutineContextΛ ੾Γସ͑Δ

Slide 17

Slide 17 text

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/

Slide 18

Slide 18 text

CoroutineContext.KeyΛΩʔ CoroutineContext.ElementΛόϦϡʔ ͱͨࣙ͠ॻ CoroutineContext

Slide 19

Slide 19 text

https://kotlinlang.org/api/latest/jvm/stdlib/kotlin.coroutines/-coroutine-context/

Slide 20

Slide 20 text

// StringܕΛอ࣋͢ΔίϯςΩετ class CoroutineContextA(val value: String) : CoroutineContext.Element { companion object Key : CoroutineContext.Key override val key: CoroutineContext.Key<*> = Key } // IntܕΛอ࣋͢ΔίϯςΩετ class CoroutineContextB(val value: Int) : CoroutineContext.Element { companion object Key : CoroutineContext.Key override val key: CoroutineContext.Key<*> = Key }

Slide 21

Slide 21 text

val contextA = CoroutineContextA("test") val contextB = CoroutineContextB(1) contextA[CoroutineContextA.Key]?.value == "test" contextB[CoroutineContextA.Key] == null val contextAB = contextA + contextB contextAB[CoroutineContextA.Key]?.value == "test" contextAB[CoroutineContextB.Key]?.value == 1 val contextABMinusA = contextAB.minusKey(CoroutineContextA.Key) contextABMinusA[CoroutineContextA.Key] == null

Slide 22

Slide 22 text

val contextA = CoroutineContextA("test") val contextB = CoroutineContextB(1) contextA[CoroutineContextA.Key]?.value == "test" contextB[CoroutineContextA.Key] == null val contextAB = contextA + contextB contextAB[CoroutineContextA.Key]?.value == "test" contextAB[CoroutineContextB.Key]?.value == 1 val contextABMinusA = contextAB.minusKey(CoroutineContextA.Key) contextABMinusA[CoroutineContextA.Key] == null

Slide 23

Slide 23 text

val contextA = CoroutineContextA("test") val contextB = CoroutineContextB(1) contextA[CoroutineContextA.Key]?.value == "test" contextB[CoroutineContextA.Key] == null val contextAB = contextA + contextB contextAB[CoroutineContextA.Key]?.value == "test" contextAB[CoroutineContextB.Key]?.value == 1 val contextABMinusA = contextAB.minusKey(CoroutineContextA.Key) contextABMinusA[CoroutineContextA.Key] == null

Slide 24

Slide 24 text

val contextA = CoroutineContextA("test") val contextB = CoroutineContextB(1) contextA[CoroutineContextA.Key]?.value == "test" contextB[CoroutineContextA.Key] == null val contextAB = contextA + contextB contextAB[CoroutineContextA.Key]?.value == "test" contextAB[CoroutineContextB.Key]?.value == 1 val contextABMinusA = contextAB.minusKey(CoroutineContextA.Key) contextABMinusA[CoroutineContextA.Key] == null

Slide 25

Slide 25 text

1. Ҿ਺Ͱ༩͑ΒΕͨCoroutineContextΛ 2. ݱࡏͷCoroutineContextʹՃ͑ͯ 3. ϒϩοΫΛ࣮ߦ͢Δ withContext

Slide 26

Slide 26 text

val contextA = CoroutineContextA("test") val contextB = CoroutineContextB(1) withContext(a) { withContext(b) { currentCoroutineContext()[CoroutineContextA.Key]?.value == "test" currentCoroutineContext()[CoroutineContextB.Key]?.value == 1 } currentCoroutineContext()[CoroutineContextA.Key]?.value == "test" currentCoroutineContext()[CoroutineContextB.Key] == null }

Slide 27

Slide 27 text

Կʹ࢖͑Δʁ

Slide 28

Slide 28 text

CoroutineContextʹ஋ΛೖΕͯ withContextʹ౉͢͜ͱͰ ίϯςΩετม਺ͱͯ͠ѻ͑Δ

Slide 29

Slide 29 text

ࠓ·ͰThreadLocalͱ͔Ͱ ؅ཧ͍ͯͨ͠ΞϨίϨΛ CoroutineͰ΋࢖͑ͦ͏

Slide 30

Slide 30 text

ར༻ྫ1 τϥϯβΫγϣϯ؅ཧ

Slide 31

Slide 31 text

఻౷తͳWeb FrameworkͰ͸ ThreadLocalͰτϥϯβΫγϣϯΛ؅ཧ τϥϯβΫγϣϯ؅ཧ

Slide 32

Slide 32 text

ྫ) Spring Framework

Slide 33

Slide 33 text

Coroutine͸࣮ߦεϨουΛ੾Γସ͑Δ →ThreadLocal͸࢖͑ͳ͍ τϥϯβΫγϣϯ؅ཧ

Slide 34

Slide 34 text

Coroutine͸࣮ߦεϨουΛ੾Γସ͑Δ →ThreadLocal͸࢖͑ͳ͍ ͷͰɺࣗલ؅ཧ τϥϯβΫγϣϯ؅ཧ

Slide 35

Slide 35 text

εϨουΛ؅ཧ͢ΔΦϒδΣΫτΛ CoroutineContextʹೖΕͯ؅ཧ͢Δ τϥϯβΫγϣϯ؅ཧ

Slide 36

Slide 36 text

jOOQͰͷτϥϯβΫγϣϯ؅ཧ val dsl: DSLContext = xxx dsl.transactionCoroutine { config -> config.dsl().execute("insert into table1 ...") config.dsl().execute("insert into table2 ...") }

Slide 37

Slide 37 text

class UseCase(private val dsl: DSLContext, private val service: Service) { suspend fun execute() { dsl.transactionCoroutine { config -> service.execute(config.dsl()) } } } class Service(private val repo: Repository) { suspend fun execute(ctx: DSLContext) { repo.save(ctx) } } class Repository { suspend fun save(ctx: DSLContext) { ctx.execute("insert into table1 ...") } }

Slide 38

Slide 38 text

DSLContextΦϒδΣΫτΛ CoroutineContextʹೖΕͯ؅ཧ͢Δ jOOQͷτϥϯβΫγϣϯ؅ཧ

Slide 39

Slide 39 text

// DSLContextΛอ࣋͢ΔElement class TransactionElement(val ctx: DSLContext) : AbstractCoroutineContextElement(TransactionElement) { companion object Key : CoroutineContext.Key } class TransactionManager(private val ctx: DSLContext) { suspend fun execute(block: suspend () -> T): T = ctx.transactionCoroutine { config -> withContext(TransactionElement(config.dsl())) { block() } } }

Slide 40

Slide 40 text

class Repository(private val dsl: DSLContext) { suspend fun save() { (coroutineContext[TransactionElement]?.ctx ?: this.ctx) .execute("insert into table1 ...") } } class UseCase( private val manager: TransactionManager, private val service: Service ) { suspend fun execute() { manager.execute { service.execute(config.dsl()) } } }

Slide 41

Slide 41 text

ར༻ྫ2 Ϣʔβ৘ใͷ؅ཧ

Slide 42

Slide 42 text

ϦΫΤετॲཧͷ࠷ॳ͔Β࠷ޙ·Ͱ ৘ใΛอ͍࣋ͨ͠ e.g., Tenant IDͳͲ Ϣʔβ৘ใͷ؅ཧ

Slide 43

Slide 43 text

class Controller(private val service: Service) { suspend fun get(session: Session) { val tenantId = session.getTenantId() service.execute(tenantId) } } class Service(private val repo: Repository) { suspend fun execute(tenantId: Long) { repo.select(tenantId) } } class Repository(private val ctx: DSLContext) { suspend fun select(tenantId: Long) { ctx.execute("select * table where tenant_id = ?", tenantId) } }

Slide 44

Slide 44 text

ϦΫΤετॲཧͷ࠷ॳ͔Β࠷ޙ·Ͱ ৘ใΛอ͍࣋ͨ͠ e.g., Tenant IDͳͲ Ϣʔβ৘ใͷ؅ཧ

Slide 45

Slide 45 text

ϦΫΤετॲཧͷ࠷ॳ͔Β࠷ޙ·Ͱ ৘ใΛอ͍࣋ͨ͠ e.g., Tenant IDͳͲ → CoroutineContextʹೖΕΔ Ϣʔβ৘ใͷ؅ཧ

Slide 46

Slide 46 text

class UserInfoElement(val tenantId: Long) : AbstractCoroutineContextElement(UserInfoElement) { companion object Key : CoroutineContext.Key } suspend fun getTenantId(): Long = coroutineContext[UserInfoElement]!!.tenantId class Controller(private val service: Service) { suspend fun get(session: Session) = withContext(UserInfoElement(session.getTenantId())){ service.execute() } } class Service(private val repo: Repository) { suspend fun execute() { repo.select() } } class Repository(private val ctx: DSLContext) { suspend fun select() { ctx.execute("select * table where tenant_id = ?", getTenantId()) } }

Slide 47

Slide 47 text

ͱ͜ΖͰ

Slide 48

Slide 48 text

͜Εͬͯ Context Parameterͷํ͕ྑ͍ʁ

Slide 49

Slide 49 text

with಺ͷม਺Λݺͼग़͠ઌͰ΋ࢀরͰ͖Δػೳ ʢ΋ͱ΋ͱ͸Context Receiverʣ Kotlin 2.1ΑΓಋೖ༧ఆ ʢexperimentalͱͯ͠ར༻Մೳʣ Context Parameter https://github.com/Kotlin/KEEP/blob/context-parameters/proposals/context-receivers.md

Slide 50

Slide 50 text

data class UserInfo(val tenantId: String) class ItemDao { context(UserInfo) fun selectById(itemId: Int): Item? = execute(""" select * from item where id = $itemId and tenant_id = $tenantId """) } get("/") { with(UserInfo("123456")) { call.respond(...) } }

Slide 51

Slide 51 text

͸CoroutineContextͱൺֱͯ͠... 👍 ίϯύΠϧ࣌ʹίϯςΩετͷݕ͕ࠪ͞ΕΔ 👍 Coroutine͡Όͳͯ͘΋࢖͑Δ 👎 Experimental 👎 ϥΠϒϥϦ/ϑϨʔϜϫʔΫͷαϙʔτ͕ෆे෼ Context Parameter

Slide 52

Slide 52 text

● Ϣʔβ৘ใ͸Context Receiver͕ྑͦ͞͏ ○ શͯͷϝιουʹ͓͍ͯඞਢ ○ ଘࡏ͢Δ͜ͱΛίϯύΠϧ࣌ʹอূ͍ͨ͠ ● τϥϯβΫγϣϯ͸CoroutineContext͕ྑͦ͞͏ ○ ଘࡏ͢Δ͜ͱΛίϯύΠϧ࣌ʹอূ͢Δඞཁ͕ͳ͍ ○ ʢSpring AOP͕Context ReceiverʹରԠ͍ͯ͠ͳ͍ʣ ࢖͍෼͚ https://github.com/Kotlin/KEEP/blob/context-parameters/proposals/context-parameters.md

Slide 53

Slide 53 text

·ͱΊ ● withContext͸CoroutineContextΛ੾Γସ͑Δ ● ಠࣗͷCoroutineContextΛఆٛ͢Δ͜ͱͰ
 ίϯςΩετม਺Λѻ͑Δ ● Context Parameterͱ͸੗Έ෼͚Ͱ͖ͦ͏ ● Kotlin 2.1ָ͠Έ✌

Slide 54

Slide 54 text

No content

Slide 55

Slide 55 text

ͨ·͝εϙϯαʔ΍ͬͯ·͢ ϊϕϧςΟ഑෍தͰ͢ KotlinΤϯδχΞืूͯ͠·͢ Android/αʔόʔαΠυ໰Θͣ એ఻