scala with ddd

65
Scala with DDD かとじゅん(@j5ik2o)

Upload: -

Post on 08-Sep-2014

9.490 views

Category:

Technology


4 download

DESCRIPTION

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

TRANSCRIPT

Page 1: Scala with DDD

Scala with DDDかとじゅん(@j5ik2o)

Page 2: Scala with DDD

Scalaで実践的な設計の話

Page 3: Scala with DDD

自己紹介

• DDD/Scala/Finagle

• http://git.io/trinity

• Haskell/MH4

Page 4: Scala with DDD

Scalaで実践的な設計の話  じゃなくてMH4の話…。

Page 5: Scala with DDD

設計には様々な正解があります。 DDDの実践例のひとつだと

思ってください。

Page 6: Scala with DDD

ところで

Page 7: Scala with DDD

ウェブアプリケーションを作るときに、

何を重視して設計するか?

Page 8: Scala with DDD

テーブル?

Page 9: Scala with DDD

UI?

Page 10: Scala with DDD

DDDでは(ドメイン)モデル

Page 11: Scala with DDD

なんで?

Page 12: Scala with DDD

詳しくはこちら?

Page 13: Scala with DDD

というのは、 冗談です

Page 14: Scala with DDD

モデルオブジェクト(=オブジェクト指向)を使って、問題を解決したいか

らです。

Page 15: Scala with DDD

複雑な問題は モデルを使って

解決する

Page 16: Scala with DDD

DomainModel

I/F

Page 17: Scala with DDD

問題の領域=ドメイン

Page 18: Scala with DDD

ボクらのハンタードメイン

Page 19: Scala with DDD

Hunter Item

Sword Armor

Monster

Page 20: Scala with DDD

ハンター世界の 一つのシナリオを 考えてみよう

Page 21: Scala with DDD

落とし物を拾うシーン

Page 22: Scala with DDD

HUNTER_LOST_MATTERテーブルに

外部キーの関連を追加する

HUNTER LOST_MATTERHUNTER_LOST_MATTER1 0..* 1 1

Page 23: Scala with DDD

これは間違いではない。 しかし実装の用語であって

ドメインの知識を 表していない

Page 24: Scala with DDD

ハンターが落とし物を拾い、アイテムとして所持する。

Hunter LostMatter

trait  Item  trait  LostMatter  extends  Item  trait  Hunter  {      val  items:  Seq[Item]      def  take(lostMatter:  LostMatter):  Try[Hunter]  }

1 0..*

Page 25: Scala with DDD

ドメインの語彙= ユビキタス言語

Page 26: Scala with DDD

シナリオ

モデル実装

Page 27: Scala with DDD

他にもシナリオがある

• モデル間の関係がどうなるのか考える

• 乗り攻撃のシーン

• しっぽを切り落とすシーン

Page 28: Scala with DDD

DDDによるレイヤー化

ドメインの概念 ドメイン層

アプリケーション層

UI

インフラストラクチャ層

Form

Dto(ViewModel)

Validator

Controller

HMTL/CSS JavaScript

Entity

ValueObject

Service

Factory

Repository

Aggregate

ORMRPCConfiguration

DataAccess

Module

Page 29: Scala with DDD

モデルと実装

Page 30: Scala with DDD

モデルの種類

モジュール

エンティティ

サービス

値オブジェクト

Page 31: Scala with DDD

エンティティ

• 見分けることができる

• 不変の識別子を持つ

Page 32: Scala with DDD

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      }  !}

Page 33: Scala with DDD

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"  }

Page 34: Scala with DDD

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

Page 35: Scala with DDD

//  値表現としての山城さんが改名されてしまったら見分けられない  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)

Page 36: Scala with DDD

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  =  …    }

Page 37: Scala with DDD

//    見つけられる  val  l  =  List(      User(1,  "Yutaka",  "Hogeshiro"),      User(2,  "Junchi",  "Kato"))  l.exists(_  ==  User(1,  "Yutaka",  "Yamashiro"))  //  true

Page 38: Scala with DDD

case  class  HunterId(value:  UUID)      extends  Identity[UUID]  !

class  Hunter(      val  identity:  HunterId,      val  name:  String,      val  rank:  Int,      //  ...  )  extends  Entity[HunterId]  {      //  ...  }

Page 39: Scala with DDD

値オブジェクト

• 識別はしない。

• 値の説明が目的。

• 原則的に不変オブジェクト。

• Identityは値オブジェクト。

Page 40: Scala with DDD

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を解毒させる      }  }

Page 41: Scala with DDD

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)      }  !

}

Page 42: Scala with DDD

ドメインモデルはユビキタス言語と対応づくこと (クラス名, 属性, 振舞い)

Page 43: Scala with DDD

指定席と自由席

• 座席予約システムの場合

• 座席と参加者はエンティティ。各チケットに座席番号が紐づくから

• イベント自体が自由席でチケットを持っていればどこでもよいならエンティティである必要はない。個数だけ把握できればいいので、値オブジェクトとなる。

Page 44: Scala with DDD

ライフサイクルの管理

Page 45: Scala with DDD

ライフサイクル管理

ファクトリ

リポジトリ

集約

Page 46: Scala with DDD

リポジトリ• エンティティをリポジトリに保存したり、識別子からエンティティを取得できる。

• ドメインの知識は表現しないで、I/Oだけを担当する。

• 内部で何をしていても、外部からはコレクションのように見える。

Page 47: Scala with DDD

よくある勘違い• DDDにおいては、User#saveはドメインモデルの責務じゃない。ユビキタス言語に対応する言葉がないから。これはリポジトリの責務。

• ARのようなモデルはドメインモデルとせずに、インフラストラクチャ層のモデルと定義した方が現実的。

Page 48: Scala with DDD

Repository ≠ DAO

Repository

on DBon Memory on Memcached on API

DAO

Page 49: Scala with DDD

どんな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]]

Page 50: Scala with DDD

Cache ManagementRepository Decorator

on DBon Memcached

Page 51: Scala with DDD

!    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                  }          }      }

Page 52: Scala with DDD

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  }

Page 53: Scala with DDD

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  }

Page 54: Scala with DDD

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          }  }

Page 55: Scala with DDD

ScalikeJDBCでUnitOfWorkを実装

Page 56: Scala with DDD

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          }  }

Page 57: Scala with DDD

   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()                  }          }      }

Page 58: Scala with DDD

REST APIで ドメインの利用例

Page 59: Scala with DDD

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                                  }                      }              })      }  }

Page 60: Scala with DDD

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()                      }              }      }  }

Page 61: Scala with DDD

やってみて思ったこと

Page 62: Scala with DDD

DDDでは フルスタックF/Wが

使いにくい

Page 63: Scala with DDD

Play2

Play2 with DDD

Anorm

ControllerBatch or ???

DomainEntity

RepositoryVO

Service

ScalikeJDBC

Page 64: Scala with DDD

まとめ

• コストがかかる。複雑でない問題には使わない。

• CoCを前提にするF/Wとは相性が悪い。F/Wとけんかしない方法を選ぶべき。

• OOPよりデータと手続きの方が高速。とはいえ、オブジェクトを使いますよね。高速化必須な場合は局所的に手続き型にする。

Page 65: Scala with DDD

ありがとございました。