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
Railway Oriented Programming を オニオンアーキテクチャに適用する...
Search
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
YuitoSato
October 25, 2024
Technology
1.7k
5
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Railway Oriented Programming を オニオンアーキテクチャに適用する by kotlin-result / Railway Oriented Programming in Onion Architecture by kotlin-result
YuitoSato
October 25, 2024
More Decks by YuitoSato
See All by YuitoSato
「規約、知識、オペレーション」から考える中規模以上の開発組織のCursorルールの 考え方・育て方 / Cursor Rules for Coding Styles, Domain Knowledges and Operations
yuitosato
9
10k
大AI時代で輝くために今こそドメインにディープダイブしよう / Deep Dive into Domain in AI-Agent-Era
yuitosato
2
5.6k
50人の組織でAIエージェントを使う文化を作るためには / How to Create a Culture of Using AI Agents in a 50-Person Organization
yuitosato
6
9.9k
リファクタリングへの耐性が高いモデルベースの統合テストの紹介 / Model-Base Integration Test for Refactoring
yuitosato
7
4.1k
Expressing Business Logic with Types: Functional DDD for OOP
yuitosato
1
240
ビジネスロジックを「型」で表現するOOPのための関数型DDD / Functional And Type-Safe DDD for OOP
yuitosato
44
33k
Java21とKotlinの代数的データ型 & パターンマッチの紹介と本当に嬉しい使い方 / Algebraic Data Type in Java and Kotlin: Happy Use of Pattern Match
yuitosato
14
5.9k
ログラスの継続的ライブラリアップデートのWhyとHow / Why and How to Update Libraries Continuously in Loglass
yuitosato
0
590
リプレイス「後」が大事!Reactフルリプレイスから2年で良かったこと・その後大事なこと / The Important Point After The Framework Replacement
yuitosato
3
1.2k
Other Decks in Technology
See All in Technology
現場で役立つ技術負債の効果的な返済方法
masuda220
PRO
11
5.1k
登壇の自信を奪う3匹のオバケ / 3 Ghosts That Rob You of Your Confidence in Public Speaking
pauli
9
1.1k
2026-09-18 gotanda.sre Terraformで複数環境作ったり、複数Stateに分割したりそれとTerragrunt / Terraform multi envs and multi states
masasuzu
4
710
おい、エージェントを使って終わらせろ
nwiizo
3
900
積み重なった技術負債への挑戦 〜初手としての全社ゴト化〜
techtekt
PRO
0
1.6k
AWS DevOps Agent スキルをつかいこなそう / Master AWS DevOps Agent Skills
kinunori
1
260
人にやさしく、AIにやさしく、書き手を選ばないIaCのガードレール再考 / Rethinking IaC Guardrails for Humans and AI Alike
kohbis
3
480
AI 時代のスタートアップエコシステ厶から考究する技術的負債との向き合い方
m3m0r7
PRO
3
2.6k
ほんとうの信頼性はヒーローが死んでからはじまる / True reliability begins after the hero dies
vtryo
0
120
AI時代、データエンジニアが一番おもろい
genshun9
0
690
【ゲームメーカーズスクランブル2026】『Shadowverse: Worlds Beyond』UIとアニメーションで実現する最高のユーザー体験を叶えるプロトタイピング
cygames
PRO
0
330
サーバーレスをどこまで使う? WebRTC対戦ゲームで選んだVPSとの共存設計
kaidouji85
0
390
Featured
See All Featured
Creating an realtime collaboration tool: Agile Flush - .NET Oxford
marcduiker
35
2.6k
The Mindset for Success: Future Career Progression
greggifford
PRO
0
510
Building a Modern Day E-commerce SEO Strategy
aleyda
45
9.2k
Ecommerce SEO: The Keys for Success Now & Beyond - #SERPConf2024
aleyda
1
2.2k
Agile Actions for Facilitating Distributed Teams - ADO2019
mkilby
0
280
Navigating Algorithm Shifts & AI Overviews - #SMXNext
aleyda
1
1.6k
Leveraging Curiosity to Care for An Aging Population
cassininazir
1
510
Learning to Love Humans: Emotional Interface Design
aarron
275
41k
Mozcon NYC 2025: Stop Losing SEO Traffic
samtorres
1
560
Un-Boring Meetings
codingconduct
0
430
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
4.9k
Amusing Abliteration
ianozsvald
1
310
Transcript
© 2024 Loglass Inc. 2024.10.25 株式会社ログラス 佐藤有斗 Railway Oriented Programming
を オニオンアーキテクチャに適用する by kotlin-result
© 2024 Loglass Inc. 佐藤 有斗(ゆいと) 株式会社ログラス プロダクト開発部 エンジニア 株式会社ログラスに2020年12月に入社。
主に新規事業の立ち上げを担当。得意分野は型と自動テスト。 KotlinでたまにOSSを作ったり、他のOSSにコントリビュート したりしてます。 Yuito Sato
© 2024 Loglass Inc. 宣伝: n月刊ラムダノートという技術情報誌に記事を寄稿しました! ご興味があればぜひ! n月刊ラムダノート Vol.4, No.3(2024)
https://www.lambdanote.com/product s/n-vol-4-no-3-2024
© 2024 Loglass Inc. ログラスとは ログラスについて 企業価値を向上する 経営管理クラウド
© 2024 Loglass Inc. ログラスについて 企業価値を向上する 経営管理クラウド
© 2024 Loglass Inc. Railway Oriented Programming とは? • 正常ケースと異常ケースの2つのレールを型で表現しながら設計をする手法
◦ いわゆる関数型的なエラーハンドリングをする設計手法 ◦ RustでいうResult。OkとErrの二つの型を持つ ◦ Kotlinではkolin-resultのResult、Arrow.ktのEitherが該当する 引用: Railway Oriented Programming https://fsharpforfunandprofit.com/rop/
© 2024 Loglass Inc. kotlin-result • KotlinでRailway Oriented Programmingができるライブラリ Result<out
V, out E> // 例)文字列からメモというモデルを作成する関数の型 (String) -> Result<Memo, CreateMemoError> 参考: kotlin-result https://github.com/michaelbull/kotlin-result
© 2024 Loglass Inc. 使用例 fun createMemo(text: String): Result<Memo, CreateMemoError>
{ return if (text.length <= 140) { Ok(Memo(text)) } else { Err(CreateMemoError()) } }
© 2024 Loglass Inc. 正常ケース 2つのレール 異常ケース
© 2024 Loglass Inc. String Memo 正常ケース String Memo
© 2024 Loglass Inc. String Memo 異常ケース String CreateMemoError 文字列が140字を
超えてる!
© 2024 Loglass Inc. Railway Oriented Programming の良いとこ • 呼び元でエラーハンドリングを強制できる
◦ 検査例外がないKotlinだと例外ハンドリングを強制できない ◦ 標準のResult型はハンドリングする例外を指定できない • 複雑な処理を制御しやすい ◦ 処理の合成がしやすい(直列と並列の処理の合成)
© 2024 Loglass Inc. Railway Oriented Programming の良いとこ • 呼び元でエラーハンドリングを強制できる
◦ 検査例外がないKotlinだと例外ハンドリングを強制できない ◦ 標準のResult型はハンドリングする例外を指定できない • 複雑な処理を制御しやすい ◦ 処理の合成がしやすい(直列と並列の処理の合成) 時間の都合上 今日はこちらのみ
© 2024 Loglass Inc. Railway Oriented Programming で処理を直列に合成する • ユーザ作成(バリデーション→ユーザを作成→通知メールを送信)
バリデーション ユーザ作成 通知メール送信 Request ValidateRegister UserRequestError Validated Request User CreateUserError Unit SendEmailError
© 2024 Loglass Inc. // バリデーション fun validateRegisterUserRequest( request: RegisterUserRequest,
): Result<ValidatedRegisterUserRequest, ValidateRegisterUserRequestError> { ... } // ユーザー作成 fun createUser( request: ValidatedRegisterUserRequest, ): Result<User, CreateUserError> { ... } // メールを送信 fun sendCreateUserNotificationEmail( createdUser: User, ): Result<Unit, SendCreateUserNotificationEmailError> { ... }
© 2024 Loglass Inc. Railway Oriented Programming で処理を直列に合成する • andThenで処理を合成(binding,
flatMapも同じことが可能) バリデーション ユーザ作成 通知メール送信 Request ValidateRegister UserRequestError Validated Request User CreateUserError Unit SendEmailError andThen andThen
© 2024 Loglass Inc. // ユーザー登録処理。各処理を連結 fun registerUser( request: RegisterUserRequest,
): Result<User, RegisterUserError> { return validateCreateUserRequest(request) // バリデーション .mapError { error -> ... } .andThen { validatedRequest -> createUser(validatedRequest).mapError { error -> ... } // ユーザー作成 } .andThen { createdUser -> sendCreateUserNotificationEmail(createdUser).mapError { error -> ... } // 通知メール送信 } }
© 2024 Loglass Inc. Railway Oriented Programming で処理を並列に合成する • zipで処理を合成
zip CreateUserError UseName CreateUserError String UserName, Email Email CreateUserError String 一つ目のエラーを返す User
© 2024 Loglass Inc. fun validateAndCreate(userName: String, email: String): Result<User,
CreateUserError> { return zip( { UserName.validateAndCreate(userName) }, { Email.validateAndCreate(email) }, ) { validatedUserName, validatedEmail -> User( id = UserId.generate(), userName = validatedUserName, email = validatedEmail, ) } }
© 2024 Loglass Inc. • zipOrAccumulateで処理を合成 zipOrAccumulate List<CreateUserError> UseName CreateUserError
String Email CreateUserError String zipOrAccumulateでエラーをまとめて返す UserName, Email User
© 2024 Loglass Inc. fun validateAndCreate(userName: String, email: String): Result<User,
List<CreateUserError>> { return zipOrAccumulate( { UserName.validateAndCreate(userName) }, { Email.validateAndCreate(email) }, ) { validatedUserName, validatedEmail -> User( id = UserId.generate(), userName = validatedUserName, email = validatedEmail, ) } }
© 2024 Loglass Inc. List<Result<V, E>>を並列に処理する • いわゆる一括処理はcombineで処理を並列に合成できる combine CreateUserError
User CreateUserError Request List<User> User CreateUserError Request User CreateUserError Request User User User 一つ目のエラーを返す
© 2024 Loglass Inc. // ユーザー作成処理を並列に合成 fun createUsers( request: ValidatedRegisterUserRequest,
): Result<List<User>, CreateUserError> { val resultUserList: List<Result<User, CreateUserError>> = requests.map { createUser(it) } val userListResult: Result<List<User>, CreateUserError> = resultUserList.combine() return userListResult }
© 2024 Loglass Inc. エラーをまとめて返すなどもカスタムすればできる • combineOrAccumlateはデフォルトでないので自作が必要 combineOr Accumulate List<CreateUserError>
User CreateUserError Request User CreateUserError Request User CreateUserError Request List<User> User User User CreateUserError CreateUserError 3つの内2つ失敗した
© 2024 Loglass Inc. fun bulkCreateUser( requests: List<ValidatedRegisterUserRequest>, ): Result<List<User>,
List<CreateUserError>> { val resultUserList: List<Result<User, CreateUserError>> = requests.map { createUser(it) } val userListResult: Result<List<User>, List<CreateUserError>> = resultUserList.combineOrAccumulate() return userListResult }
© 2024 Loglass Inc. import com.github.michaelbull.result.partition // partitionメソッドを使って意外と簡単に自作が可能 fun <V,
E> List<Result<V, E>>.combineOrAccumulate(): Result<List<V>, List<E>> { val (values, errors) = this.partition() return if (errors.isEmpty()) { Ok(values) } else { Err(errors) } }
© 2024 Loglass Inc. kotlin-resultの処理の合成まとめ • 処理を「直列」に合成したい(成功したら次へ) ◦ andThen、binding、flatMapを使う •
処理を「並列」に合成したい(お互いの結果に依存しない) ◦ zip, combineを使う • 処理を「並列」に合成したいかつエラーをまとめたい ◦ zipOrAccumulateを使う。(combineOrAccumulateは拡張が必要)
© 2024 Loglass Inc. ちなみに初コントリビュートはzipOrAccumulateです
© 2024 Loglass Inc. Railway Oriented Programming & オニオンアーキテクチャ -
どの層でResultを使うの? Domain Application Presentation Infrastructure DB
© 2024 Loglass Inc. Railway Oriented Programming & オニオンアーキテクチャ -
どの層でResultを使うの? Domain Presentation Infrastructure DB Application
© 2024 Loglass Inc. Railway Oriented Programming & オニオンアーキテクチャ -
どの層でResultを使うの?→Application層とDomain層で使う Domain Presentation Infrastructure Application 異常値は 基本例外を投げて ハンドリング しない App層からの エラーを 例外に変換 メソッドは基本 Resultを返す Domain、 Repositoryの 処理を呼び出し、 処理を合成
© 2024 Loglass Inc. Domain層 - 基本Resultを返す class User(...) {
companion object { fun validateAndCreate( userName: String, email: String, ): Result<User, ValidateAndCreateUserError> {...} } }
© 2024 Loglass Inc. Application層 - Resultを返す処理を結合 class RegisterUserUseCase( private
val userRepository: UserRepository, private val createUserNotificationEmailSender: CreateUserNotificationEmailSender, ) { fun execute( param: RegisterUserDto, ): Result<Unit, RegisterUserUseCaseError> { return User.validateAndCreate(param.userName, param.email) .mapError { error -> ValidateAndCreateUserUseCaseError(error) }.andThen { createdUser -> userRepository.insert(createdUser) createUserNotificationEmailSender .send(createdUser) .mapError { error -> SendCreateUserNotificationEmailUseCaseError(error) } } } }
© 2024 Loglass Inc. Application層 - Resultを返す処理を結合 Domain層処理① Domain層処理② 保存処理
エラー値 成功値 成功値 エラー値 Unit andThen andThen データ取得 データ リクエスト
© 2024 Loglass Inc. Presentation層 - Resultのエラーを 例外に変換 class UserController(
private val registerUserUseCase: RegisterUserUseCase, ) { fun registerUser( request: RegisterUserRequest ): String { val dto = request.toDto() val result = registerUserUseCase.execute(dto) return result .map { "OK" } .getOrThrow { error -> error.toException() } } } fun RegisterUserUseCaseError.toException(): BadRequestException { return when (this) { is RegisterUserUseCaseError.ValidateAndCreateUserUseCaseError -> … is RegisterUserUseCaseError.SendCreateUserNotificationEmailUseCaseError -> … // ... } }
© 2024 Loglass Inc. Infrastructure層 - 基本Resultを返さない - 基本自力復帰できないエラーが多いため -
復帰できるエラーがある場合は返す - 例)メールの送信エラーなど // Domain層 interface UserRepository { fun insert(user: User) } // Infrastructure層(Domain層のInterfaceを実装) class UserJDBCRepository(): UserRepository { override fun insert(user: User) { // ユーザー登録処理 … } }
© 2024 Loglass Inc. 補足)ユーザーが自力復帰できないエラーはResultで管理しない - 500になるようなエラーは基本ハンドリングしない Domain層処理① Domain層処理② 保存処理
エラー値 成功値 成功値 エラー値 Unit データ取得 データ リクエスト 500エラー (データ不整合) 500エラー (DB落ちてた)
© 2024 Loglass Inc. まとめ: Railway Oriented Programming & オニオンアーキテクチャ
- どの層でResultを使うの?→Application層とDomain層で使う Domain Presentation Infrastructure Application 異常値は 基本例外を投げて ハンドリング しない App層からの エラーを 例外に変換 メソッドは基本 Resultを返す Domain、 Repositoryの 処理を呼び出し、 処理を合成
© 2024 Loglass Inc. しかし一つ問題があります
© 2024 Loglass Inc. エラー時に自動でロールバックされない問題 - Application層がResultを返すと、例外を投げないためRDBが自動でロールバック しない - そのため現状ログラスではApp層でエラー→例外への変換をしている
- しかし処理を統合する責務を持つApp層がResultを返せないと価値が十分に引き出せない
© 2024 Loglass Inc. エラー時に自動でロールバックされない問題 class RegisterUserUseCase( private val userRepository:
UserRepository, private val createUserNotificationEmailSender: CreateUserNotificationEmailSender, ) { // SpringBootのアノテーションで例外投げられたら自動ロールバック @Transactional fun execute( param: RegisterUserDto, ): Unit { … } }
© 2024 Loglass Inc. 実験: カスタムアノテーション @Retention(AnnotationRetention.RUNTIME) @Transactional(propagation = Propagation.REQUIRED)
annotation class ResultTransactional @Component @Aspect class ResultTransactionalAspect { @Around("@annotation(ResultTransactional)") fun handleResultTransaction(joinPoint: ProceedingJoinPoint): Any? { val proceeded = joinPoint.proceed() val isErr = proceeded.javaClass.name == "com.github.michaelbull.result.Failure" if (isErr) { TransactionAspectSupport.currentTransactionStatus().setRollbackOnly() } return proceeded } } - @ResultTransactional というアノテーションを自作 ⚠ 動くことは確認しましたが、 ログラスではまだ実運用していません
© 2024 Loglass Inc. 使ってみる class RegisterUserUseCase( private val userRepository:
UserRepository, private val createUserNotificationEmailSender: CreateUserNotificationEmailSender, ) { // 返り値がErrなら自動でロールバック @ResultTransactional fun execute( param: RegisterUserDto, ): Unit { … } }
© 2024 Loglass Inc. なんかできそう!(続報をお待ちください)
© 2024 Loglass Inc. 今日のコード https://github.com/YuitoSato/kotlin-spring-boot-jooq
© 2024 Loglass Inc.