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
YuitoSato
October 25, 2024
Technology
1.8k
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
11k
大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
10k
リファクタリングへの耐性が高いモデルベースの統合テストの紹介 / Model-Base Integration Test for Refactoring
yuitosato
7
4.2k
Expressing Business Logic with Types: Functional DDD for OOP
yuitosato
1
250
ビジネスロジックを「型」で表現する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
6k
ログラスの継続的ライブラリアップデートの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
Essential architectural thinking (revised edition)
ufried
0
120
自分で立ててみるLLMサービス
y_sera15
0
160
AIは「聞けば答える」から「任せると自分で進める」へ - JACTIMペナン地域部会・ペナン三水会講演
knishioka
0
110
AI 時代の Azure エンジニアリング ~ 私たちは何を磨き、何を任せるのか ~
chack411
1
610
"優秀な誰か"の下位互換にならないために
yut0naga1_fa
1
1.8k
【Data Engineering Summit2026_登壇資料】医療ビッグデータ×AIエージェントを支えるデータ基盤とハーネスエンジニアリング
jmdc_presentation
0
140
子育てエンジニアの可処分時間が減っても成長を諦めない戦い方
sudoakiy
6
4.5k
Post AI時代のリファクタリング実践記 / Refactoring in the post AI era
kaminashi
1
190
大型犬好きによるPugの紹介
yamanoku
1
150
初対面のメンバーがチームを組み、Claude Codeでゼロからサービスをリリース
mkdev10
1
130
組み立てて楽しむ AWS Blocks 入門
kmiya84377
0
230
全PRの83%が AIレビューだけでマージできるようになった開発組織は その後どうなったか @2026/10
athug
0
140
Featured
See All Featured
Designing Experiences People Love
moore
143
24k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
33
5.2k
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
1
430
Jess Joyce - The Pitfalls of Following Frameworks
techseoconnect
PRO
1
440
Optimizing for Happiness
mojombo
378
71k
Building a Scalable Design System with Sketch
lauravandoore
464
34k
Have SEOs Ruined the Internet? - User Awareness of SEO in 2025
akashhashmi
0
520
Agile that works and the tools we love
rasmusluckow
331
22k
A designer walks into a library…
pauljervisheath
211
25k
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.7k
技術選定の審美眼(2025年版) / Understanding the Spiral of Technologies 2025 edition
twada
PRO
120
120k
Leo the Paperboy
mayatellez
10
2.4k
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.