Upgrade to Pro — share decks privately, control downloads, hide ads and more …

R2DBCを導入しようとした話

Sponsored · Your Podcast. Everywhere. Effortlessly. Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.

 R2DBCを導入しようとした話

Avatar for Tasuku Nakagawa

Tasuku Nakagawa

September 28, 2023
Tweet

More Decks by Tasuku Nakagawa

Other Decks in Programming

Transcript

  1. Task / Tasuku Nakagawa 2023೥7݄͔Βݱ৬ େࡕڌ఺ۈ຿ GitHub: T45K X (چTwitter):

    getupmax ࢿྉ: https://speakerdeck.com/t45k/r2dbcwodao-ru-suruhua
  2. @Repository class Repository(private val ctx: DSLContext) { fun findById(id: Long):

    Record? = ctx.selectFrom(TABLE) .where(ID.eq(id)) .fetchOne() } DSLͰDB໰͍߹Θ͕ͤͰ͖ΔORM jOOQͱ͸
  3. @Configuration class JooqConfiguration( private val connectionFactory: ConnectionFactory) { @Bean fun

    dslContext(): DSLContext = DSL.using(connectionFactory).dsl() } jOOQ Config
  4. @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ʹ ม׵Մೳ
  5. 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) }
  6. class UseCase( private val tx: TransactionalOperator, private val repo: Repository,

    ) { suspend fun call() = tx.execute(mono { repo.save() }) } Spring Frameworkۘ੡ Ҿ਺͕Mono TransactionOperator
  7. class UseCase( private val ctx: DSLContext, private val repo: Repository,

    ) { suspend fun call() = ctx.transactionCoroutine { repo.save() } } jOOQۘ੡ DSLContext͔Βੜ͍͑ͯΔ transactionCoroutine
  8. class UseCase( private val ctx: DSLContext, private val repo: Repository,

    ) { suspend fun call() = ctx.transactionCoroutine { repo.save() } } jOOQۘ੡ DSLContext͔Βੜ͍͑ͯΔ transactionCoroutine
  9. class UseCase( private val ctx: DSLContext, private val repo: Repository,

    ) { suspend fun call() = ctx.transactionCoroutine { repo.save() } } jOOQۘ੡ DSLContext͔Βੜ͍͑ͯΔ transactionCoroutine ͜Ε͚ͩͰ͸ޮ͔ͳ͍
  10. class UseCase( private val ctx: DSLContext, private val repo: Repository,

    ) { suspend fun call() = ctx.transactionCoroutine { config -> repo.save(config.dsl()) } } ίʔϧόοΫ಺ͰDSLContextΛऔಘ ͦΕʹରͯ͠ΫΤϦΛ࣮ߦ͠ͳ͍ͱ͍͚ͳ͍ transactionCoroutineͷ᠘
  11. όέπϦϨʔʁ 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) } }
  12. όέπϦϨʔʁ 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) } } όέπϦϨʔ όέπϦϨʔ
  13. class TransactionElement(val ctx: DSLContext) : AbstractCoroutineContextElement(TransactionElement) { companion object Key

    : CoroutineContext.Key<TransactionElement> } DSLContextΛอ࣋͢Δ CoroutineContext
  14. @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Λ߹੒
  15. @Repository class Repository(private val ctx: DSLContext) { suspend fun getCtx()

    = coroutineContext[TransactionElement.Key]?.ctx ?: this.ctx suspend fun save() { getCtx()... // CRUDॲཧ } } RepoͰ࢖͏ctxΛ੾Γସ͑
  16. AOP

  17. @Component @Aspect class CommonProcess( private val repo: Repository, ) {

    @Before("execution(* *Query.*(..))") fun check(joinPoint: JoinPoint): Any? { repo.find() ?: throw RuntimeException() } } AOPͰڞ௨ॲཧΛ͍ͯ͠Δ
  18. @Component @Aspect class CommonProcess( private val repo: Repository, ) {

    @Before("execution(* *Query.*(..))") fun check(joinPoint: JoinPoint): Any? { repo.find() ?: throw RuntimeException() } } AOPͰڞ௨ॲཧΛ͍ͯ͠Δ ϦϙδτϦͷϝιου͕ suspendؔ਺ʹͳͬͨ
  19. @Component @Aspect class CommonProcess( private val repo: Repository, ) {

    @Before("execution(* *Query.*(..))") fun check(joinPoint: JoinPoint): Any? { runBlocking { repo.find() } ?: throw RuntimeException() } } runBlocking͢Δʁ
  20. @Component @Aspect class CommonProcess( private val repo: Repository, ) {

    @Before("execution(* *Query.*(..))") fun check(joinPoint: JoinPoint): Any? { runBlocking { repo.find() } ?: throw RuntimeException() } } runBlocking͢Δʁ ελοΫ͢Δ ʢݪҼෆ໌ʣ
  21. suspend fun hoge(value: String){ } public static final Object hoge(

    String value, Continuation $completion) { return Unit.INSTANCE; } suspendؔ਺͸ Java͔Β͜͏ݟ͑Δ
  22. suspend fun hoge(value: String){ } public static final Object hoge(

    String value, Continuation $completion) { return Unit.INSTANCE; } suspendؔ਺͸ Java͔Β͜͏ݟ͑Δ Continuation͕ Coroutineͷ͋Ε͜ΕΛ࣋ͭ
  23. @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) } }
  24. @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ΛύΫΔ
  25. @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ίʔϧ
  26. @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࣮ߦ
  27. @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) } ࣗલͰ༻ҙ͢Δ
  28. @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… } ϦϙδτϦ
  29. ىಈ࣌ʹ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)