Successfully reported this slideshow.
We use your LinkedIn profile and activity data to personalize ads and to show you more relevant ads. You can change your ad preferences anytime.

GANMA!でDDDをやってみてから1年くらい経った

4,578 views

Published on

“Septeni×Scala”勉強会 #3 「ドメイン駆動設計やってみたpart2」
http://septeni-scala.connpass.com/event/15273/
の発表資料です

Published in: Technology
  • Be the first to comment

GANMA!でDDDをやってみてから1年くらい経った

  1. 1. GANMA!でDDDをやってみて から1年くらい経った Septeni × Scala #3 2015/06/16 杉谷 -配布用-
  2. 2. 前説 • GANMA!をDDDで作ってみたので、実際 にどういう組み方をしたのかをご紹介し ます。 – リリースしてから1年半分の反省も添えてみ ました。 – 甘いところは多いので、突っ込み所を探す感 じでご覧ください
  3. 3. GANMA!について • 2013/12〜 • スマホ向けの完全オリジナ ルコミックを配信するアプ リ – アニメ化・単行本化も続々 と。 • 以下の3要素 – サーバー – リーダー3種 – 管理ツール群 • 今日はサーバーでDDDを やってみたのお話です。
  4. 4. 前提@2013 • なんでDDD? – なんか前職で流行ってた。 – 詳しくはSepteni×Scala#1の 発表資料をご覧ください。 • DDD扱ったことあった? – なかった。有給消化中にDDD本を読んでみた だけ。 – Scalaも初挑戦だった。
  5. 5. “マンガ”のモデリング 少年ジャンプをイメージしつつ ユビキタス言語や構造を考えてみる。
  6. 6. “マンガ”のモデリング • 始めにGANMA!のマンガ配信業務について 考えます。 – 紙とは違う点も多々ありますが、基本 「週3で漫画雑誌が公開される」というサー ビスです。※今は雑誌形式ではなくなったけど。 • この漫画雑誌を全員で「マガジン」と呼 ぶことにして、ドメインを考えてみまし た。
  7. 7. “マガジン”と”ストーリー” • “マガジン”の具体例と して、週刊少年ジャン プを考えてみます。 • ジャンプには 「ナルト」の「第313 話」といったように 「第XXX話」が収録さ れています。 • これを“ストーリー”と 呼ぶことにします編集 さんも含めて全員で) 週刊少年ジャンプ <Magazine> ナルト 第1話 <Story> ワンピース 第1話 <Story> …
  8. 8. ”ストーリー”と”シリーズ” • またストーリーは「ナ ルト」や「ワンピー ス」など、一連の作品 群への名前があります。 • これを”シリーズ”と呼 ぶことにします。(全 員で以下略… 週刊少年ジャンプ <Magazine> ワンピース 第1話 <Story> ワンピース <Series> …
  9. 9. マガジンに係わる業務を考える 閲覧系 • マガジン情報(と、 それに関するもの) の取得 • マガジンの新刊一覧 を陳列する • シリーズを通しで読 めるマガジンを陳列 する(いわゆる単行 本) 管理業務 • 作家の管理 • ストーリーの管理 • マガジンの編成
  10. 10. ”ストーリー”ほりさげ(1) • IDがあります(Entity) • “シリーズ”がいます • “作者”がいます – 作者は原作・作画など複数の可能 性がありますが、横着して一つの み保持で。 – 【悩んだ】クラス名を「Author」 としているが、日頃「おーさー」 ではなく「作者」と呼んでいるの で「Sakusya」とすべきか? • 日本語英語の対応さえ固定してれば 問題無し • “ページ”が含まれます。 – 第一話 • 1ページ目 • 2ページ目 • … – “ページ”とはS3上などのURLでア クセスできる画像を指します。 • “サブタイトル”があります – “第45話 にゃんこ襲来” – “#183 eyes of the unknown” – 読み切り等、サブタイトル無しも 存在なのでOption pages:List[RemoteFile] = [ S3(“storyId/1.jpg”), S3(“storyId/2.jpg”), … ] id:StoryId subTitle:Option[String] seriesId:SeriesId authorId:AuthorId Storyクラス
  11. 11. ”ストーリー”ほりさげ(2) • “ストーリーリポジト リ”が居ます – 一覧 / 作者別一覧 / シ リーズ別一覧業務 – 保存業務(ストーリー登 録・更新機能) • インフラ層にいる StoryTableとの仲介で す。 • “ページ”はStoryに集 約されており、連動 して保存されたり読 み出されてたりしま す。(リポジトリは居 ない) StoryRepository • get(storyId) • list • findBy(authorId) • findBy(seriesId) • entry(story) • update(story) • delete(storyId) Story … Story StoryTable
  12. 12. ”シリーズ”ほりさげ(1) • IDがあります(Entity) • “タイトル”があります – ≒作品名 – 読み切りでも作品名はあ るので無題は無いはず? • こっちにも“作者”がいま す。 – ストーリーとシリーズで” 別作者が可能”を意味しま す。 – 実際はほとんどストー リー作者と一致しますが 例外あり • Magazine: ドラクエ4コマ 漫画劇場① • Series:ドラクエ4コマ漫画 劇場(作者:編集部 • Story: 柴田亜美作、衛藤ヒ ロユキ作、… title:Stringid:SeriesId authorId:AuthorId Seriesクラス
  13. 13. ”シリーズ”ほりさげ(2) • “シリーズリポジト リ”が居ます – 一覧 / 作者別一覧 – 保存業務(シリーズ登 録・更新機能) • インフラ層にいる SeriesTableとの仲介 です。 SeriesRepository • get(seriesId) • list • find(authorId) • entry(series) • delete(seriesId) Series … Series SeriesTable
  14. 14. ”マガジン”ほりさげ(1) • IDがあります(Entity) • “タイトル” があります • “ストーリー”を収録していますが、 ストーリー以外も収録してそうで す。 – 表紙とか広告とか告知とか。 – ストーリーと静止画を収録できるよ うにし、”マガジンアイテム”と呼ぶ ようにします • “単行本”という考えが方がありま す – “同じシリーズ”の話をある程度の話 数でまとめた”マガジン” – 単行本でないマガジンを”編成本”と 呼んでいます – 陳列場所が違う • 編成本 : 新刊タブ • 単行本 : ランキングタブ – seriedBindに値があるかどうかで 区別。 • 【悩み】呼び方も機能も違うわ けだからクラスで別ける必要が ある気がする items:List[MagazineItem]=[ StoryItem(storyId), ImageItem(imageId), …] id:MagazineId title:String seriesBind :Option[SeriesId] Magazineクラス
  15. 15. ”マガジン”ほりさげ(2) • “マガジンリポジト リ”が居ます – 一覧 – 保存業務(シリーズ登 録・更新機能) – ランキング • 【悩み】ここに居 るのは不適切な気 がする • インフラ層にいる MagazineTableとの仲介 です。 • マガジンアイテムはマガ ジンに集約されていて、 連動して保存されたり読 まれたりします。(リポ ジトリ無し) MagazineRepository • get(magazineId) • list() • search() • delete(magazineId) • entry(magazine) • update(magazine) • ranking() Magazine Magazine MagazineTable …
  16. 16. 悩んだ: IDの振り方 • IDにはAUTO_INCREMENTを使うことが多いが、 INSERTするまでIDを振れない問題 – 採番用のテーブルを作る? • 実装がすこし手間。 • パフォーマンスと負荷集中とデータ量が怖い – UUID? • UUIDにはv1とv4がある – v1 : MACアドレスを使っているので生成機材問わず一意保証 – v4 : 乱数。 一意保証無し。怖い。JDK標準搭載のはこれ。 – そのときはSELECT UUID(); を利用した • UUIDv1(NICが無いときはv4)。 • 極端には遅くないだろうし、Slaveでも使えるだろうし。 • ちゃんと調べたらSQL使わずともJavaからv1生成可能だった – Java Uuid Generator (JUG)
  17. 17. IDの振り方別解 • Snowflake系 – タイムスタンプ + シーケンス番号 + 何らかの手段で 付与したデバイスID – Scalaを用いて分散IDワーカを実装する (ChwtworkCreator’sNote – かとじゅん氏) • Timestamp(41bit) + データセンターID(5bit) + ワーカー ID(5bit) + シーケンス(12bit) – 軽量なTime-based ID生成器”shakeflake(仮称)”につい て(Smartnews開発者ブログ) • 遅延生成 – 素直に後でIDを生成してセット – 実践ドメイン駆動設計の第5章”エンティティ”に遅延 生成の説明もあり(オススメはしていなかった)
  18. 18. 悩んだ: DB → Entity(1) • DB操作テーブルはInfra層 / Entityは Domain層 • DB取得結果からEntityを作るとき、素 直に”Entityを作って返す”と書くのは まずい気がした – Infra層から上位であるDomain層のメソッ ド呼び出し – ID指定でのインスタンス化がどこからでも 出来るのもゆるい感じが。 • あずかり知らないIDを持つEntityが作れてし まう。 • できれば、ID指定でインスタンス化する手 段はPublicにしたくない感がする import domain.manga.Series object SeriesTable { def get(id) = { val row = SQL(“select〜… Series(row[“id”], row[“title”], …) } } 【インフラ層】 infra/db/manga/SeriesTable.scala
  19. 19. 悩んだ: DB → Entity(2) object SeriesTable{ def get(id){ val row = SQL(“select〜… (row[“id”], row[“title”], …) } object SeriesRepository{ def get(row){… (id, title, authorId,…) = SeriesTable.get(id) Series(id, title, authorId, …) } というわけで、最初はインフラ層から はタプルで返して、Domain層でインス タンスを作っていた。 【ドメイン層】 domain/manga/Series.scala 【インフラ層】 infra/db/manga/SeriesTable.scala …が面倒すぎた
  20. 20. DB → Entity解法(1) • 解法① - DAO – ドメイン層に 生データ ⇔Entity変換のメ ソッドかクラスを設ける。 – 素直な解法 class SeriesRepository { def convertToEntity(row:Row)={ Series(row[“id”], row[“title”]…) } } 【ドメイン層】 domain/manga/Series.scala
  21. 21. DB → Entity解法(2) • 解法② - 依存性逆転の原則 – 実践DDD p118 – 定義(trait/interface)は domain層で、実装はinfra層 を可とする • DB->Entityは普通にinfra層で インスタンスを作って返す – なんとなく気持ちが悪いが、 最近はこれ系を愛用中。 • 実践DDDには他の方法も 載っています。 class SeriesTableResult extend Series { … } trait Series { val id:SeriesId val title:String … } 【ドメイン層】 domain/manga/Series.scala 【インフラ層】 infra/db/manga/SeriesTable.scala
  22. 22. APP層でやっていること例 • 閲覧系 – マガジン情報(と、それに関 するもの)の取得 • MagazineRepositoryを叩くだけ – マガジンの一覧やランキング • MagazineRepositoryのlistやら Rankingを叩くだけ • 管理業務 – 作家の管理 1. 必要入力を受け取る 2. Authorオブジェクトを作る 3. entryする。 4. あとはget/list 5. JSON出力 – ストーリーの管理 1. 必要入力を受け取り”下書 き”を作る – Storyと似たフィールド構成 だが、NULLABLEが多い 2. 下書きをストーリーに変換 する 3. entryする 4. あとはget/list 5. JSON出力 – マガジンの編成 • 格納したいマガジンアイテム の一覧を受け取る • Magazineを作る • entryする
  23. 23. 実装上の反省 • RepositoryとTable操作系をobjectにしてし まった – テストが大変 – コンテキストが絡まざるを得ない事をすると きにも大変 • パフォーマンスチューニングやトランザクション – DI出来るように徐々に改修中 • Repository系でFutureを使わなかった – チューニングを極められない
  24. 24. やってみてどうだったか • DDD良い – 実践しきれてるかどうかはわからないけど、それでも良い 感じ。 – 編集者も含めて、用語を合わせるのは混乱が少なくて良い。 • 説明が同じになると、動員できる知恵が多くなり、ドメインを 捕らえる精度が上がって、ますます良い。 • ドメイン層の実装が、口頭説明と同じになるのはわかりやすい。 – ドメイン層に徹底的に余計なことを書かないようにするの は、とても見通しが良い。 • 大きめのリファクタを決断しやすい。口頭説明でおかしさを感 じたら、リファクタ決断の時
  25. 25. だいたい以上です。
  26. 26. ご静聴ありがとうございました!
  27. 27. 【付録】 勉強会冒頭で利用した 会社紹介の資料
  28. 28. Septeni × Scala 勉強会 #3 〜 ドメイン駆動設計やってみた(2) 〜
  29. 29. 2回目のテーマ:ドメイン駆動設計 チャットワーク株式会社 加藤潤一 さん Play2 with DDD 〜何からはじ めて何に気をつけるべきか〜 セプテーニオリジナル 原田侑亮 Scala : DDD × 弊社実践例 本勉強会について 本日のトピック 社内研修を兼ねつつ、セプテーニの知名度向上を目標としたもの 1回目のテーマ:Scala普及について チャットワーク株式会社 加藤潤一 さん Scala関数プログラミング初級 セプテーニオリジナル 杉谷保幸 Scalaに至るまでの物語 セプテーニオリジナル 寺坂郁也 新卒で初めて学ぶ言語が Scalaで良かったこと/大変だったこと ※資料はconnpassにて共有されています
  30. 30. … インターネット広告事業 自社サービスの企画・開発 海外の開発拠点 ソーシャルゲーム開発 広告プラットフォーム事業 マンガコンテンツ事業 セプテーニとは、ネット広告業を本業としつつ 1部署1会社としていろいろやっている会社です。 セプテーニグループ
  31. 31. セプテーニグループ 売上高 平均年齢 働きがいのある会社ランキング2015
  32. 32. 書籍化・アニメ化も決定! オリジナルコミックアプリ 最強ネット広告運用支援システム 10代少女に人気・ファッションアプリ ・Playframework + Scala ・AngularJS + TypeScript ・DDD採用 ・Unit Test 完備 ・スクラム ・Android + Scala ・Swift ※検証中 ・Playframework + Scala ・AngularJS + TypeScript ・DDD採用 ・Unit Test 完備 ・スクラム ・Sisioh(http://sisioh.org/) …for ・弊社最後のLAMP構成 ・レガシーコード改善ガイド活用 技術トピック ・スクラム ・Android + Kotolin
  33. 33. 求人 セプテーニ・オリジナルでは Scalaエンジニアを募集しています 社内カフェ(ランチ営業あり) スタンディング作業可の最新デスク
  34. 34. 本日のテーマ:ドメイン駆動設計(2) セプテーニオリジナル 杉谷保幸 GANMA!でDDDをやってみてから1年くらい経った セプテーニオリジナル 原田侑亮 「DDD × 新人」が学んでみたレシピ共有 20:10 - 21:00 -

×