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

R2DBCを導入しようとした話

 R2DBCを導入しようとした話

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)