Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
R2DBCを導入しようとした話
Search
Tasuku Nakagawa
September 28, 2023
Programming
490
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
2.1k
withContextってスレッド切り替え以外にも使えるって知ってた?
t45k
3
3.1k
DB呼び出し回数を減らしてコア機能を高速化した話
t45k
0
350
コードレビュー改善のためにJenkinsとIntelliJ IDEAのプラグインを自作してみた話
t45k
0
170
Other Decks in Programming
See All in Programming
Python Lambdaにおけるテストしやすい設計とAI支援の活用 / Designing Testable Python Lambda Functions with AI
kitano_yuichi
1
310
Spec-driven Development with AI
simas
PRO
0
2.3k
Getting Started with Vibe Coding
miccheng
0
190
PyConTW 2026 - AI Coding 不只是工具升級, 區域醫院醫療資訊系統協作開發與資訊團隊轉型實踐
cclai999
0
240
flutter_test のその先へ — Flutter アプリの LLM 機能に「評価」という品質保証を
kosukesaigusa
0
200
AI時代のTerminal環境を UNIX哲学で設計・運用する
yasunori0418
1
350
Lambda durable functionsでHuman in the Loopを楽に…楽に……
har1101
2
300
graphql-ruby を Spinel で速くしようとした話
mtsmfm
0
140
作れるエンジニアから、価値を届けるエンジニアへ
ryunakayama
1
240
Railsバージョンを4倍にした話
optim
0
970
Web Framework Benchmark Battle 2026, Any%
a_matsuda
3
3.2k
Railsのためのパスキー実践入門 〜マルチテナントSaaSでの意思決定〜
utakaha
4
3.6k
Featured
See All Featured
How to train your dragon (web standard)
notwaldorf
96
6.9k
Bootstrapping a Software Product
garrettdimon
PRO
306
120k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.7k
We Have a Design System, Now What?
morganepeng
55
8.4k
The Curse of the Amulet
leimatthew05
3
17k
Digital Projects Gone Horribly Wrong (And the UX Pros Who Still Save the Day) - Dean Schuster
uxyall
1
3.8k
Navigating Team Friction
lara
192
16k
Information Architects: The Missing Link in Design Systems
soysaucechin
1
1.3k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
3.3k
Rebuilding a faster, lazier Slack
samanthasiow
85
9.7k
For a Future-Friendly Web
brad_frost
184
11k
What the history of the web can teach us about the future of AI
inesmontani
PRO
3
800
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