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

C952d9f90c7ec6cb1b5513c05d3db536?s=47 yoshiyoshifujii
September 16, 2019

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

C952d9f90c7ec6cb1b5513c05d3db536?s=128

yoshiyoshifujii

September 16, 2019
Tweet

Transcript

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

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

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

    そのあたりをご紹介できればと思います サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 3 / 50
  4. 「まなびほぐし(アンラーン unlearn )」というのは、「まなび(learn )」のやり直しである。しか し、「やり直し」と言っても、これまで学んできた知識や技能を「帳消しにする」などということが できるわけはない。「一たす一は二である」という知識を、あえて「なかったことにする」わけには いかない。ここはやはり、これまでの「まなび」。通して身に付けてしまっている「型」としての 「まなびの身体技法(まなび方)」について、それをあらためて問い直し、「解体」して、組み替え るということを意味しているのであろう。(『まなびを学ぶ』62 頁)

    サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 4 / 50
  5. Yoshitaka Fujii @yoshiyoshifujii Chatwork 株式会社(13 ヶ月目) Scala 関西 Summit スタッフ(4

    年目) 登壇 ScalaMatsuri2016 Scala でドメイン駆動設計に真正面から取り組んだ話 ScalaMatsuri2017 Serverless Architecture をScala で構築するぞ ScalaMatsuri2019 実践 Clean Architecture ⛰ 自己紹介 サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 5 / 50
  6. サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 6 / 50

  7. https://2019.scala­kansai.org サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 6 / 50

  8. あじぇんだ どんなことしたの? Scala の教育 DDD とScala で開発 サマーインターンシップ2019 で学生とDDD なScala

    開発に取り組んだ Scala 秋祭り 7 / 50
  9. どんなことしたの ? 講義パート : 1 週間 業務パート : 2 週間

    サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 8 / 50
  10. 講義パートの目的 業務パートを過ごすうえで、最低限必要となる知識を伝える 業務パートを過ごすうえで、初めて聞く単語を、限りなく少くしたい 「あ、これ、あそこで習った( 聞いた) ことやから、そこから調べれば何とかなる」状態を目指す サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ

    Scala 秋祭り 9 / 50
  11. 講義パートの概要 ソフトウェア開発の全体像 要件定義 設計 開発・テスト 配置 運用 インフラ チーム開発 サマーインターンシップ2019

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

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

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

    50
  15. Scala の教育 ­ 前提 インターン生は、Scala の経験が、 無い 関数型経験者はいる (OCaml 、Rust

    、Elm など) サーバサイドに興味がある チーム開発をしてみたい Scala を業務でどう取り組んでいるのか知りたい これを機会にScala はじめたいという方々… サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 14 / 50
  16. インターンが始まるまでに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
  17. インターンが始まるまでに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
  18. Scala の教育 ­ 講義 サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り

    17 / 50
  19. Scala の教育 ­ 講義 目的 実践Scala 入門 と 業務パートで必要となる知識の間を埋める 業務パートで使う技術スタックを説明

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

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

    class を上手に利用する 汎用的な型を特化した型にするため、型に対するパターンマッチ タプルを上手に利用する 値の変換の過程で、可読性を損なわない範囲で trait を上手に利用する インターフェースの観点、再利用の観点、責務の分離の観点 サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 19 / 50
  22. DDD と Scala で開発 サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り

    20 / 50
  23. DDD と Scala で開発 ドメインモデリングからのUnlearn Scala に関する指摘事項からのUnlearn サマーインターンシップ2019 で学生とDDD なScala

    開発に取り組んだ Scala 秋祭り 21 / 50
  24. ドメインモデリング ユースケースを定義し、内容に齟齬が無いか、PM と議論し、合意形成 ユースケースの内容を議論するなかで、ユビキタス言語の正しい表現に着目 ドメインモデルに採用するユビキタス言語をPM と合意 ユビキタス言語をドメインモデルとして定義するにあたり既存のドメインモデルとの関連を検討 検討したドメインモデルの関連について社員がレビュー ドメインモデルの関連にいたった根拠やユースケースをP­R に記載

    そこから歴戦の猛者である先輩社員たちによる思考のトレースと可能性の模索 Dev チーム( インターン生) は、その考察から自分たちで落とし所を探しドメインモデルを確定 ドメインモデルをコードに落としてレビュー サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 22 / 50
  25. サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 23 / 50

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

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

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

  29. ユースケースを見てみる 利用者はサインインしたUserAccount を使ってMessage をBookmark のリストに1 件追加する 利用者はサインインしたUserAccount を使ってBookmark のリストを表示する 利用者はサインインしたUserAccount

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

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

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

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

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

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

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

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

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

  39. ドメインモデリングからの Unlearn ユースケースの文脈から、「Bookmark のリスト」が読み取れたため、永続化を意識せずにモデリン グすると、 BookmarkList を定義したい ドメインモデリングにおいては、永続化層を意識せずに実施する その後、どのように永続化するか、クエリの要求も踏まえて永続化層の設計をする 永続化層の検討では、データの制限の有無、データストアの種類など、様々な制約を考慮する

    最終的には、ドメイン層以外のフィードバックもドメインモデリングに影響を与える サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 37 / 50
  40. CQRS+ES を使うと、後半の 永続化層の様々な制約をある 程度考慮せずにドメインモデ リングが可能 ちなみに … サマーインターンシップ2019 で学生とDDD なScala

    開発に取り組んだ Scala 秋祭り 38 / 50
  41. Scala に関する指摘事項から Unlearn Future 関連 生成ロジックとするか事後条件とするか サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ

    Scala 秋祭り 39 / 50
  42. 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
  43. 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
  44. 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
  45. 正常系をベースに書く _ <- 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
  46. 生成ロジックとするか事後条件とするか 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
  47. 生成ロジックとするか事後条件とするか _ <- 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
  48. サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 46 / 50

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

  50. Scala に関する指摘事項から Unlearn Future の並行処理を適切に使おう Future で明示的な例外をthrow しない if は式なので、else

    書く 生成ロジックを暗黙知にせず、ファクトリメソッドに明示する サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 48 / 50
  51. まとめ サマーインターンシップを通して、社内でも暗黙知となっている部分をUnlearn することができた 改めて学び直すこと、言語化することを通して、色々な気付きがあるのは良いですね 引き続き、このような機会を創出していくことを、社内やコミュニティで実現できると良いですね サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala

    秋祭り 49 / 50
  52. 一緒に働くエンジニアを募集しています! https://corp.chatwork.com/ja/recruit/ サマーインターンシップ2019 で学生とDDD なScala 開発に取り組んだ Scala 秋祭り 50 /

    50