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

サマーインターンシップ2019で学生とDDDなScala開発に取り組んだ / Working ...

サマーインターンシップ2019で学生とDDDなScala開発に取り組んだ / Working on DDD and Scala development with students at Summer Internship 2019

yoshiyoshifujii

September 16, 2019
Tweet

More Decks by yoshiyoshifujii

Other Decks in Programming

Transcript

  1. タイトルですが、正確には… Chatwork では、サマーインターンシップを 今年 (2019 年 ) 、初めて開催しまして、その中 で、学生の皆さんと、とある Web

    アプリケー ションを、ドメイン駆動設計 して Scala で 開発することに取り組みました。 になります。 サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 2 / 50
  2. その過程で改めて ドメイン駆動設計 と Scala を Unlearn ( 学びほぐし ) する機会となりました

    そのあたりをご紹介できればと思います サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 3 / 50
  3. Yoshitaka Fujii @yoshiyoshifujii Chatwork 株式会社(13 ヶ月目) Scala 関西 Summit スタッフ(4

    年目) 登壇 ScalaMatsuri2016 Scala でドメイン駆動設計に真正面から取り組んだ話 ScalaMatsuri2017 Serverless Architecture をScala で構築するぞ ScalaMatsuri2019 実践 Clean Architecture ⛰ 自己紹介 サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 5 / 50
  4. どんなことしたの ? 講義パート : 1 週間 業務パート : 2 週間

    サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 8 / 50
  5. 講義パートの概要 + 観点 ソフトウェア開発の全体像 ←Chatwork の開発の全体の流れ 要件定義 ← RDRA をベースとした、かとじゅんによる講義、みっちり2

    時間。贅沢。 設計 ← DDD をベースとした、かとじゅんによる講義、みっちり4 時間。贅沢。 開発・テスト ← 後程、詳細に 配置 ← Chatwork のCI/CD を中心に 運用 ← Chatwork の運用を中心に一般的な座学 インフラ ← Chatwork で使うインフラを中心に紹介 チーム開発 ← Scrum/ カンバン/ モブの座学とワークショップ サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 11 / 50
  6. 業務パート とあるWeb アプリケーションの開発業務 Scrum / カンバン / モブワーク などを取り入れたモダンなチーム開発を実践する Scrum

    チームの一員となる Dev チーム ( インターン生) Product Owner ( 社員) Scrum Master ( 社員) ステークホルダーに、Product Manager ( 社員) PM が作成した PRD (Product Requirements Document) の簡易版をインプットする Scrum チームで、要件定義、設計、開発、テスト、配置 を目指す サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 12 / 50
  7. Scala の教育 ­ 前提 インターン生は、Scala の経験が、 無い 関数型経験者はいる (OCaml 、Rust

    、Elm など) サーバサイドに興味がある チーム開発をしてみたい Scala を業務でどう取り組んでいるのか知りたい これを機会にScala はじめたいという方々… サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 14 / 50
  8. インターンが始まるまでに1 ヶ月弱 何か、事前に学んできていただく必要がある 実践Scala 入門をプレゼント 可能な限り読んできてね!分からないところあったら聞いてね! Using Akka HTTP https://doc.akka.io/docs/akka­

    http/10.1.9/introduction.html#using­akka­http 分からないところあったら聞いてね! Scala の教育 ­ 事前 サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 15 / 50
  9. インターンが始まるまでに1 ヶ月弱 何か、事前に学んできていただく必要がある 実践Scala 入門をプレゼント 可能な限り読んできてね!分からないところあったら聞いてね! Using Akka HTTP https://doc.akka.io/docs/akka­

    http/10.1.9/introduction.html#using­akka­http 分からないところあったら聞いてね! ノーリアクションで… 不安… Scala の教育 ­ 事前 サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 16 / 50
  10. Scala の教育 ­ 講義 目的 実践Scala 入門 と 業務パートで必要となる知識の間を埋める 業務パートで使う技術スタックを説明

    実際に開発してもらうプロジェクトのScala コードを解説 ハンズオン形式で実際にScala のコードを書いてもらう サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 17 / 50
  11. Scala の教育 ­ 講義 Scala による開発 実践Scala 入門は、あくまでScala という言語全般についての幅広い知識を得るもの 実際の現場では、より実践的であることが求められる

    コードの読みやすさ コードの保守性 非機能要件とのトレードオフ Scala は色々な書き方ができてしまうので、機能要件と非機能要件を満たす範囲で、出来るだけ読み やすく( 理解しやすく) 、保守性の高いコードにする サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 18 / 50
  12. Scala の教育 ­ 講義 Scala を実践的に使う チーム開発を意識したコード 型を意識したコード 既存のライブラリを上手に利用する case

    class を上手に利用する 汎用的な型を特化した型にするため、型に対するパターンマッチ タプルを上手に利用する 値の変換の過程で、可読性を損なわない範囲で trait を上手に利用する インターフェースの観点、再利用の観点、責務の分離の観点 サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 19 / 50
  13. ドメインモデリング ユースケースを定義し、内容に齟齬が無いか、PM と議論し、合意形成 ユースケースの内容を議論するなかで、ユビキタス言語の正しい表現に着目 ドメインモデルに採用するユビキタス言語をPM と合意 ユビキタス言語をドメインモデルとして定義するにあたり既存のドメインモデルとの関連を検討 検討したドメインモデルの関連について社員がレビュー ドメインモデルの関連にいたった根拠やユースケースをP­R に記載

    そこから歴戦の猛者である先輩社員たちによる思考のトレースと可能性の模索 Dev チーム( インターン生) は、その考察から自分たちで落とし所を探しドメインモデルを確定 ドメインモデルをコードに落としてレビュー サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 22 / 50
  14. ユースケースを見てみる 利用者はサインインしたUserAccount を使ってMessage をBookmark のリストに1 件追加する 利用者はサインインしたUserAccount を使ってBookmark のリストを表示する 利用者はサインインしたUserAccount

    を使ってBookmark のリストの表示順を変更できる 利用者は、Bookmark のリストを表示したとき、サインインしたUserAccount を使ってMessage を Bookmark のリストから削除する サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 27 / 50
  15. ユースケースを見てみる 利用者はサインインしたUserAccount を使ってMessage を Bookmark のリスト に1 件追加する 利用者はサインインしたUserAccount を使って

    Bookmark のリスト を表示する 利用者はサインインしたUserAccount を使って Bookmark のリスト の表示順を変更できる 利用者は、 Bookmark のリスト を表示したとき、サインインしたUserAccount を使ってMessage を Bookmark のリスト から削除する サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 28 / 50
  16. lazy val bookmark = Bookmark( id = bookmarkId, status =

    BookmarkStatus.Active, messageId = command.messageId, userAccountId = command.userAccountId, createdAt = now ) val result = for { message <- messageRepository.findById(command.messageId).recoverWith { case e: AggregateNotFoundException => Future.failed(new MessageNotFoundException(e)) } thread <- threadRepository.findById(message.breachEncapsulationOfThreadId).recoverWith { case e: AggregateNotFoundException => Future.failed(new ThreadNotFoundException(e)) } _ <- Future { if (!thread.hasMember(command.userAccountId)) throw new AddBookmarkForbiddenException("You are not a m } _ <- bookmarkRepository.store(bookmark) } yield { AddBookmarkSuccess(bookmarkId) } サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 40 / 50
  17. Future 関連 ­ 指摘事項 Future.apply は並行処理としてスケジューリングされてしまいますが、ここは大した計算処理ではな いのでスレッド実行せずに同期処理でよい Future#successful, failed でよい

    Future の中で明示的に例外をthrow しないようにしましょう。Future.failed を使う if 式は必ずelse 句書く。if は式なので必ず値を返す必要がある。命令型プログラミングとしてelse 句を 書かない場合もあるが、本来の使い方としては値を返すべき _ <- Future { if (!thread.hasMember(command.userAccountId)) throw new AddBookmarkForbiddenException("You are not a m } サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 41 / 50
  18. Future 関連 ­ コードで提案 before after _ <- if (!thread.hasMember(command.userAccountId))

    Future.failed(new AddBookmarkForbiddenException("You are not a member!")) else Future.succcessful(()) _ <- Future { if (!thread.hasMember(command.userAccountId)) throw new AddBookmarkForbiddenException("You are not a m } サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 42 / 50
  19. 正常系をベースに書く _ <- if (thread.hasMember(command.userAccountId)) Future.succcessful(()) else Future.failed(new AddBookmarkForbiddenException("You are

    not a member!")) 正常系を先に書く Thread が Member を持っているなら、正常。それ以外は、異常。 否定の ! は、「持っている」の否定なので「持っていない」になるな、と脳内変換が必要 「読みやすさ」を重視するなら、否定とか無いほうが良い どうしても異常を先に検査するなら、 thread.hasNotMember とかにする サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 43 / 50
  20. 生成ロジックとするか事後条件とするか lazy val bookmark = Bookmark( id = bookmarkId, status

    = BookmarkStatus.Active, messageId = command.messageId, userAccountId = command.userAccountId, createdAt = now ) val result = for { message <- messageRepository.findById(command.messageId).recoverWith { case e: AggregateNotFoundException => Future.failed(new MessageNotFoundException(e)) } thread <- threadRepository.findById(message.breachEncapsulationOfThreadId).recoverWith { case e: AggregateNotFoundException => Future.failed(new ThreadNotFoundException(e)) } _ <- if (thread.hasMember(command.userAccountId)) Future.successful(()) else Future.failed(new AddBookmarkForbiddenException("You are not a member!")) _ <- bookmarkRepository.store(bookmark) } yield AddBookmarkSuccess(bookmarkId) サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 44 / 50
  21. 生成ロジックとするか事後条件とするか _ <- if (thread.hasMember(command.userAccountId)) Future.successful(()) else Future.failed(new AddBookmarkForbiddenException("You are

    not a member!")) 上記の処理は、Bookmark の生成ロジックと言えそう こんな感じで、生成処理をFactory Method としてドメインに持っていくのが良いかなーと思いま す。 object Bookmark { def generate(thread: Thread, messageId: MessageId, userAccountId: UserAccountId): Either[DomainError, Bookma if (thread.hasMember(userAccountId)) { ... } サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 45 / 50
  22. Scala に関する指摘事項から Unlearn Future の並行処理を適切に使おう Future で明示的な例外をthrow しない if は式なので、else

    書く 生成ロジックを暗黙知にせず、ファクトリメソッドに明示する サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 48 / 50