Se ha denunciado esta presentación.
Utilizamos tu perfil de LinkedIn y tus datos de actividad para personalizar los anuncios y mostrarte publicidad más relevante. Puedes cambiar tus preferencias de publicidad en cualquier momento.

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

4.932 visualizaciones

Publicado el

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

Publicado en: Tecnología
  • Sé el primero en comentar

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 -

×