Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
R2DBCを導入しようとした話
Search
Tasuku Nakagawa
September 28, 2023
Programming
470
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
R2DBCを導入しようとした話
Tasuku Nakagawa
September 28, 2023
More Decks by Tasuku Nakagawa
See All by Tasuku Nakagawa
デッドコード消せてますか?構文解析とGradleプラグイン開発で始めるコードベース改善
t45k
4
1.8k
withContextってスレッド切り替え以外にも使えるって知ってた?
t45k
3
3k
DB呼び出し回数を減らしてコア機能を高速化した話
t45k
0
330
コードレビュー改善のためにJenkinsとIntelliJ IDEAのプラグインを自作してみた話
t45k
0
150
Other Decks in Programming
See All in Programming
Honoでのサプライチェーン侵害対策 〜 3つのライブラリに学ぶ
yusukebe
7
1.7k
正しくソフトウェアを作る、前提を疑うための認知の視点 / doubt-premise
minodriven
21
7.2k
Contextとはなにか
chiroruxx
1
390
そのテスト、説明できますか?~LWテスト戦略FW~のご紹介
nakahara
0
190
IBM Bobを活用したレガシーアプリの最新化
oniak3ibm
PRO
1
240
どこまでゆるくて許されるのか
tk3fftk
0
300
Skillsは効率化、Agentsは"自分の拡張"——Builder時代のエージェント編成(CC Night 2026)
wemra
1
190
コンテキストの使い捨てをやめる — ビジネスルール駆動開発と miko —
ioki
0
260
Even G2とAWSで推しのエージェントを召喚しよう!
har1101
1
140
自作OSでスライド発表する
uyuki234
1
3.6k
例外の正しい扱い方 そのエラー try-catchして大丈夫?
jinwatanabe
0
320
AIで効率化できた業務・日常
ochtum
0
160
Featured
See All Featured
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
118
120k
Code Reviewing Like a Champion
maltzj
528
40k
Keith and Marios Guide to Fast Websites
keithpitt
413
23k
Agile Leadership in an Agile Organization
kimpetersen
PRO
0
180
Six Lessons from altMBA
skipperchong
29
4.3k
A Modern Web Designer's Workflow
chriscoyier
698
190k
We Have a Design System, Now What?
morganepeng
55
8.2k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
201
75k
Templates, Plugins, & Blocks: Oh My! Creating the theme that thinks of everything
marktimemedia
31
2.8k
Designing for Timeless Needs
cassininazir
1
270
Faster Mobile Websites
deanohume
310
32k
Navigating Team Friction
lara
192
16k
Transcript
R2DBCΛ ಋೖ͠Α͏ͱͨ͠ 2023/09/29ʢۚʣ αʔόʔαΠυKotlin LTେձ vol.10 גࣜձࣾϚωʔϑΥϫʔυ Task
Task / Tasuku Nakagawa 20237݄͔Βݱ৬ େࡕڌۈ GitHub: T45K X (چTwitter):
getupmax ࢿྉ: https://speakerdeck.com/t45k/r2dbcwodao-ru-suruhua
ϚωʔϑΥϫʔυΫϥυ ࿈݁ձܭ
KotlinʢJVMʣͷݟ͕ͳ͍ঢ়ଶͰ։ൃ։࢝ →͍͔ͭ͘ͷΓ͍ͨ͜ͱΛఘΊͯϦϦʔε Ұ݅ͷܖͰϢʔβ͕Ͳͬͱ૿͑Δ →ૣ͍͏ͪʹεέʔϧ͢ΔΑ͏ʹ͍ͨ͠ ϚωʔϑΥϫʔυΫϥυ ࿈݁ձܭ
DBΞΫηεΛϊϯϒϩοΩϯάͰΔͭ ↕︎ JDBC R2DBC
• ϋϚΓϙΠϯτ • ʢݱঢ়ͷʣղܾࡦ ӡ༻ͷ͠·ͤΜʢ·ͩͰ͖ͳ͍ʣ R2DBCಋೖ
• Kotlin 1.9.0 • Spring Boot 3.0.5 • Spring WebFlux
• jOOQ 3.7.5 • MySQL ελοΫ
ϦϙδτϦ
@Repository class Repository(private val ctx: DSLContext) { fun findById(id: Long):
Record? = ctx.selectFrom(TABLE) .where(ID.eq(id)) .fetchOne() } DSLͰDB͍߹Θ͕ͤͰ͖ΔORM jOOQͱ
jOOQR2DBCʹରԠࡁΈ https://blog.jooq.org/reactive-sql-with-jooq-3-15-and-r2dbc/
dependencies { runtimeOnly("io.asyncer:r2dbc-mysql:1.0.2") runtimeOnly("io.r2dbc:r2dbc-pool:1.0.1.RELEASE") // ͪ͜Βݱࡏ։ൃ͕ࢭ·͍ͬͯΔ // runtimeOnly("dev.miku:r2dbc-mysql:0.8.2.RELEASE") } ґଘ
build.gradle.kts
spring: r2dbc: url: r2dbc://{jdbcͱಉ͡ઃఆ} username: username password: password pool: enabled:
true ઃఆ application.yaml
@Configuration class JooqConfiguration( private val connectionFactory: ConnectionFactory) { @Bean fun
dslContext(): DSLContext = DSL.using(connectionFactory).dsl() } jOOQ Config
jOOQͰR2DBCΛ ͏४උͬͨ
jOOQ 3.17͔Β coroutineΛαϙʔτ https://www.jooq.org/doc/3.17/manual/sql-building/kotlin-sql-building/kotlin-coroutines/
@Repository class Repository(private val ctx: DSLContext) { suspend fun findById(id:
Long): Record? = ctx.selectFrom(TABLE) .where(ID.eq(id)) .awaitFirstOrNull() } jOOQ 3.17ͷมߋ ΫΤϦ͕ PublisherΛฦ͢ Coroutineʹ มՄೳ
ͳ͍
jOOQ 3.17.5࣌Ͱ ࣮͞Ε͍ͯͳ͍ ʢηϚϯςΟοΫόʔδϣχϯά...ʣ
jOOQ 3.17.15 or 3.18ܥΛ ͏ͱղܾ
fetch/fetchOneΛ ଟ༻͍ͯ͠Δ
fetch/fetchOne ϒϩοΩϯά͢Δ શͯΛselectʹ ॻ͖͑Δͷ໘
fetch/fetchOneޓͷ ϝιουΛ४උͯ͠Ұׅஔ suspend inline fun <reified R : TableRecord<*>, T
: TableImpl<R>> DSLContext.nonBlockingFetchOne( tableImpl: T, vararg condition: Condition ): R? = this.select() .from(tableImpl) .where(*condition) .awaitFirstOrNull() ?.map { it.into(R::class.java) }
τϥϯβΫγϣϯ
ݱঢ়ɺR2DBCͰ @Transaction ͑ͳͦ͞͏
R2DBC༻ͷ τϥϯβΫγϣϯཧ
class UseCase( private val tx: TransactionalOperator, private val repo: Repository,
) { suspend fun call() = tx.execute(mono { repo.save() }) } Spring Frameworkۘ Ҿ͕Mono TransactionOperator
class UseCase( private val ctx: DSLContext, private val repo: Repository,
) { suspend fun call() = ctx.transactionCoroutine { repo.save() } } jOOQۘ DSLContext͔Βੜ͍͑ͯΔ transactionCoroutine
લऀjOOQඇରԠ ࣮࣭ޙऀҰ
class UseCase( private val ctx: DSLContext, private val repo: Repository,
) { suspend fun call() = ctx.transactionCoroutine { repo.save() } } jOOQۘ DSLContext͔Βੜ͍͑ͯΔ transactionCoroutine
class UseCase( private val ctx: DSLContext, private val repo: Repository,
) { suspend fun call() = ctx.transactionCoroutine { repo.save() } } jOOQۘ DSLContext͔Βੜ͍͑ͯΔ transactionCoroutine ͜Ε͚ͩͰޮ͔ͳ͍
class UseCase( private val ctx: DSLContext, private val repo: Repository,
) { suspend fun call() = ctx.transactionCoroutine { config -> repo.save(config.dsl()) } } ίʔϧόοΫͰDSLContextΛऔಘ ͦΕʹରͯ͠ΫΤϦΛ࣮ߦ͠ͳ͍ͱ͍͚ͳ͍ transactionCoroutineͷ᠘
όέπϦϨʔʁ class UseCase( private val ctx: DSLContext, private val service:
Service, ) { suspend fun call() = ctx.transactionCoroutine { config -> service.do(config.dsl()) } } class Service( private val repo: Repository, ) { suspend fun do(ctx: DSLContext) { repo.save(ctx) } }
όέπϦϨʔʁ class UseCase( private val ctx: DSLContext, private val service:
Service, ) { suspend fun call() = ctx.transactionCoroutine { config -> service.do(config.dsl()) } } class Service( private val repo: Repository, ) { suspend fun do(ctx: DSLContext) { repo.save(ctx) } } όέπϦϨʔ όέπϦϨʔ
ίʔϧόοΫͰݺΕͨ Repository DSLContextΛ Γସ͍͑ͨ
CoroutineContextͰ ࣋ͪճ͢
class TransactionElement(val ctx: DSLContext) : AbstractCoroutineContextElement(TransactionElement) { companion object Key
: CoroutineContext.Key<TransactionElement> } DSLContextΛอ࣋͢Δ CoroutineContext
@Component class TransactionManager(private val ctx: DSLContext) { suspend fun <T>
execute(block: suspend () -> T): T = ctx.transactionCoroutine { config -> withContext(coroutineContext + TransactionElement(config.dsl())) { block() } } } } ϚωʔδϟͰContextΛ߹
@Repository class Repository(private val ctx: DSLContext) { suspend fun getCtx()
= coroutineContext[TransactionElement.Key]?.ctx ?: this.ctx suspend fun save() { getCtx()... // CRUDॲཧ } } RepoͰ͏ctxΛΓସ͑
AOP
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Before("execution(* *Query.*(..))") fun check(joinPoint: JoinPoint): Any? { repo.find() ?: throw RuntimeException() } } AOPͰڞ௨ॲཧΛ͍ͯ͠Δ
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Before("execution(* *Query.*(..))") fun check(joinPoint: JoinPoint): Any? { repo.find() ?: throw RuntimeException() } } AOPͰڞ௨ॲཧΛ͍ͯ͠Δ ϦϙδτϦͷϝιου͕ suspendؔʹͳͬͨ
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Before("execution(* *Query.*(..))") fun check(joinPoint: JoinPoint): Any? { runBlocking { repo.find() } ?: throw RuntimeException() } } runBlocking͢Δʁ
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Before("execution(* *Query.*(..))") fun check(joinPoint: JoinPoint): Any? { runBlocking { repo.find() } ?: throw RuntimeException() } } runBlocking͢Δʁ ελοΫ͢Δ ʢݪҼෆ໌ʣ
Coroutineͷ࣮ߦʹ ׂΓࠐ·͍ͤͨ
suspend fun hoge(value: String){ } public static final Object hoge(
String value, Continuation $completion) { return Unit.INSTANCE; } suspendؔ Java͔Β͜͏ݟ͑Δ
suspend fun hoge(value: String){ } public static final Object hoge(
String value, Continuation $completion) { return Unit.INSTANCE; } suspendؔ Java͔Β͜͏ݟ͑Δ Continuation͕ Coroutineͷ͋Ε͜ΕΛ࣋ͭ
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Around("execution(* *Query.*(..))") fun check(pjp: ProceedingJoinPoint): Any? { val con = pjp.args.last() as Continuation<Any?> return suspend { repo.find() ?: throw RuntimeException() suspendCoroutineUninterceptedOrReturn<Any?> { con2 -> val args = pjp.args.sliceArray(0..<pjp.args.size - 1) joinPoint.proceed(args + con2) } }.startCoroutineUninterceptedOrReturn(con) } }
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Around("execution(* *Query.*(..))") fun check(pjp: ProceedingJoinPoint): Any? { val con = pjp.args.last() as Continuation<Any?> return suspend { repo.find() ?: throw RuntimeException() suspendCoroutineUninterceptedOrReturn<Any?> { con2 -> val args = pjp.args.sliceArray(0..<pjp.args.size - 1) joinPoint.proceed(args + con2) } }.startCoroutineUninterceptedOrReturn(con) } } JoinPoint͔Β ContinuationΛύΫΔ
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Around("execution(* *Query.*(..))") fun check(pjp: ProceedingJoinPoint): Any? { val con = pjp.args.last() as Continuation<Any?> return suspend { repo.find() ?: throw RuntimeException() suspendCoroutineUninterceptedOrReturn<Any?> { con2 -> val args = pjp.args.sliceArray(0..<pjp.args.size - 1) joinPoint.proceed(args + con2) } }.startCoroutineUninterceptedOrReturn(con) } } ύΫͬͨContinuation্Ͱ DBίʔϧ
@Component @Aspect class CommonProcess( private val repo: Repository, ) {
@Around("execution(* *Query.*(..))") fun check(pjp: ProceedingJoinPoint): Any? { val con = pjp.args.last() as Continuation<Any?> return suspend { repo.find() ?: throw RuntimeException() suspendCoroutineUninterceptedOrReturn<Any?> { con2 -> val args = pjp.args.sliceArray(0..<pjp.args.size - 1) joinPoint.proceed(args + con2) } }.startCoroutineUninterceptedOrReturn(con) } } ͦͷ··JoinPoint࣮ߦ
Spring Framework 6.1͔Β αϙʔτ͞ΕΔ༷😇 https://github.com/spring-projects/spring-framework/issues/22462
ͦͷଞ
• ஈ֊తʹमਖ਼͍͖͍ͯͨ͠ • ݁߹ςετͰɺJDBCͰDBΛݕূ͍ͨ͠ • suspendؔʹͰ͖ͳ͍͕ؔ͋Δ JDBCͱR2DBCͷซ༻
@Configuration class JooqConfiguration( private val connectionFactory: ConnectionFactory) { @Bean fun
dslContext(): DSLContext = DSL.using(connectionFactory).dsl() @Bean("jdbcDSLContext") fun jdbcDslContext( @Value("\${spring.datasource.url}") url: String, @Value("\${spring.datasource.username}") username: String, @Value("\${spring.datasource.password}") password: String, ): DSLContext = DefaultDSLContext( DataSourceBuilder.create().url(url).username(username) .password(password).build(), SQLDialect.MYSQL) } ࣗલͰ༻ҙ͢Δ
@Repository class Repository( private val ctx: DSLContext, @Qualifier("jdbcDSLContext") private val
jdbcCtx: DSLContext, ) { fun findById(id: Long): Record? = ctx… fun blockingFindById(id: Long): Record? = jdbcCtx… } ϦϙδτϦ
ىಈ࣌ʹUnknownHostException NettyͷDNSϦκϧό Caused by: java.net.UnknownHostException: Failed to resolve 'my_db:3306' [A(1)]
after 2 queries at io.netty.resolver.dns.DnsResolveContext.finishResolve (DnsResolveContext.java:1088)
DBͷϗετ໊ʹ”_”ؚ͕·Ε͍͚ͯͳ͍ Docker Compose͍ͬͯΔ߹ҙ NettyͷDNSϦκϧό
• jOOQ৽͍͠όʔδϣϯΛ͏ • CoroutineContextΛ׆༻ͯ͠ τϥϯβΫγϣϯΛཧ͢Δ • AOPͰContinuationΛ͍ճ͢ • JDBCͱซ༻ͯ͠ஈ֊తʹमਖ਼͢Δ ·ͱΊ
• QA • ϦϦʔεޙελοΫ͠ͳ͍͜ͱΛفΔ • ύϑΥʔϚϯεܭଌ͍ͨ͠ ࠓޙ
WE’RE HIRING!! https://hrmos.co/pages/moneyforward/jobs?category=1666322478647443458,1666323214451404802
None