ddd+scala

  • 3,040 views
Uploaded on

DDDをScalaでどうやって実装していくかという話題。前半はScalaの適当な紹介と、その後にDDDのビルディングブロックをScalaコード上に反映するにはどうするかって話です。残りのサンプルは時間がなくて説明していません。すみません。

DDDをScalaでどうやって実装していくかという話題。前半はScalaの適当な紹介と、その後にDDDのビルディングブロックをScalaコード上に反映するにはどうするかって話です。残りのサンプルは時間がなくて説明していません。すみません。

More in: Technology
  • 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
3,040
On Slideshare
0
From Embeds
0
Number of Embeds
1

Actions

Shares
Downloads
28
Comments
0
Likes
11

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
  • 本日は休日にも関わらず足を運んでいただきまして、ありがとうございます。\n陽と陰の、陰の担当ということで、マニアックにScalaでDDDをやるにはどうするかってテーマでいきます。\n
  • DDD翻訳版の出版おめでとうございます。昨年のDevLOVEで和田さんにお願いして、和智さんを紹介して貰って、私と隣でやっている都元さんで2部(4,5,6章)のレビューを担当しました。\n
  • ScalaでDDDをやるにはどうしたらよいかというテーマで話します。\nまずは、みなさんJavaがわかる前提でScala早わかりでざっくりと解説。\nその後、DDD+Scalaです。主にDDDの2部の話を中心にScalaでどう実践するかというテーマで話します。\n
  • Scala早わかり。理屈はそこそこに、ざっくりとどんな感じでコードを書けばよいか勘所を紹介します。\n
  • 最近流行っているScalaです。今私も日経ソフトウエアでScalaの特集記事も書いているぐらいですから、注目されている言語です。注目されている理由はいろいろありますが,Scalaは、関数型言語の機能を取り込むことで、簡潔で明瞭なコーディング、言語自体の拡張性、バグを作り込みにくいプログラミングスタイル、並行処理向きなど、オブジェクト指向とは異なる特徴を持っています。あまり詳しく説明するとこれだけで終わってしまうので、このセッションで必要最低限の知識だけをいれてScalaとDDDを考えてみましょう。\n
  • お金を表すMoneyクラス(amountはお金の量, currencyは通貨単位)\nScalaはJavaの世界に明瞭で簡潔をもたらします。\n
  • おなじみなHelloWorldですが。\nまずobjectが出てきました。Javaにはないです。何かというとシングルトンです。インスタンスが一つしか作れないオブジェクトですね。これがあるので、Scalaではstaticがありません。\ndefから始まるのがメソッドです。Ruby, Pythonに近いですね。mainもstaticなしで記述します。引数はメソッド名の後ろに書きますが、型名は変数名の後ろに記述します。配列はArray型を使います。[]のブラケットはジェネリックスの型変数を指定しています。メソッドの後ろにあるのがメソッドの戻り値の型です。関数の本体は=の後ろに続けて記述します。publicキーワードがないですが、デフォルトのアクセス衆力しはpublicなのでわざわざ付ける必要がありません。\n
  • Scalaではvalとvarを使って変数を宣言します。\nvalはJavaのfinal変数と同じです。再代入ができません。定数ですね。\n\n
  • varは通常の変数です。再代入可能です。\n関数型プログラミングのスタイルを行う場合はvalが基本になります。つまり不変性を重視したスタイルになります。逆にJavaなどの状態を扱う命令型プログラミングのスタイルの場合はvarを利用します。\nこれらはどちらがいいとか悪いとかはなく、適材適所だと思います。プログラムのわかりやすさ、不具合を作りこまないようにするとか、並行性を重視する場合はvalが基本になってくると思います。\n
  • nameは文字列。文字列型はjava.lang.Stringクラス。\n型はコンパイラによって推論される。\n型アノテーションを使って明示的に指定できる\n
  • HelloWorldの例で説明した通りです。\n足し算するメソッドの例です。return a + bですが、メソッドの最後の式の値が戻り値になります。その場合はreturnキーワードは不要です。また一つの式で終わる場合は中括弧は不要です。また、式から戻り値が型推論できる場合はメソッドの戻り値の型も省略できます。\n
  • if式とfor式です。文ではなく式になっています。式は値を評価し返します。\nif式はJavaの三項演算子みたいな感じ。メソッドの=に続けて簡潔に記述できます。\nfor式はJavaと同様繰り返し処理で使えます。またyield(イールド)で返した値を格納したコレクションを返すことができます。\n
  • 次はクラスです。クラス名の後ろにコンストラクタの引数リストを書きます。\nそしてクラスのブロックがコンストラクタブロックです。そこに宣言した変数がフィールドになります。firstNameとlastNameがpublicフィールドです。\n
  • \n
  • Javaと同様にnewして使います。\n
  • HelloWorldでも説明したobjectですが、newせずにシングルトンオブジェクトとして使えます。staticの代わりに使えます。コンストラクタは定義できません。\n
  • Scalaにはコンパニオンクラスとコンパニオンオブジェクトという機能があります。\n同じファイルか同じパッケージにある同じ名前のクラスとオブジェクトのことです。\nJavaだと名前空間で衝突しますが、Scalaではしません。\nこのお金のMoneyクラスに対して、Moneyオブジェクトがあり、JPYという定数や、applyメソッドを提供しています。Money.JPYはクラス側には定義できないので、コンパニオンオブジェクトに定義しています。また、applyはファクトリメソッドです。\nappyメソッドは糖衣構文によって省略して記述できます。newする必要がなくなり、記述性が高まります。\n
  • Scalaではmatch式を使うことで様々なものをパターンマッチできます。Javaのswtich文に似ていますが、それを超越した式です。\nこれはswitch文でお馴染みの数値のマッチングです。nをセレクターといいますが、match式の中のcaseそれぞれが条件です。=>以降に式を書きます。その式は評価されてmatch式の戻り値になります。\n
  • 数値以外にも文字列や、型、コレクション、正規表現にもマッチさせることができます。かなり強力なので、Scalaではif式はほとんど使いません。\n
  • メソッドはクラスに紐づくものです、関数は独立しています。\n1つ以上の引数をとり1つの結果を生成するマッピングなどと例えられています。\nScalaでは関数リテラルという記述方法で関数を定義します。aを二乗する関数です。名前がないので、無名関数とも呼ばれます。この無名関数を変数に代入することができます。他の関数やメソッドの戻り値や引数に取ることができます。関数型言語では、関数単体を制限なく利用可能です。一人前のオブジェクトといいます。\nsquareは関数名ではなく、無名関数への参照の名前です。関数の型は型推論で省略可能です。呼び出しも簡単にできます。\n\n\n
  • クラスに紐づくメソッドを関数に変換するのは簡単です。メソッド名の後ろに _を付けます。もしくは関数の型アノテーションを指定するとよいです。\n
  • コレクションは不変と可変があります。\n
  • 次は要素の繰り返し処理です。\n(1)foreachは関数を引数に取ります。引数が要素型で戻り値はUnitです。要素毎にその関数を呼び出します。(2)引数の型指定と括弧は省略できます。(3)引数をプレースホルダに置き換えることができます。(4)プレースホルダが一度しか出てこない場合は引数=>を省略できます。(5)println文の引数が1つである場合はプレースホルダ自体を省略可能です。\nちなみに、mapも繰り返し操作が可能です。JavaのMap.Entryに相当します。_1がキーで、_2が値です。複数の値をひとまとめにするタプルというものですが、MapはタプルのIterableを実装しているのでListのように扱うことができます。\n
  • 他にも便利メソッドがあります。特定の条件の要素だけを取得するフィルターメソッドや、異なるコレクションへの変換ができるmapメソッドです。ちなみにRangeで数列を簡単につくれます。そのRangeとmapとパターンマッチを使ってFizzBuzzが6行で書けます。\n
  • \n
  • 次はtraitです。traitは実装コードも書けるインターフェイスだと思ってもらってOKです。\nクラスに書くような実装コードの断片を定義して、他のtraitやclassにmix-inできます。\n
  • \n
  • \n
  • それぞれのドメインモデルを説明するために抽象的な型をtraitを使って説明したいと思います。\nまず最初は DDDのモデル駆動開発の話の最初に出てくるのが、エンティティです。そのエンティティを表すtraitです。\ntraitは簡単に言えば実装も書けるインターフェイスです。equalsとhashCodeの実装が記述されています。またidも定義されています。idは抽象フィールドといって実装クラスで定義が必要です。\nエンティティは、システム上で識別を目的とするオブジェクトです。識別とは「物事の種類や性質などを見分けること」です。言い換えると、見分けることが必要なオブジェクトがエンティティです。エンティティの同一性は、属性ではなく識別子が同一かによって判定されるので、equalsではmatch式を使ってidを判定する実装になっています。これを実装クラスに継承(Mix-in)すればよいわけです。\n
  • 先ほどのEntityを実装したEmployeeクラスです。Entityのトレイトのidフィールドがあったので、Employeeでもid属性が必要になります。\nエンティティの概念的な同一性は、idのみによって行われるので、このようになります。エンティティの属性は変化しても、識別子が変わらなければエンティティを見分けることができます。従業員の識別を属性で行っていると、身長や体重や名前などが変わった場合に識別できなくなってしまいます。そういうことを回避するためにエンティティを使います。\n\n\n
  • 同時にファクトリも説明しますが、複雑になりがちなオブジェクトの生成処理を担うオブジェクトです。\n例えば、車というオブジェクトで車自身を作ることはできない。やはり、自動車工場が車を生産するというのが自然だし、車の購入者は工場内の複雑な生産工程を気にする必要はない。このように本来の役割でない複雑な生成処理をファクトリに任せることで、エンティティやバリューオブジェクトを扱いやすくできます。\nScalaのコンパニオンオブジェクトはまさにファクトリ責務をクラスから分離するのにちょうどいい場所です。コンパニオンオブジェクトのapplyメソッドにファクトリを実装してください。\n
  • ファクトリを呼び出すだけです。\n
  • 次は、値オブジェクトとそのファクトリです。\n始めに紹介したMoneyクラスです。Moneyクラスはバリューオブジェクト。つまり値を表すオブジェクトです。エンティティのように概念的な同一性を持ちません。物事を表すオブジェクトです。\n例えば、100円がそれぞれにあっても、100円という価値を表していればよいのであって、個々の100円の識別に関心があるわけではありません。何であるかだけが問題で、誰とか、どれであるかは問われない設計の要素です。\n値オブジェクトは様々なオブジェクトで共有されるため、不変である必要があります。そのためフィールドはvalで宣言しています。共有するなら不変。性能面を考慮して可変とする場合もあるが、原則的に不変です。\nまた、equalsメソッドでは、一般的にIDより等価判定ではなく、属性が同じかどうかの判定を行います。エンティティと同様にファクトリはコンパニオンオブジェクトで実装します。unapplyメソッドは抽出子メソッドです。これは後で説明します。\n\n
  • 値オブジェクトの等価判定の例です。これはDDDに限らず一般契約に基づき実装されます。\nunapplyという抽出子メソッドは、applyメソッドの逆のことを行うメソッドで、オブジェクトのフィールドを抽出することができます。クラス構造に依存せずにクラスの情報にアクセスすることができます。また、match式と組み合わせるとパターンマッチに利用できます。エンティティにも定義することができます。\n
  • Scalaにはバリューオブジェクトを簡単に作るための機能があります。case classです。\nこのように宣言すると前例と同じものがコンパイラによって定義されます。単純な値オブジェクトならこれで十分だと思います。\nまた、特に値オブジェクトとしての型をトレイトで定義していませんが、必要ならマーカートレイトを定義するとよいかもしれません。\n\n
  • 値オブジェクトは不変なので、完全にインスタンスを置き換える以外に変更の手段がありません。値オブジェクトの唯一のデメリットとも呼ばれていますが、頻繁に更新があるような場合はビルダーが欲しくなるかもしれません。実装方法は私のブログの方にあるのでValueObjectBuilderで検索してみてください。\n
  • 次はサービスですね。\nエンティティやバリューオブジェクトは個別に振る舞いを持つ場合がある。\nしかし、すべての振る舞いがエンティティやバリューオブジェクトに属するとは限らない。逆に属すると不自然な振る舞いもある。そのような場合にサービスを使う。\nこの例は口座間の送金を表したサービスです。サービスでは原則的に状態を持たずに、ドメインオブジェクトを使ったスクリプトのように振舞う。多くの場合はobjectで実装すればよいと思います。\n\n
  • 次はアグリゲート。アグリゲートは難しいですね。\nルートエンティティは、不変条件のチェックを行い、グローバルな同一性を持つ。ローカルエンティティは集約内での同一性を保証。\n外部のオブジェクトは、境界内部のオブジェクトの参照を保持できない。ローカルエンティティを他のオブジェクトに渡せるが一時的な参照。値オブジェクトは不変であれば参照を渡せる、可変ならコピーが必要。\nリポジトリなどからの入出力は、集約ルートの単位となる。つまり、トランザクション境界。\n
  • すべての変更はEmployeeを通す必要があるが、変更できてしまう。(1)では外部のDepartmentの参照を持っているためで、(2)では内部のDepartmentの参照を公開しているので、 不変条件が維持できていない。\n
  • そこで一つの解決策としては、JavaではCloneable(クローナブル)を実装して防御的コピーを生成することで、不変条件を維持するという方法があります。Scalaでは@cloneableをクラスに修飾すれば、cloneメソッドを実装できるようになります。\n(1)コンストラクタから外部のdeptを受け取ったら、cloneで複製を作ります。\n(2)外部へdeptを返す場合もcloneで複製を返します。\n(3)外部からdeptを取り込む場合もcloneで複製を作って取り込みます。\nこうすることでEmployeeが集約としての不変条件を維持できます。\nまた、Employe自身も可変オブジェクトなどでcloneを提供します。その場合はdeptをディープコピーするようにしなければなりません。(4)\n
  • 型として利用しやすいようにするには、このようなトレイトを定義するとよいです。Cloneableをエンティティにミックスインするためのトレイトです。\n
  • \n
  • Cloneable以外にはファクトリで複製を作る方法もあります。こちらのほうが構造がシンプルですが、具象クラスがあるところでしか複製が作れません。具象クラスに依存できない場合はcloneの方が有利でしょう。\n
  • リポジトリは、オブジェクトの貯蔵庫という意味。\nエンティティは生成された後に破棄されるまでの間、データベースやファイル、メモリーなどに一時的に永続化することが多い。しかし、永続化は複雑なものであり、ドメインの本質でもない。その永続化をリポジトリが担うことで、このような本質的ではない複雑さを排除する。\nここではまずリードオンリーなリポジトリとして、EntityResolverというのを定義しています。\n多くのメソッドはScalaのIterableの力を借りています。resolveメソッドはエンティティを\n
  • この例はオンメモリで管理するリポジトリですが、 resolveとiteratorを実装するだけで完成です。Iterableでコレクションで使える様々なメソッドが利用できます。foreachやexistsやmapなどが使えます。\n
  • 次は書込みができるリポジトリです。storeとかdeleteがあります。\n
  • \n
  • \n
  • \n
  • つまり、JavaBeansですね。それを生成するツールです。\n
  • 全部を吐き出すのは骨が折れるので、Freemarkerを使います。\n
  • 操作方法はこんな感じ。\n
  • \n
  • ClassMetaにはID以外に、クラス名やパッケージ名、FieldMetaのリストなどが保持しているエンティティです。@BeanPropertyや他のgetterはJavaBeansを前提にするFreemarkerのためのメソッドです。ちなみにIDはStringではなく、Identifierという値オブジェクトを利用しています。\n
  • ClassMetaのコンパニオンオブジェクトです。\n1番目がファクトリメソッド、2番目はIDの自動生成。\n3番目がコピー用のファクトリメソッド。\n4番目が抽出子メソッドです。\n
  • FieldMetaはClassMetaに従属するため、識別が不要な値オブジェクトです。case classでさくっと作ります。BeanPropertyはFreemarker対応です。\n
  • 次はリポジトリです。今回はリードオンリーなのでEntityResolverのみです。\n\n
  • 次はコード生成サービスです。\n
  • 最後はアプリケーションです。\n
  • \n

Transcript

  • 1. DDD + Scala (@j5ik2o)
  • 2. DDD , Scala , DSL , ...DDD 2 (4,5,6 )@j5ik2o
  • 3. agendaScala DDD Scala DDD + Scala DSL JavaBeans
  • 4. Scala
  • 5. ScalaScala .scala .class Java
  • 6. Scala vs Java Money Scala } public BigDecimal getAmount() {case class Money(amount :BigDecimal, currency : Currency) return amount; Money Java }public class Money { public Currency getCurrency() { private final BigDecimal amount; return currency; } private final Currency currency; public Money(BigDecimal amnt, / equals, hashCode /Currency creny) { (ry amount = amt; } currency = creny;
  • 7. Hello, World!!object HelloWorld{ def main(args: Array[String]):Unit = { println(“Hello, World!!”) }}
  • 8. valscala> val name = "Junichi Kato"name: java.lang.String = Junichi Katoscala> name = "JUNICHI KATO"<console>:6: error: reassignment to val name = "JUNICHI KATO"
  • 9. varscala> var name = "Junichi Kato"name: java.lang.String = Junichi Katoscala> name = "JUNICHI KATO"name: java.lang.String = JUNICHI KATO
  • 10. typescala> val name = "Junichi Kato"name: java.lang.String = Junichi Katoscala> val name:String = "Junichi Kato"name: String = Junichi Katoscala> val num:Number = 100Lnum: java.lang.Number = 100
  • 11. methoddef add(a:Int, b:Int):Int = { return a + b}def add(a:Int, b:Int):Int = a + bdef add(a:Int, b:Int) = a + bhoge.add(1, 2) / 3 /
  • 12. if & forifval a = 10val ret = if (a % 2 == 0) true else falseval ret = if (a % 2 == 0) { println(“ ”); true }else { println(“ ”); false }def isEven(n: Int) = if (n % 2 == 0) true else falseforfor(i <- 1 to 3) println(i) / 1,2,3 /for(i <- Array(1,2,3)) println(i) / 1,2,3 /val numbers = for(i <- 1 to 10) yield i
  • 13. classclass PersonName(fn: String, ln: String){ require(fn.length > 0) // IAE require(ln.length > 0) // IAE val firstName = fn val lastName = ln def fullName = “%s, %s”.format(firstName, lastName)}
  • 14. classclass PersonName(val firstName:String, vallastName: String){require(firstName.length > 0)require(lastName.length > 0)def fullName = “%s, %s”.format(firstName,lastName)
  • 15. classval pn = new PersonName(“Junichi”, “Kato”)println(pn.fullName)val pn2 = new PersonName(“”, “Kato”) / IAE /
  • 16. objectobject EmployeeDao { val DEFAULT_NAME = ... def findAll = ...}val result = EmployeeDao.findAllval defaultName = EmployeeDao.DEFAULT_NAME
  • 17. { , }class Money(val amount:BigDecimal, val currency: Currency) ...object Money { val JPY = Currency.getInstance(“JPY”) def apply(amount: BigDecimal, currency: Currency) = newMoney(amount, currency)}val money:Money = Money(100, Money. JPY)/ val money:Money = Money.apply(100, Money.JPY) /
  • 18. matchdef numberMatch(n:Int) = n match { case 1 => “one” case 2 | 3 => “t wo or three” case _ => “other”}println(numberMatch(1)) / one /println(numberMatch(2)) / t wo or three /println(numberMatch(3)) / t wo or three /println(numberMatch(4)) / other /
  • 19. matchval pattern = """([a-z]+)""".r case Array(1,2,3) => println(“def matchTest(word: Any) = word 1,2,3 ”)match { case _ => throw new case “ABC” => println(“ABC ”) IllegalArgumentException case pattern(s) => println(" } = "+s) matchTest(“ABC”) / ABC / case s: String => println(“ matchTest("aaaa") // = = (%s)”.format(s)) (aaaa) case n: Int if (n >= 2) => println(“2 matchTest(10) / 2 / ”) matchTest(Array(1,2,3)) // 1,2,3
  • 20. (a:Int) => a * aval square: (Int) => Int = (a:Int) => a * aval square = (a:Int) => a * aval result = square(2) / 4 /
  • 21. object MyMath { def add(a:Int, b:Int) = a + b}val f1 = MyMath.add _val f2: (Int,Int) => Int = MyMath.add
  • 22. Collectionval il1 = List(1,2,3)val il2 = list :+ 4 / 1,2,3,4 /val im1 = Map(1 -> “ 2 -> “b”, 3 -> “c”) a”,val im2 = m1 + (4 -> “d”) / 1 -> “ 2 -> “b”, 3 -> “c”, 4 -> “d” / a”,val ml = ListBuffer(1,2,3)ml += 4val mm = collection.mutalble.Map(1 -> “ 2 -> “b”, 3 -> “c”) a”,mm += (4 -> “d”)
  • 23. Collectionval numbers = List(1,2,3,45)numbers.foreach((n:Int) => println(n) ) / (1) /numbers.foreach(n => println(n)) / (2) /numbers.foreach(_ => println(_)) / (3) /numbers.foreach(println(_)) / (4) /numbers.foreach(println) / (5) /varl map = Map(1 -> “ 2 -> “b”) a”,map.foreach(entry => println(“key = %s, value =%s”.format(entry._1,entry._2)))
  • 24. Collectionval evens = numbers.filter(_ % 2 == 0) //numbersval list = List(1,2,3).map(_ * 2) / 2,4,6 /
  • 25. CollectionRange ( (Seq) )val range = 1 to 10; range.foreach(println)for(i <- 1 to 10 by 2) println(i) / 1 3 5 7 9 /6 FizzBuzz(1 to 100).map{ case n if (n % 15 == 0) => “FizzBuzz” case n if (n % 3 == 0) => “Fizz” case n if (n % 5 == 0) => “Buzz” case n => n}.foreach(println)
  • 26. traittrait Greeting { def greet:Unit }class JapaneseGreeting extends Greeting { def greet = println(“ ”)}class EnglishGreeting extends Greeting { def greet = println(“Hello”)}mix-intrait Logging { def log(msg: String) = println(msg) }class Employee(name:String) extends AbstractEmployee withLogging { log(“name = “+name)}
  • 27. DDD + Scala
  • 28. Domain Object & Lifecycle
  • 29. Entitytrait Entity { val id: String // OK def equals(other: Any) = other match { case that: Entity => id == that.id case _ => false } def hashCode = id.hashCode}
  • 30. Entityclass Employee(val id: String, val name:String)extends Entityval kato1 = new Employee(“1”, “Junichi Kato”)val kato2 = new Employee(“2”, “Junichi Kato”)val kato3 = new Employee(“3”, “JUNICHI KATO”)assert(kato1 != kato2)assert(kato1 == kato3)
  • 31. Entity with Factoryclass Employee object Employee{(val id:String, def apply(id:String, name:String,var name:String, dept:Department) = newvar dept:Department) Employee(id, name, dept)extends Entity }
  • 32. Entity with Factoryval kato = Employee(“KATO”,Department(“DEV”))
  • 33. Value Object with Factoryclass Money override def toString = "Money(%s, %s)".format(amount,(val amount: BigDecimal, currency)val currency: Currency){ } override def equals(that: Any): object Money {Boolean = that match { def apply(amount: BigDecimal, case other: Money => amount == currency: Currency) = newother.amount && currency == Money(amount, currency)other.currency def unapply(money: Money) = case _ => false Some(money.amount, } money.currency) override def hashCode = }amount.hashCode +currency.hashCode
  • 34. Value Object with FactoryVOassert(Money(100, Money.JPY) == Money(100, Money.JPY))assert(Money(100, Money.JPY) != Money(105, Money.JPY))assert(Money(100, Money.JPY) != Money(100, Money.USD)) unapplyval Money(amt, cry) = money1_100yenprintln(“ amount = %s, currency = %s”.format(amt, cry))money1_100yen match { / match /case Money(amt, cry) => println(“ amount = %s, currency = %s”.format(amt,cry))case _ => ()}
  • 35. Value Object(case class)case class Money(amount: BigDecimal, currency:Currency) apply, unapply val toString, equals, hashCodeVO case class Money(amount:BigDecimal, currency: Currency) extends ValueObject
  • 36. ValueObject Buildercase class PersonName(firstName: String, lastName:String)class PersonNameBuilder extendsValueObjectBuidler[PersonName, PersonNameBuilder] { ... } VOval personName1 = newPersonNameBuilder().withFirstName("Junichi").withLastName("Kato").buildval personName2 = newPersonNameBuilder().withLastName(lastName.toUpperCase).build(personName1)
  • 37. Serviceobject TransferSer vice { def transfer(money: Money,from:BankAccount, to:BankAccount) = to.push(from.pull(money))}TransferService.transfer(Money(1000,JPY),BankAccount(“012345”),BankAccount(“543210”))
  • 38. Aggregateclass Employee(val id:String, var name:String, var dept:Department) extends Entity Employee id, name, dept
  • 39. Aggregate Department ( )class Employee(val id:String, var name:String, var dept:Department) extends Entityclass Department(val id: String, var name: String) extends Entityval dept = Department(“1:1”, “DEV)val emp = Employee(“1”, “KATO”, dept) / (1) /dept.name = “SALES” / (1) /val dept = emp.dept / (2) /dept.name = “SALES” / (2) /
  • 40. Aggregate(Cloneable)@cloneable }class Department(val id: String, var name: override def clone = { / (4) /String) extends Entity { val result = override def clone = super.clone.asInstanceOf[Employee]super.clone.asInstanceOf[Department] result.dpt = dpt.clone} }@cloneable }class Employee(val id:String, var val dept = Department(“1:1”, “DEV”)name:String, _dpt: Department) extendsEntity { val emp = Employee(“1”, “KATO”, dept) / (1) / private var dpt = _dpt.clone / (1) / dept.name = “SALES” def dept = dpt.clone / getDept / (2) val dept = emp.dept / (2) / def dept_= (value: Deaprtment) { // dept.name = “SALES”setDept emp.dept = Department(“1:1”, “SALES”) / (3) / dpt = value.clone / (3) / val cloneEmp = emp.clone / (4) /
  • 41. Aggregate(clone)clone Mix-in@cloneabletrait EntityCloneable[T <: Entity] { this: Entity => override def clone: T = super.clone.asInstanceOf[T]}
  • 42. Aggregate(clone)class Employee (val id: String, var name: String) extends Entity withEntityCloneable[Employee]val emp = Employee(“1”, “Kato”)val cloneEmp = emp.clone
  • 43. Aggregate(Factory) Factoryobject Department { def apply(dept: Department) = new Department(dept, dept.name)}object Employee { def apply(emp: Employee) = new Employee(emp.id, emp.name, Department(emp.dept))}val emp = Employee(“1”, “KATO”, Department(“1:1”, “DEV”))val cloneEmp = Employee(emp)
  • 44. Repositorytrait EntityResolver[T <: Entity] extends Iterable[T]{ def resolve(id: String): T def apply(id: String) = resolve(id) def contains(id: String): Boolean = exists(_.id == id) def contains(entity: T): Boolean = exists(_ == entity)}
  • 45. Repositoryclass EmployeeResolver extends val er = new EmployeeResolverEntityResolver[Employee] { val employee = er.resolve(id) private val employees = val employee = er(id) / er.apply /Map(“1” -> Employee(“1”, -> resolve“KATO”, Department(“1:1”,“DEV”))) val employee = er.contains(id) def resolve(id: String) = val exists = er.exists(_.name ==employees(id) “KATO”) def iterator = val employees =employees.map(e => er.filter(_.name.startWith(“K”))e._2.clone).iterator er.foreach(println)}
  • 46. Repositorytrait Repository[T <: Entity] extendsEntityResolver[T] { def store(entity: T) def update(identifier: Identifier, entity:T) =store(entity) def delete(identity: Identifier) def delete(entity: T)}
  • 47. Repositoryclass EmployeeRepository / resolve, iterator ... /extendsRepository[Employee] { } private val employees = val er = newcollection.mutalble.Map.emp EmployeeRepositoryty[String, Employee] val emp = Employee(id, def store(emp: Employee) = “KATO”, Department(“DEV))employees += (emp.id -> emp) er.store(emp) def delete(id: String) = er(id) = empemployee -= emp.id er.delete(id) def delete(emp: Employee) =delete(emp.id) er.delete(emp)
  • 48. ( )=id(4a74c322-08ab-450b-b674-793e1d7f399a) = classDepartment { package = dept fields { name = java.lang.String }}
  • 49. package dept;public class Department { private java.lang.String name; public void setName(java.lang.String name){ this.name = name; } public java.lang.String getName(){ return name; }}
  • 50. (.ftl)<#if classMeta.getPackageName()??> <#assign getter = "is"/>package ${classMeta.getPackageName()}; <#elseif f.getTypeName() == "java.lang.Boolean"></#if> <#assign getter = "is"/>public class ${classMeta.getName()} { <#else><#list classMeta.getFieldMetas() as f> <#assign getter = "get"/> private ${f.getTypeName()} ${f.getName()}; </#if> public void set${f.getName()?cap_first} public ${f.getTypeName()} ${getter}$(${f.getTypeName()} ${f.getName()}){ {f.getName()?cap_first}(){ this.${f.getName()} = $ return ${f.getName()};{f.getName()}; } } </#list> }<#if f.getTypeName() == "boolean">
  • 51. codegen -hcodegen -c sample.config -t template -e exportcodegen -c [4a74c322-08ab-450b-b674-793e1d7f399a]@sample.config -ttemplate -e export
  • 52. ClassMeta FieldMeta CodeGenSer viceFactory ClassMetaRepository
  • 53. ClassMeta Entitypackage codegen.domainclass ClassMeta(@BeanProperty val identifier: Identifier, @BeanProperty val name:String, val packageName: Option[String], val fieldMetas: List[FieldMeta])extends Entity { override def toString: String = "ClassMeta(%s, %s, %s,%s)".format(identifier, name, packageName, fieldMetas) def getPackageName(): String = if (packageName.isEmpty) null elsepackageName.get def getFieldMetas(): java.util.List[FieldMeta] = fieldMetas.asJava}
  • 54. ClassMeta Objectobject ClassMeta { def apply(identifier: Identifier, name: String, packageName: Option[String],fieldMetas: List[FieldMeta]) = new ClassMeta(identifier, name, packageName, fieldMetas)def apply(name: String, packageName: Option[String], fieldMetas:List[FieldMeta]): ClassMeta = apply(Identifier(), name, packageName, fieldMetas) def apply(classMeta: ClassMeta): ClassMeta = apply(classMeta.identifier, classMeta.name, classMeta.packageName,classMeta.fieldMetas) def unapply(classMeta: ClassMeta) = Some(classMeta.identifier, classMeta.name, classMeta.packageName,classMeta.fieldMetas)}
  • 55. FieldMeta VO & VO Factorycase class FieldMeta(@BeanProperty name:String, @BeanProperty typeName: String)
  • 56. ClassMetaRepositorypackage codegen.domainclass ClassMetaRepository(configSource: BufferedSource)extends EntityResolver[ClassMeta] { private val classMetas = newModelParser().parse(configSource) private val classMetaMap = classMetas.map(classMeta =>(classMeta.identifier, classMeta)).toMap def iterator: Iterator[ClassMeta] =classMetaMap.map(classMeta => ClassMeta(_._2)).iterator def resolve(identifier: Identifier): ClassMeta =ClassMeta(classMetaMap(identifier))}
  • 57. CodeGenSer vicepackage codegen.domain val exportClassDir = getExportClassDir(classMeta)object CodeGenSer vice{ exportClassDir.mkdirs def generate(exportDir: File, templateDir: File,classMetas: List[ClassMeta], using(new FileWriter(new File(exportClassDir, classMeta.name + ".java"))) { beginHandler: Option[(ClassMeta) => Unit], fileWriter => endHandler: Option[(ClassMeta) => Unit] ) = { template.process(rootMap.asJava, val configuration = new Configuration fileWriter); fileWriter.flush();configuration.setDirectoryForTemplateLoading(templateDir) } / using / val template = / ... /configuration.getTemplate("java.ftl") } / foreach / classMetas.foreach { } classMeta => / ... / / ... / } val rootMap = Map("classMeta" ->classMeta)
  • 58. Applicationpackage codegen.application getExportDir(parameters),object Application extends Logging { getIdList(parameters)) def main(args: Array[String]) { } try { val commandLine = new } catch {CommandLineParser().parse(args.mkString(" ")) case e: CommandLineParseException => commandLine match { println(" case Help() => println("""-c ")[[id1,id2]@]file.config [-t templateDir][-e exportDir]""") } case parameters: Parameters => }generate(getConfigFile(parameters), getTemplateDir(parameters), }
  • 59. Application private def generate(configFile: File, CodeGenSer vice.generate(exportDir, templateDir, targets, templateDir: File, Some({ exportDir: File, c => info("id(%s) : class %s ids: List[String]) { ".format(c.identifier.value, c.name)) info("= %s, = %s, }), = %s".format(configFile, templateDir, Some({exportDir)) c => info("id(%s) : class %s val repos = new ".format(c.identifier.value, c.name))ClassMetaRepository(Source.fromFile(configFile)) })) val targets = ids match { info(" ") case Nil => repos.toList case xs => xs.map { } catch { e => repos.resolve(Identifier(e)) case e: Exception => error(" } ", e) } } try { }