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

共通基盤の実装に Kotlin - Quarkus を選定した理由と採用したアーキテクチャ

Sponsored · SiteGround - Reliable hosting with speed, security, and support you can count on. →
Nealle
November 14, 2026

共通基盤の実装に Kotlin - Quarkus を選定した理由と採用したアーキテクチャ

Kotlin Fest 2026登壇資料
https://2026.kotlinfest.dev/sessions/session-13

長らくPythonでサービスを提供してきた弊社ですが、この度共通基盤を実装するにあたってKotlinとQuarkusを採用しました。
以下について、理由と振り返りを交えながら紹介したいと思います。
• 数ある言語の中からどうしてKotlinを選んだのか
• 数あるフレームワークの中からどうしてQuarkusを選んだのか
• そしてどんなソフトウェア・アプリケーションアーキテクチャで実装したのか

Nealle

November 14, 2026

Transcript

59 slides
  1. はじめに ⾃⼰紹介 株式会社Nealle アーキテクチャグループ GM 野呂 有我 • ⽇々技術選定、設計、⼤きめの実装などを⾏っている •

    Nealleには副業で2年くらいお世話になっていて、そこからJoin • 今年で4年⽬、副業を合わせると6年⽬になります 02
  2. はじめに サービス紹介 PRODUCT TODAY'S TOPIC Park Direct 共通基盤 ⽉極駐⾞場の検索から 複数のプロダクトから

    契約・管理までを、オンラインで 使われる基盤を、 完結できるサービス 新しいサービスとして開発中 05
  3. ⽬次 01 ⾔語選定の背景 02 PoC と Kotlin の思想 03 JVM

    の弱点と Quarkus 04 アーキテクチャ 05 本番稼働から約半年 09
  4. ⾔語選定の背景 以前からの社内の様⼦ 現状 開発⾔語 これまでのメインは Python ⽣い⽴ち オフショア開発で誕⽣ コアドメイン周りが 結構カオスに

    新しい部分は オニオンアーキテクチャ +丁寧な型ヒント そうでない所も多く、 影響範囲の特定が しんどい 12
  5. ⾔語選定の背景 ⾔語に求めたもの 01 02 堅牢さ ⾼い⽣産性 03 04 IDE の

    references を信じたい AI にガードレールを敷きたい 14
  6. ⾔語選定の背景 候補の絞り込み TypeScript スキーマの管理がしんどくなりそう → BFF の⾔語として採⽤。BE の候補からは外す Go Rust

    学習曲線が極端 → 候補から外す Java Kotlin 残ったのは Java / Kotlin / Go。この 3 つで PoC を⾏った 17
  7. PoC と Kotlin の思想 3 ⾔語で PoC を作って、実際に⽐較 Java Kotlin

    Go 事前の本命 ⽐較の基準 シンプルさの代表 題材はとてもシンプルな会員基盤 19
  8. PoC と Kotlin の思想 Kotlin と⽐べて、Java は しんどかった点 • 何と⾔っても、Lombok

    的なものを LLM が⾃然には読んでくれない • Null の排除が難しい 良さそうだった点 • JVM ネイティブな⾔語で、 アップデートの追従に安⼼感がある • どんどん新しい機能が 導⼊されている 加えてKotlin は、リフレクション的なものをほぼ排除できる。 ここに⼤きめの旨みを感じた 20
  9. PoC と Kotlin の思想 LLM には「書かれていないコード」が⾒えない Java + Lombok Kotlin

    @Value data class Member( @Builder val id: MemberId, public class Member { val email: Email?, String id; ) String email; // LSPから⾒えるものは全て⾒える。 } // Null も型に出る // getEmail() も builder() も // ソース上には存在しない 専⽤の MCP もあるが、スピードでもトークンでもメリットは薄い。※ コードはイメージ 21
  10. PoC と Kotlin の思想 Kotlin と⽐べて、Go は しんどかった点 型の弱さ LLM

    が型の弱い書き⽅を覚えていて、 何度⾔っても出してくる 良さそうだった点 • 予期していた 「ライブラリの少なさ」は、 そこまで感じなかった • PoC の完成は流⽯に早かった func Handle(p interface {}) 22
  11. PoC と Kotlin の思想 PoC の⽐較まとめ 観点 Java Go Kotlin

    LLM との相性 Lombok を読まない interface {} を出す 書いたものが全て 型・Null Null の排除が難しい 型が弱い Null が型に出る アップデート追従が その他 安⼼ PoC が早い リフレクションを ほぼ排除 堅牢さ・⽣産性・AI のガードレールに、⼀番近いのは Kotlin だった 23
  12. PoC と Kotlin の思想 関数型の特徴を備えるのに、Monad抽象 は作らせない できること immutable 関数ファースト 末尾再帰最適化

    型クラスサポート それでも Monad抽象 だけは作らせないし、 優遇しない 聞いたわけではないけど、 そこにかなり強い こだわりを持っているように 感じられる Tagless Final 的なもの 25
  13. PoC と Kotlin の思想 Scala への、JetBrains の答え(なのかな?) Scala は しかし

    結果 Monad を優遇した 1 つ Monad抽象 を 導⼊すると、 全てを Monad に しないと、かえって 不便になる 弊社で導⼊するには ハードルの⾼い⾔語 になってしまった for式の糖⾐構⽂ ⾼カインドパラメータ… Scala は好きだけど、今の事業で使うかと⾔われたら選択しない 26
  14. JVM の弱点と Quarkus JVM の弱点は、起動の遅さ JVM の性質 起動が遅く、コールドスタートが 重い 共通基盤の前提

    サーバレスと 起動の遅さは、相性が悪い 元々、全体をサーバレスな構成に する予定だった 28
  15. JVM の弱点と Quarkus 以前から Quarkus の PoC をしているメンバーが居た Quarkus:Red Hat

    が中⼼となって開発されているフレームワーク 01 02 03 コールドスタートが 速い フットプリントが ⼩さい ネイティブ コンパイルも可能 ネイティブにしなくても、⾼速に起動する 29
  16. JVM の弱点と Quarkus Kotlin Native / Quarkus Native は検討した? 01

    02 03 使わなくても、 それなりの 速度が出た そもそも Lambda で 使わなくなった Lambda で 使う場合も、 SnapStart が 使える ネイティブコンパイルは、使わない選択に 30
  17. PoC と Kotlin の思想 Quarkus は、リフレクションと相性が悪い Quarkus の作り • 起動時の処理を、

    ビルド時のコード⽣成に寄せている • 実⾏時はふつうの メソッド呼び出しだけで動く リフレクションを使うと • native image では、事前に 登録しないと実⾏時に壊れる • 登録しても、バイナリサイズと メモリ使⽤量が増える リフレクションを使わずに書くほど、 Quarkus の速さを活かせる → ここでもやはり Kotlinに分がある そしてリフレクションがないコードは⼈にもLLMにも優しい 32
  18. アーキテクチャ 前提:Python では Onion アーキテクチャを推奨 前提 Python では Onion アーキテクチャを推奨

    だから Kotlin でも、 Clean 系の アーキテクチャを使いたい 参考: Python で Onion アーキテクチャを選定した理由と、奮闘している様⼦は https://speakerdeck.com/nealle/drfwoshao-sizutu-onionakitekutiyaniji-seteiku-djangocongress-jp-2025 34
  19. アーキテクチャ そして、リアクティブのジレンマ 恩恵を受けたい ⼀⽅で 慣れていないと、ロジックを ⼀切書けない Quarkus は基本的に、 リアクティブを持ち味とした フレームワーク

    (Vert.x + Mutinyを使⽤) どこまでをリアクティブにするか、 都度判断するのがしんどい リアクティブチェーンを 気にし始めると、レビューも⾟い 35
  20. アーキテクチャ ドメイン層は同期、Usecase 層と Infra 層はリアクティブ View Reactive REST の⼊⼝ Usecase

    Reactive Uni / Multi でフローを組む Domain Sync 素の Kotlin で同期的に書く Infrastructure Reactive DB・外部 API を⾮同期で どこをリアクティブにするか、判断が要らない 36
  21. アーキテクチャ 再掲 View Reactive REST の⼊⼝ Usecase Reactive Uni /

    Multi でフローを組む Domain Sync 素の Kotlin で同期的に書く Infrastructure Reactive DB・外部 API を⾮同期で リアクティブの恩恵を受けつつ、トレードオフを限りなく減らすことに成功 🎉 42
  22. アーキテクチャ 同期/⾮同期の層境界 ルール 検出内容 ねらい ForbiddenUniInDomainModel domain.model 配下の関数が戻り値の型として Uni<T> を明⽰している

    I/O の関⼼事をドメインモデルから外し、純粋な同期コードに 保つ。同期メソッドなら単純な assertion でテストできる RequireUniReturnType OnRepository Repository<T, ID> を継承したインターフェースの メソッドが Uni<...> を返していない(戻り値の省 略=Unit も検出) Uni で不要に包んだり外したりする処理や、ブロッキング呼び 出しが⼊り込むのを防ぎ、リアクティブなチェーンを⼀貫させ る RequireUniReturnType OnApplicationService application.service の execute、 application.query の query が Uni<...> を返して いない 基底インターフェース(CommandService / QueryService) の継承を忘れた場合でも、ユースケースの⼊⼝を Uni に強制す る。Repository 側のルールと対になる ForbiddenTransactional Annotation @Transactional(jakarta / spring)の使⽤。テ ストにも適⽤ ブロッキング⽤のトランザクション境界は Mutiny と互換性が ない。リアクティブ⽤の @WithTransaction に統⼀し、 ApplicationService 層に付ける ドメインモデルは同期的に書き、I/O を伴う層は Mutiny の Uni で書く、 という境界を両側から縛る 45
  23. アーキテクチャ ドメインモデルを外部から切り離す ルール 検出内容 ねらい ForbiddenJavaTimeInDomain domain.model 配下で LocalDate /

    Instant / LocalDateTime / OffsetDateTime / ZonedDateTime を直接参照している(import・完全 修飾名のどちらも検出。BusinessDate / BusinessDateTime ⾃⾝は除外) ドメインの独⽴性を保ち、LocalDate.now() などの直接呼び出し をなくしてテストしやすくする。BusinessDate という型名で業務 上の意味を明⽰する ForbiddenProvider InDomainModel domain.model 配下のクラスが、 BusinessDateTimeProvider をコンストラクタ引数や プロパティとして持っている 現在時刻の取得は技術的な関⼼事として呼び出し元に任せる。モデ ルは時刻の値を引数で受け取り、純粋な関数としてテストできるよ うにする ForbiddenTryCatchInDomain domain 配下での try / catch(try / finally も検 出) 失敗は Result<T> で表す。検証は例外を投げる API ではなく、検 査⽤の API で先に判定する。業務例外はドメインで握りつぶさず、 ExceptionMapper で集約して処理する ForbiddenJpaEntity OutsidePersistenceLayer JPA の @Entity が infrastructure.persistence.entity 以外で使われてい る ORM の技術的な成果物をほかの層に漏らさない。ドメインモデル との変換は Mapper が受け持つ RequireRecordSuffix ForJpaEntity @Entity を付けたクラスの名前が Record で終わって いない Customer(集約)と CustomerRecord(JPA Entity)を名前で 区別できるようにする ドメインモデルが時刻やインフラの都合に直接触れないようにする 46
  24. アーキテクチャ ドメインモデルの不変性(Immutable) ルール 検出内容 ねらい ForbiddenVarInDomain domain 配下での var 宣⾔(プロパティ・コンスト

    ラクタ引数・ローカル変数) 状態の変更は Wither(copy() に @ConsistentCopyVisibility を併⽤)で表し、別の状態への 遷移は遷移先クラスの from で表す ForbiddenMutable CollectionFactory mutableListOf / mutableMapOf / mutableSetOf の使⽤(domain に限らない。 JPA の Record は除外) map / filter / fold などの関数型パイプラインを使う。要素を 積み上げる⽤途には buildList などを使う 47
  25. アーキテクチャ 常に正しい状態(Always Valid)の保証 ルール 検出内容 ねらい ForbiddenInternalConstructor InDomainModel domain.model 配下クラスの

    internal constructor コンストラクタは private にし、⽣成を companion のファクト リに限定して検証を迂回させない ForbiddenInitInValueObject ValueObject<T> を実装したクラスの init { } ブロッ ク 検証を fromInput() の Policy に集約する。複数の違反をまとめて Result で返せるようにし、ofPersisted と fromInput の役割分担 を崩さない RequireValidation InValueObject VO の fromInput に、検証の呼び出し(Policy / Result.zip / 内部 VO の fromInput への委譲など) が含まれていない 検証がひとつもない VO は、VO にするのが早すぎるというシグナ ルとして扱う RequireValueClass ForEntityId EntityId<T> の実装が value class になっていない ボックス化のコストをなくしつつ、Customer.Id と Car.Id の取り 違えをコンパイルエラーにする RequirePrivateConstructor ForEntityId EntityId<T> 実装のプライマリコンストラクタが private でない(public や省略も検出) UUID 形式の検証を迂回した ID の⽣成を防ぐ。internal だけを禁 ⽌する上のルールより厳しい ForbiddenNotNullAssertion InDomain domain 配下での !! null の可能性を型で表し、Result.zip や contracts ベースのヘル パー(allSpecified など)で安全に値を取り出す 不正な状態のインスタンスが⽣成される経路を塞ぐ 48
  26. アーキテクチャ 横断的関⼼事の配置 ルール 検出内容 ねらい ForbiddenPrintln OutsideLogging ロギング⽤パッケージ(customer では com.nealle.customer.lib.logging)以外での

    println / print Logger を経由した構造化ログに⼀本化する。差し替えやす く、将来ロギング基盤を置き換えるときの影響も logging パッ ケージの中に閉じる 49
  27. アーキテクチャ ニーリーにとって、ほぼ初の共通基盤(会員基盤) List・Filter 系のクエリ • ⾃サービスの情報と トランザクション境界 • 更新が複数の DB

    にまたがる JOIN してフィルタしたい • → Kinesis で共通基盤の State を 各サービスへレプリケーション • → 結果整合性を受け⼊れ、監視 と補償トランザクションで対処 quarkus-amazon-kinesisを使⽤ DB が分かれて困る 2 点に State Sourcing と結果整合性で対処 52
  28. アーキテクチャ どうして State Sourcing を選んだのか よくある選択 • この⼿の構成では、 会員基盤の実際 •

    各サービスは、会員基盤で Event Sourcing が選ばれる 起きたイベントそのものには ことが多い印象だが… それほど興味がない • 履歴が追えれば⼗分だった 逆に、サービス側で起きたイベントを購読したい需要は⽇々⾼まっているため、 そちらでは Event Sourcing を検討中 54
  29. 本番稼働から約半年 そして今 共通基盤の第 1 弾 共通基盤の第 2 弾 さらに 会員基盤

    決済基盤 別プロダクト 本番で約半年 作成中 メインのバックエンド ⾔語として採⽤ 58

More Decks by Nealle

Other Decks in Technology