• Like
Scala with DDD
Upcoming SlideShare
Loading in...5
×

Scala with DDD

  • 9,764 views
Uploaded on

Scalaを使ってDDDを実践する方法について簡単に説明。

Scalaを使ってDDDを実践する方法について簡単に説明。

  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Be the first to comment
No Downloads

Views

Total Views
9,764
On Slideshare
0
From Embeds
0
Number of Embeds
17

Actions

Shares
Downloads
25
Comments
0
Likes
38

Embeds 0

No embeds

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
    No notes for slide

Transcript

  • 1. Scala with DDD かとじゅん(@j5ik2o)
  • 2. Scalaで実践的な設計の話
  • 3. 自己紹介 • DDD/Scala/Finagle • http://git.io/trinity • Haskell/MH4
  • 4. Scalaで実践的な設計の話  じゃなくてMH4の話…。
  • 5. 設計には様々な正解があります。 DDDの実践例のひとつだと 思ってください。
  • 6. ところで
  • 7. ウェブアプリケーション を作るときに、 何を重視して設計するか?
  • 8. テーブル?
  • 9. UI?
  • 10. DDDでは(ドメイン)モデル
  • 11. なんで?
  • 12. 詳しくはこちら?
  • 13. というのは、 冗談です
  • 14. モデルオブジェクト(=オ ブジェクト指向)を使っ て、問題を解決したいか らです。
  • 15. 複雑な問題は モデルを使って 解決する
  • 16. I/F Domain Model
  • 17. 問題の領域= ドメイン
  • 18. ボクらのハンタードメイン
  • 19. Hunter Item Monster Sword Armor
  • 20. ハンター世界の 一つのシナリオを 考えてみよう
  • 21. 落とし物を拾うシーン
  • 22. HUNTER_LOST_MATTER テーブルに 外部キーの関連を追加する HUNTER 1 0..* HUNTER_LOST_MATTER 1 1 LOST_MATTER
  • 23. これは間違いではない。 しかし実装の用語であって ドメインの知識を 表していない
  • 24. ハンターが落とし物を拾い、 アイテムとして所持する。 Hunter 1 0..* LostMatter trait  Item   trait  LostMatter  extends  Item   trait  Hunter  {      val  items:  Seq[Item]      def  take(lostMatter:  LostMatter):  Try[Hunter]   }
  • 25. ドメインの語彙= ユビキタス言語
  • 26. シナリオ 実装 モデル
  • 27. 他にもシナリオがある • モデル間の関係がどうなるのか考える • 乗り攻撃のシーン • しっぽを切り落とすシーン
  • 28. DDDによるレイヤー化 UI Controller Form Dto(ViewModel) Validator JavaScript HMTL/CSS アプリケーション層 Factory Entity ドメインの概念 ValueObject ドメイン層 Repository Service Module Aggregate インフラストラクチャ層 Configuration RPC ORM DataAccess
  • 29. モデルと実装
  • 30. モデルの種類 エンティティ 値オブジェクト サービス モジュール
  • 31. エンティティ • 見分けることができる • 不変の識別子を持つ
  • 32. trait  Entity[ID  <:  Identity[_]]  {   !    /**  エンティティの識別子。  */      val  identity:  ID   !    override  final  def  hashCode:  Int  =          31  *  identity.##   !    override  final  def  equals(obj:  Any):  Boolean  =            obj  match  {              case  that:  Entity[_]  =>                  identity  ==  that.identity          case  _  =>  false      }   ! }
  • 33. trait  Identity[+A]  extends  Serializable  {   !    def  value:  A   ! }   ! object  EmptyIdentity      extends  Identity[Nothing]  {   !    def  value  =  throw  EmptyIdentityException()   !    override  def  equals(obj:  Any):  Boolean  =          EmptyIdentity  eq  obj   !    override  def  hashCode():  Int  =  31  *  1   !    override  def  toString  =  "EmptyIdentity"   }
  • 34. case  class  User(id:  Int,                                  firstName:  String,                                  lastName:  String)   ! val  l  =  List(      User(1,  "Yutaka",  “Yamashiro"),      User(2,  "Junchi",  “Kato")   )   l.exists(_  ==  User(1,  "Yutaka",  "Yamashiro"))  //  true
  • 35. //  値表現としての山城さんが改名されてしまったら見分けられない   val  l  =  List(      User(1,  "Yutaka",  “Yamashiro").          copy(lastName  =  “Hogeshiro"),      User(2,  "Junchi",  "Kato"))   l.exists(_  ==  User(1,  "Yutaka",  "Yamashiro"))  //  false   ! //  この操作は危険。オブジェクトを取り違える可能性。   User(1,  "Yutaka",  "Yamashiro").copy(id  =  2)
  • 36. class  User(val  id:  Int,                        val  firstName:  String,                        val  lastName:  String)  {        override  def  equals(obj:  Any):  Boolean  =  obj  match  {            case  that:  User  =>  id  ==  that.id            case  _  =>  false        }        override  def  hashCode  =  31  *  id.##        //  識別子は更新できない        def  copy(firstName:  String  =  this.firstName,                          lastName:  String  =  this.lastName)  =              new  User(firstName,  lastName)   }   object  User  {      def  apply(…):  User  =  …     }
  • 37. //    見つけられる   val  l  =  List(      User(1,  "Yutaka",  "Hogeshiro"),      User(2,  "Junchi",  "Kato"))   l.exists(_  ==  User(1,  "Yutaka",  "Yamashiro"))  //  true
  • 38. case  class  HunterId(value:  UUID)      extends  Identity[UUID]   ! class  Hunter(      val  identity:  HunterId,      val  name:  String,      val  rank:  Int,      //  ...   )  extends  Entity[HunterId]  {      //  ...   }
  • 39. 値オブジェクト • 識別はしない。 • 値の説明が目的。 • 原則的に不変オブジェクト。 • Identityは値オブジェクト。
  • 40. sealed  trait  Item  {      val  name:  String      def  beUsedBy(hunter:  Hunter):  Try[Hunter]   }   case  class  Analepticum()  extends  Item  {      val  name  =  "analepticum"      def  beUsedBy(hunter:  Hunter):  Try[Hunter]  =  {          //  hunterを回復させる      }   }   case  class  Antidote()  extends  Item  {      val  name  =  "antidote"      def  beUsedBy(hunter:  Hunter):  Try[Hunter]  =  {          //  hunterを解毒させる      }   }
  • 41. class  Hunter(      val  identity:  HunterId,      val  name:  String,      val  rank:  Int,      val  items:  Set[Item]   )  extends  Entity[HunterId]  {   !    def  use(item:  Item):  Try[Hunter]  =  {            require(items.exists(_  ==  item))            item.beUsedBy(hunter)      }   ! }
  • 42. ドメインモデルはユビキ タス言語と対応づくこと (クラス名, 属性, 振舞い)
  • 43. 指定席と自由席 • 座席予約システムの場合 • 座席と参加者はエンティティ。各 チケットに座席番号が紐づくから • イベント自体が自由席でチケット を持っていればどこでもよいなら エンティティである必要はない。 個数だけ把握できればいいので、 値オブジェクトとなる。
  • 44. ライフサイクルの管理
  • 45. ライフサイクル管理 ファクトリ リポジトリ 集約
  • 46. リポジトリ • エンティティをリポジトリに 保存したり、識別子からエン ティティを取得できる。 • ドメインの知識は表現しない で、I/Oだけを担当する。 • 内部で何をしていても、外部 からはコレクションのように 見える。
  • 47. よくある勘違い • DDDにおいては、User#saveはドメイン モデルの責務じゃない。ユビキタス言語 に対応する言葉がないから。これはリポ ジトリの責務。 • ARのようなモデルはドメインモデルとせ ずに、インフラストラクチャ層のモデル と定義した方が現実的。
  • 48. Repository DAO Repository on Memory on Memcached on DB DAO on API
  • 49. どんなI/Fがあるか def  resolve(identity:  ID)                        (implicit  ctx:  EntityIOContext):  Future[E]   ! def  store(entity:  E)                    (implicit  ctx:  EntityIOContext):  Future[(R,  E)]   ! def  delete(identity:  ID)                      (implicit  ctx:  EntityIOContext):  Future[(R,  E)]   ! def  resolveChunk(index:  Int,  maxEntities:  Int)                                  (implicit  ctx:  EntityIOContext):                                    Future[EntitiesChunk[ID,  E]]   ! //  toList,  toSetは  メモリ版の実装のみ。                                   def  toList:  Future[List[E]]   def  toSet:  Future[Set[E]]
  • 50. Cache Management Repository Decorator on Memcached on DB
  • 51. !    protected  val  storage:  AsyncRepository[ID,  E]   !    protected  val  cache:  AsyncRepository[ID,  E]   !    def  resolve(identity:  ID)                            (implicit  ctx:  EntityIOContext):
                          Future[E]  =  {          implicit  val  executor  =  getExecutionContext(ctx)          cache.resolve(identity).recoverWith  {              case  ex:  EntityNotFoundException  =>                  for  {                      entity  <-­‐  storage.resolve(identity)                      (_,  result)  <-­‐  cache.store(entity)                  }  yield  {                      result                  }          }      }
  • 52. def  filterByPredicate(predicate:  E  =>  Boolean,                                              index:  Option[Int]  =  None,                                              maxEntities:  Option[Int]  =  None)                                            (implicit  ctx:  EntityIOContext):                                            Future[EntitiesChunk[ID,  E]]   ! def  filterByCriteria(criteria:  Criteria,                                            index:  Option[Int]  =  None,                                            maxEntities:  Option[Int]  =  None)                                          (implicit  ctx:  EntityIOContext):                                            Future[EntitiesChunk[ID,  E]]   ! trait  CriteriaValue[A]  {      val  name:  String      val  operator:  OperatorType.Value      val  value:  A      def  asString:  String   }   ! trait  Criteria  {      protected  val  criteriaValues:  List[CriteriaValue[_]]      def  asString:  String   }
  • 53. val  repository:  HunterRepository  =   HunterRepository(RepositoryType.Memory)   //  or  RepositoryType.Memcached   ! val  hunter  =  new  Hunter(EmptyIdentity,  ...)   ! val  updateTime  =  for  {      (newRepos,  newEntity)  <-­‐  repository.store(hunter)   //  def  store(entity:  E):  Try[(R,  E)]      hunter  <-­‐  newRepos.resolve(newEntity.identity)   //  def  resolve(identity:  ID):  Try[E]   }  yield  {      hunter.updateTime   }
  • 54. val  repository:  HunterRepository  =        HunterRepository(RepositoryType.JDBC)   ! val  hunter  =  new  Hunter(EmptyIdentity,  ...)   ! //  def  withTransaction[T](f:  (DBSession)  =>  T):  T   val  updateTime  =  UnitOfWork.withTransaction  {      tx  =>          implicit  ctx  =  EntityIOContext(tx)          for  {              (newRepos,  newEntity)  <-­‐  repository.store(hunter)   //  def  store(entity:  E)   //    (implicit  ctx:  EntityIOContext):  Try[(R,  E)]              hunter  <-­‐  newRepos.resolve(newEntity.identity)   //  def  resolve(identity:  ID)   //    (implicit  ctx:  EntityIOContext):  Try[E]          }  yield  {              hunter.updateTime          }   }
  • 55. ScalikeJDBCで UnitOfWorkを実装
  • 56. val  repository:  HunterRepository  =        HunterRepository(RepositoryType.JDBC)   ! val  hunter  =  new  Hunter(EmptyIdentity,  ...)   ! //  def  withTransaction[T](f:  (DBSession)  =>  Future[T]):  Future[T]   val  updateTime  =  UnitOfWork.withTransaction  {      tx  =>          implicit  ctx  =  EntityIOContext(tx)          for  {              (newRepos,  newEntity)  <-­‐  repository.store(hunter)   //  def  store(entity:  E)   //          (implicit  ctx:  EntityIOContext):  Future[(R,  E)]              hunter  <-­‐  newRepos.resolve(newEntity.identity)   //  def  resolve(identity:  ID)   //          (implicit  ctx:  EntityIOContext):  Future[E]          }  yield  {              hunter.updateTime          }   }
  • 57.    def  withTransaction[A](op:  (DBSession)  =>  Future[A])                                  (implicit  executor:  ExecutionContext):  Future[A]  =  {          Future(ConnectionPool.borrow()).flatMap  {              connection  =>                  val  db  =  DB(connection)                  Future(db.newTx).flatMap  {                      tx  =>                          Future(tx.begin()).flatMap  {                              _  =>                                  op(db.withinTxSession(tx))                          }.andThen  {                              case  Success(_)  =>  tx.commit()                              case  Failure(_)  =>  tx.rollback()                          }                  }.andThen  {                      case  _  =>  connection.close()                  }          }      }
  • 58. REST APIで ドメインの利用例
  • 59. class  HunterController(hunterRepository:  HunterRepository)        extends  ControllerSupport  {      def  createHunter  =  SimpleAction  {          request  =>              val  params  =  parseJson(request)              val  formValidation  =  CreateForm.validate(params)              formValidation.fold(validationErrorHandler,  {                  case  form  =>                      UnitOfWork.withSession  {                          implicit  session  =>                              hunterRepository.                                  store(form.asEntity).flatMap  {                                      case  (_,  entity)  =>                                          responseBuilder.                                              withJValue(entity.asJValue).toFuture                                  }                      }              })      }   }
  • 60. class  HunterController(hunterRepository:  HunterRepository)        extends  ControllerSupport  {      def  transferItems(from:  HunterId,                                          to:  HunterId,                                          itmes:  Seq[Item])  =  SimpleAction  {          request  =>              UnitOfWork.withSession  {                  implicit  session  =>                      for  {                          toHunter  <-­‐  hunterRepository.resolve(to)                          fromHunter  <-­‐  hunterRepository.resolve(from)                          _  <-­‐  fromHunter.transerItems(items,  toHunter)                      }  yield  {                          createResponse()                      }              }      }   }
  • 61. やってみて思ったこと
  • 62. DDDでは フルスタックF/Wが 使いにくい
  • 63. Play2 with DDD Controller Batch or ??? Entity VO Domain Repository Service ScalikeJDBC Anorm Play2
  • 64. まとめ • コストがかかる。複雑でない問題には使わない。 • CoCを前提にするF/Wとは相性が悪い。F/Wとけん かしない方法を選ぶべき。 • OOPよりデータと手続きの方が高速。とはいえ、オ ブジェクトを使いますよね。高速化必須な場合は局所 的に手続き型にする。
  • 65. ありがとございました。