2011年12月23日金曜日

ScalazでBean Validation

このエントリはScala Advent Calender 2011の第23日目です。

Java業界で今年最大のニュースといえば、Oracle Open Worldで発表されたOracleのクラウド参入を上げる人も多いでしょう。OracleのPaaSが成功するのかどうかは未知数ですが、Java EEがPaaSのプラットフォームとして強力に推進されることは確実で、Java VM系での標準コンテナ仕様になるとみて間違いないと思います。

現状では、WebアプリケーションをJavaで作る場合TomcatやJettyなどの生ServletにSpringなどのフレームワークを載せて使うのが一般的で、Java EEのEJBはあまり使われていません。Java EEからEJBを引くと生Servletになるのであれば、Java EE仕様はあまり関係なくて、今まで通りTomcatやJettyベースでよいことになります。

しかし、実はJava EEのプラットフォームも地味に進歩してきていて、Java EEからEJB(のフルスペックやその他管理系の機能)を引いた部分の機能がかなり大きくなってきており、生Servletとの乖離が大きくなっています。この部分は、Java EEのWeb Profileとして仕様化もされています。Java EEのフルスペックは大変ですが、Web Profileなら使用頻度とのバランス的にリーズナブルな大きさであり、このWeb ProfileがJava VM系のPaaS標準仕様になると予想されるわけです。

ScalaもJava VM上で動作させることが普通なので、Javaプラットフォームの進化とは切っても切れない関係です。そういうわけでScalaでクラウドアプリケーションを書く場合も、Java EEのWeb Profileを念頭に置いておきたいところです。

Web Profileではいくつか重要な機能が追加されていますが、その一つがBean Validationです。Bean Validationは、Java Beansのプロパティを検証する機能です。プロパティ(属性やメソッド)に対するアノテーションで指定された値の値域と実際に格納されている値がマッチしているか検証してくれます。この機能は、普通のJava SEベースのプログラムでも利用したいぐらいの便利な機能で、たまたまJava EEの枠組みで仕様化されていますが、事実上Javaの標準機能といえます。(Jarを追加すればJava SE上でも簡単に使えます。) そういう意味でもScalaプログラミングにも積極的に取り入れたいところです。

さて、ScalaでValidationといえばScalaz Validationですね(笑)。

Scalaz Validationは、applicative functorとして実現されたValidationクラスを中心とした機能で、いわゆるapplicative styleというプログラミングスタイルで、正常系処理と異常系処理を綺麗に取り扱うメカニズムを提供します。

Scalaでクラウドアプリケーションを作る場合、Beans ValidationとScalaz Validationを併用するのが望ましいことはいうまでもありません。そこで、この2つのValidationシステムを統一的に扱うためのプログラミング方法を試行することにしました。

Person

まず、検証対象となるクラスをPersonを定義します。通常のScalaクラスですが、属性に@NotNullや@Size(min=1)といったアノテーションがつけられている点がBean Validationのための追加点です。

class Person(nm: String, ag: Int, ad: Option[String]) {
  @NotNull @Size(min=1)
  val name: String = nm
  @Min(0) @Max(150)
  val age: Int = ag;
  @NotNull // Option内の判定はできない
  val address: Option[String] = ad
}

本来は、case classを使いたいところですが、コンストラクタの引数につけたアノテーションをBean Validationは認識してくれないみたいなので、泣く泣くこの実装にしています。これは、ScalaからBean Validationを使うときの要注意項目ですね。

また、Optionの中身はBean Validationの基本機能では扱えないので、より本格的に作り込む場合は、カスタムのバリデーターを作る必要があります。

Personのコンパニオンオブジェクト

次に、Personクラスを操作するための関数を集めたコンパニオンオブジェクトPersonを定義します。ここで、Bean ValidationとScalaz Validationを接続する処理を実現します。

以下では、プログラムに直接コメントしていきます。

object Person {
  // Scalaでは、型名が長くなることが多いので、よく使うものはtypeで定義しておくとよい。
  type ViolationNelA[A] = NonEmptyList[ConstraintViolation[A]]
  type ViolationNel = ViolationNelA[Person]
  type ValidationB[B] = Validation[ViolationNel, B]

  // javax.validationパッケージからBeanValidationのValidatorを取得。
  val validator = {
    // Validationがscalaz.Validationと重なるので、BeanValidationという名前で取り込む。
    import javax.validation.{Validation => BeanValidation}
    val vf = BeanValidation.buildDefaultValidatorFactory()
    vf.getValidator()
  }

  def isValid(p: Person) = validate(p).isEmpty

  // Bean Validatorを使って、Personオブジェクトの検証を行う。
  // asScala.toListでScalaのListに格納する。
  // ConstraintViolationはBean Validatorが検出した異常情報。
  def validate(p: Person): List[ConstraintViolation[Person]] = {
    validator.validate(p).asScala.toList
  }

  // 指定された値からPersonオブジェクトを生成する。成功した場合はscalaz.SuccessにPersonオブジェクトを、失敗した場合はscalaz.FailureにConstraintViolationのリストを格納する。
  // scalaz.Successとscalaz.FailureはScalaz.Validationのサブクラスでそれぞれ検証の成功と失敗を示す。
  // この関数でBean Validationの結果をScalaz Validation化している。
  def createV(name: String, age: Int, address: Option[String]): ValidationB[Person] = {
    val p = new Person(name, age, address)
    validate(p) match {
      case vs if vs.isEmpty => p.success
      // vs.toNel.get.failといったものがScalaz的な書き方。コンパクトに記述できる。
      case vs => vs.toNel.get.fail
    }
  }

  // Bean Validationは、Java Beanに値を設定した後にしか使えない。
  // Webアプリなどで、生文字列からJava Beansを生成する場合、文字列が適切な値に変換できないため、そもそもJava Beansを生成できないケースもある。そのケースを取り扱うため、文字列からの値変換に失敗した場合はその時点でエラー、Java Beansを生成後はBean Validatorで検証し、いずれの場合も結果はscalaz.Validationで通知する。
  def createVFromStrings(name: String, age: String, address: String): Validation[NonEmptyList[String], Person] = {
    // 型名が長くなりがちなので、内部関数やvalで吸収する。
    // 名前はコメントをつける気持ちでつけるとよい。
    def parseint(s: String, name: String) = s.parseInt match {
      case Success(a) => a.success[NonEmptyList[String]]
      case Failure(e) => violationmsg(name, s, e.getMessage).fail.liftFailNel
    }
    def parsestring(a: String, name: String) = a.success[String].liftFailNel
    def parseoption[T](a: Option[T], name: String) = a.success[String].liftFailNel
    def constraintviolation2string(cv: ConstraintViolation[Person]) = {
      violationmsg(cv.getPropertyPath.toString, cv.getInvalidValue.toString, cv.getMessage)
    }
    def violationmsg(path: String, value: String, msg: String) = {
      "%s = %s: %s".format(path, value, msg)
    }
    // ロジックの中心は内部関数を使ってコンパクトに書く
    (parsestring(name, "name") |@| parseint(age, "age") |@| parseoption(Option(address), "address"))(createV(_, _, _)) match {
      case Success(Success(a)) => a.success[String].liftFailNel
      case Success(Failure(e)) => e.map(constraintviolation2string(_)).fail
      case Failure(e) => e.fail[Person]
    }
  }

  // List[ValidationB[Person]]をValidationB[List[Person]]に変換。型クラスTraverseと似たような動き。
  // TraverseでValidationをうまく扱うことができなかったので、foldrで実装してみた。
  // 「(s <**> p)(_ :: _)」の所がapplicative styleのプログラミング。ValidationがSuccessの場合に走るロジック(正常処理)を記述する。ValidationがFailureの場合(異常処理)は、applicative functorであるValidationのコンテナ側が実装している裏ロジック(?)が走って、monoidとして実現されているエラー情報を蓄積していく。
// applicative functorのメカニズムを用いることでプログラマが記述する正常系ロジックとValidationが自動的に実行する異常系ロジックを綺麗に分離できる。
  def sequenceV(persons: List[ValidationB[Person]]): ValidationB[List[Person]] = {
    persons.foldr(mzero[List[Person]].success[ViolationNel])((s, p) => (s <**> p)(_ :: _))
  }
}

applicative functorはfunctorとmonadの中間に位置する型クラスです。それぞれ計算の文脈(コンテナ)の扱いに違いが出てきます。functorはピュアなアプリケーションロジック実行後ピュアな文脈(コンテナ)を生成、monadはアプリケーションロジックが文脈(コンテナ)を操作するのに対して、applicative functorはピュアなアプリケーションロジックの裏で暗黙的に文脈(コンテナ)が引き継がれていきます。このapplicative functorの有名な応用がValidationで、実際に触ってみるとピュアなアプリケーションロジックをベースに文脈(コンテナ)依存の処理を進められるapplicative styleのプログラミングがなかなか便利なことが分かります。

ValidationMatchers

Bean ValidationをScalaプログラムで扱うときは、ScalaTestのボキャブラリとなるカスタムマッチャーを作っておくと便利です。Bean ValidationとScalaz Validationを併用するので、両方の機能を包含したValidationMatchersを定義することにします。

package advent2011

import org.scalatest.matchers._
import javax.validation.ConstraintViolation
import scalaz._
import Scalaz._

// アプリケーションロジックのためのボキャブラリの追加はtraitの典型的な使い方の一つ。
trait ValidationMatchers {
  // Bean Validation用のボキャブラリ
  def containViolations(violations: List[(String, String)]) = {
    ContainViolationsMatcher(violations)
  }

  // Scalaz Validation用のボキャブラリ
  object success extends ValidationSuccessMatcher
  def fail(messages: List[String]) = {
    ValidationFailMatcher(messages)
  }
}

// Bean Validation用のマッチャー。
// BeMatcherやMatcherをextendsして、applyメソッドを定義するだけなので非常に簡単。
// テスト用のボキャブラリを簡単に追加できる。
case class ContainViolationsMatcher(violations: List[(String, String)]) extends BeMatcher[List[ConstraintViolation[_]]] {
  def apply(value: List[ConstraintViolation[_]]) = {
    def iscontain(nm: (String, String)) = {
      val (name, message) = nm
      value.any(v => v.getPropertyPath.toString == name && v.getMessage == message)
    }
    // allはscalazの型クラスFoldableの関数。関数名に∀の記号を使うこともできる。
    val result = violations.all(iscontain)
    MatchResult(result, "does not contains expected violation", "contains expected violation")
  }
}

// Scalaz Validation用のマッチャー。Successの判定をする。
case class ValidationSuccessMatcher() extends Matcher[Validation[NonEmptyList[String], _]] {
  def apply(value: Validation[NonEmptyList[String], _]) = {
    val result = value.isSuccess
    MatchResult(result, "failure", "success")
  }
}

// Scalaz Validation用のマッチャー。Failureの判定をする。
case class ValidationFailMatcher(messages: List[String]) extends Matcher[Validation[NonEmptyList[String], _]] {
  def apply(value: Validation[NonEmptyList[String], _]) = {
    value match {
      case Success(a) => MatchResult(false, "incorrect success", "")
      case Failure(e) => if (e.all(messages.contains)) {
        MatchResult(true, "", "correct failure")
      } else {
        MatchResult(false, "incorrect failure", "")
      }
    }
  }
}

PersonSpec

最後に、Personの使い方をScalatestのWordSpecで書いてみました。WordSpecはBDD(Behavior Driven Development)スタイルのSpecを記述するためのクラスです。Scalatest標準のShouldMatchersに加えて、先ほど作成したValidationMatchersのボキャブラリを追加しています。
ScalatestのBDDは、テスト用のアプリケーションロジックと結果判定がコーディングスタイル上明確に分離できるので、プログラムの視認性が高くなります。また、カスタムマッチャーを作り足すことで、より英文っぽい記述が可能になるので、そのあたりの遊び的な要素がプログラミングを進める上で良い感じです。

class PersonSpec extends WordSpec with ShouldMatchers with ValidationMatchers {
  "A Person" should {
    "provide isValid and validate operation" that {
      "against valid Person" in {
        val p = new Person("taro", 30, "Yokohama".some)
        Person.isValid(p) should be (true)
        Person.validate(p) should be === Nil
      }
      "against invalid Person" in {
        val p = new Person("", -150, null)
        val expected = List("address" -> "may not be null",
                            "name" -> "size must be between 1 and 2147483647",
                            "age" -> "must be greater than or equal to 0")
        Person.isValid(p) should be (false)
        Person.validate(p) should have length (expected.size)
        // 追加したボキャブラリを使用
        Person.validate(p) should be (containViolations(expected))
      }
    }
    "provide createV to create Person with Validation." that {
      // アプリケーションロジックを普通のScalaプログラム的に書いた場合。
      "Plain usage." in {
        val taro = Person.createV("taro", 30, Some("Yokohama"))
        val hanako = Person.createV("hanako", 25, Some("Kamakura"))
        if (taro.isSuccess && hanako.isSuccess) {
          val tage = taro match {
            case Success(p) => p.age
          }
          val hage = hanako match {
            case Success(p) => p.age
          }
          val avg = (tage + hage) / 2.0
          avg should be (27.5)
        } else {
          sys.error("invalid")
        }
      }
      // アプリケーションロジックをScalazのapplicative styleで書いた場合。
      // よりコンパクトで分かりやすく記述できる。
      "Scalaz usage, applicative style." in {
        val taro = Person.createV("taro", 30, Some("Yokohama"))
        val hanako = Person.createV("hanako", 25, Some("Kamakura"))
        val avgv = (taro <**> hanako)((x, y) => (x.age + y.age) / 2.0)
        // この段階までValidationの文脈(コンテナ)の上で計算が進んでいる。
        // 以下のmatch式で、アプリケーションロジックが正常に動作した場合と、エラーがある場合を分離して、それぞれのロジックを記述している。
        avgv match {
          case Success(avg) => avg should be (27.5)
          case Failure(e) => sys.error("invalid")
        }
      }
    }
    "provide createV and sequenceV for applicative style." that {
     // ValidationのListを扱う場合2例。いずれもapplicative style。
      "Use sequenceV to convert List[Validation[Person]] to Validation[List[Person]] " in {
        val taro = Person.createV("taro", 30, Some("Yokohama"))
        val hanako = Person.createV("hanako", 25, Some("Kamakura"))
        val jiro = Person.createV("jiro", 35, Some("Tokyo"))
        val persons = List(taro, hanako, jiro)
        val personsv = Person.sequenceV(persons)
        val avgv = personsv.map(x => x.map(_.age).sum.toFloat / x.length)
        avgv match {
          case Success(avg) => avg should be (30.0)
          case Failure(errors) => sys.error("invalid")
        }
      }
      "Use foldl to sum of age" in {
        val taro = Person.createV("taro", 30, Some("Yokohama"))
        val hanako = Person.createV("hanako", 25, Some("Kamakura"))
        val jiro = Person.createV("jiro", 35, Some("Tokyo"))
        val persons = List(taro, hanako, jiro)
        val sumv = persons.foldl(0.success[Person.ViolationNel])((s, p) => (s <**> p)(_ + _.age))
        val avgv = sumv.map(_.toFloat / persons.length)
        avgv match {
          case Success(avg) => avg should be (30.0)
          case Failure(errors) => sys.error("invalid")
        }
      }
    }
    "provide createVFromStrings to create Person from plain strings." that {
      "Valid parameters." in {
        val person = Person.createVFromStrings("taro", "30", "Yokohama")
        // 追加したボキャブラリを使用
        person should success
      }
      "Invalid parameters of type mismatch." in {
        val person = Person.createVFromStrings("", "a", "Yokohama")
        val expected = List("""age = a: For input string: "a"""")
        // 追加したボキャブラリを使用
        person should fail(expected)
      }
      "Invalid parameters of invalid value." in {
        val person = Person.createVFromStrings("", "30", "Yokohama")
        val expected = List("""name = : size must be between 1 and 2147483647""")
        // 追加したボキャブラリを使用
        person should fail(expected)
      }
    }
  }
}
実行結果は以下のようになります。トップレベル, that, inの三層でテストを整理できるのがなかなか便利です。
[info] PersonSpec:
[info] A Person 
[info]   should provide isValid and validate operation that 
[info]   - against valid Person
[info]   - against invalid Person
[info]   should provide createV to create Person with Validation. that 
[info]   - Plain usage.
[info]   - Scalaz usage, applicative style.
[info]   should provide createV and sequenceV for applicative style. that 
[info]   - Use sequenceV to convert List[Validation[Person]] to Validation[List[Person]] 
[info]   - Use foldl to sum of age
[info]   should provide createVFromStrings to create Person from plain strings. that 
[info]   - Valid parameters.
[info]   - Invalid parameters of type mismatch.
[info]   - Invalid parameters of invalid value.
[info] Passed: : Total 9, Failed 0, Errors 0, Passed 9, Skipped 0
[success] Total time: 2 s, completed 2011/12/23 11:54:09

まとめ

Bean ValidationとScalaz Validationを併用する方法について試行してみました。
Bean Validation用のアノテーションとScalaの相性に若干問題があるようですが、プログラミング的には特に問題なくシームレスに繋げることが確認できました。

また、Scalaz Validationの実現技術であるapplicative functorによるapplicative styleによるプログラミング、ScalatestによるBDDという技術も合わせて使ってみました。いずれもJavaでは実用化が難しい技術で、Scalaを使うメリットですね。

今回使用した技術は以下のものになります。合わせてプログラミングしてみてJava EE web profile技術、Scalaz、ScalaTestによるTDD/BDDといったところがScalaプログラミングのベースになりそう、という感を強くしました。

  • Bean Validation
  • Scalaz Validation
  • Scalaz applicative functor (applicative style)
  • Scalatest BDD
  • Scalatest カスタムマッチャー

2011年12月14日水曜日

ImmutableとBuilder

このエントリはJava Advent Calender 2011の第14日目です。

OOPで最も重要なテクニックは何でしょうか。色々な考え方があるでしょうが、ボクはImmutable Objectがまず最初に浮かびます。
かなり前にJava World誌に書いていたJavaデザインノートという連載でも、第1回のテーマはImmutable Objectでした。

Immutable Object(不変オブジェクト)は、生成時に属性を設定した後、属性値を変更することができないオブジェクトです。Javaの場合、StringやDateなどの情報を保持する基本クラスの多くがImmutable Objectになっており、Javaプログラミングを下支えしています。

Immutable Objectの長所は、とにかく属性値が変更されないこと。一度生成したオブジェクトは何の心配もなく、メソッドの引数で渡すことができます。その先で、どのような共有のされかたをしても、知らないところで勝手に内容が変更されるということがありません。マルチスレッドプログラミングでは、この性質はさらに重要になります。

一方、Immutable Objectは以下の短所があります。

  1. 値を変更するためには、オブジェクトをまるごと複写する必要がある。
  2. すべての値の設定をオブジェクト生成時に行う必要があり、プログラミングがちょっと煩雑。

前者の問題は、最近のハードウェア性能の向上で、普通のWebアプリ、業務アプリではまず問題にはならないでしょう。まるごと複写といってもshallow copyとなるので、まるまるデッドコピーとはなるわけではありません。 また、多少の性能劣化があっても、プログラム品質、開発効率の向上の方がより重要というケースは多いはずです。 よほど性能要件の高い場合でも適材適所で使い分ければよいわけです。

そういう意味で、Value Objectは当然として、DTOといった情報量の多いオブジェクトもできるだけImmutable Object化していきたいところです。

となると、現在問題となるのは後者。まずオブジェクト生成時のコンストラクタで全情報を引数で渡さなければなりません。さらに、オブジェクトの変更(つまりデータを変更して複写)では、オブジェクトのほとんどの情報と変更したいデータを用意して、コンストラクタに渡すという手間が必要になります。 StringやDateのようなValue Objectでは、オブジェクトが管理する情報が1つやせいぜい2つなので全く問題ありませんが、DTO等の場合は管理する情報が数十個になるケースも考えられ、かなり煩雑になります。数十個と言わずとも5個ぐらいでも十分嫌になります。実際のプログラミングではこの手間が嫌で、Immutableの方がよいと分かっていても普通のMutable Objectを選択することになりがちです。これを何とかしたいですね。

この問題を解決するのがBuilderです。

プログラムはこんな感じ。

Immutable Object「Person」と、そのBuilderである「Person.Builder」を定義しています。 Personは、Immutable Objectであることを活用して属性をpublicにして外から扱いやすくしています。 Person.Builderの方は、普通のMutableオブジェクトで、値を設定しやすくするための工夫を好きなだけ行うことができます。ここではwithXXXメソッドでメソッドチェインを実現してみました。

package advent2011;

import java.util.List;
import java.util.ArrayList;
import static java.util.Collections.unmodifiableList;

public class Person {
    public final String name;
    public final int age;
    public final String address;
    public final List<String> phones;

    public Person(String name, int age, String address,
                  List<String> phones) {
        this.name = name;
        this.age = age;
        this.address = address;
        this.phones = unmodifiableList(new ArrayList<String>(phones));
    }

    public static class Builder {
        public String name;
        public int age;
        public String address;
        public ArrayList<String> phones = new ArrayList<String>();

        public Builder() {
        }

        public Person build() {
            return new Person(name, age, address, phones);
        }

        public Builder withName(String name) {
            this.name = name;
            return this;
        }

        public Builder withAge(int age) {
            this.age = age;
            return this;
        }

        public Builder withAddress(String address) {
            this.address = address;
            return this;
        }

        public Builder withPhone(String phone) {
            this.phones.add(phone);
            return this;
        }
    }
}

こんな感じで使います。

package advent2011;

import org.junit.Test;
import static org.hamcrest.core.Is.is;
import static org.junit.Assert.*;

public class PersonTest {
    @Test
    public void buildPerson() {
        Person.Builder builder = new Person.Builder();
        builder.withName("Taro")
            .withAge(30)
            .withAddress("Yokohama")
            .withPhone("045-123-4567");
        Person person = builder.build();
        assertThat(person.name, is("Taro"));
        assertThat(person.age, is(30));
        assertThat(person.address, is("Yokohama"));
        assertThat(person.phones.get(0), is("045-123-4567"));
        System.out.println(person);
    }
}

Personオブジェクトを生成す時の手間は、普通のMutableオブジェクトの時と同じ。 buildメソッドで返ってくるPersonオブジェクトはImmutableオブジェクトなので、安全に使うことができます。 どちらの目的も叶える解ですね!

新たな問題

さて、Builderを使うとImmutableオブジェクトが使いやすくなるのは分かったのですが、新たに別の問題が発生します。

このBuilderを用意する手間がなかなか大変なんです。コーディング量も多いですし、コーディング内容が単純作業のためが短調で面白くない、という問題もあります。 この手間があるので、このままでは結局使わないテクニックになってしまいそうです。

単調なコーディングあるところ、自動生成あり。ここで登場するのが自動生成です。クラスの形が分かっていれば、その情報からJavaプログラムのソースコードを生成すればよることが可能です。

SimpleModelerService

現在開発中のSimpleModelerServiceでは、この問題に対応するためにDSLからJavaプログラムを自動生成する機能を提供する予定です。DSLにはScala DSLに加えて、CSV、マインドマップ(XMind)、Excelなどが利用できるようになる予定です。 今回は、CSVを使って試してみましょう。

以下の内容のCSVファイルを用意します。

person,name;age(int);address?;phone+

CSVの第1カラムにクラス名、第2カラムに属性のリストを定義しています。「(int)」はint型、「?」「+」は多重度(?は0または1、+は1以上)を意味しています。

これを以下のフォームに設定して「生成」すると、色々なコードが生成されたものをzipで固めたものが返ってきます。

この中にある、DDPerson.javaに今回のテーマであるImmutableオブジェクトとBuilderが記述されています。ぜひ覗いてみてください。

ファイル名:

ImmutableオブジェクトとそのBuilderの作成は、手間がかかるのでつい手抜きをしたくなりますが、プログラムの自動生成を活用すれば楽々クリアできますね。
SimpleModelerのJava生成機能は、近々公開したいと思いますので、ぜひご利用ください。

2011年12月7日水曜日

SimpleModelerServiceのアーキテクチャ

昨日公開したXMind→クラス図変換サービスは、g3上に構築したSimpleModelerServiceというRESTサービスです。
このRESTサービスはScalaでプログラミングし、WAR形式にパッケージングしたものをGlassFish上で動作させています。
Java系のPaaS標準コンテナは、WAR形式をJavaEEのWebプロファイル+αを動作させるものになると予想されるので、この環境をScala&自作フレームワークで試してみるというという目的もありました。

SimpleModelerServiceは以下の4つのモジュールから構成されています。

Goldenport
アプリケーションフレームワーク
g3
クラウドアプリケーションフレームワーク
SimpleModeler
モデルコンパイラ
SimpleModelerService
SimpleModelerサービス

SimpleModelerServiceが最上位にあるクラウドアプリケーションの本体で、g3上に構築されています。SimpleModelerServiceはgoldenport上に構築されたスタンドアロンアプリケーションであるSimpleModelerをRESTサービス化します。

SimpleModelerServiceの開発規模は以下の表になります。Scalaが約121.6Ks、Javaが約10.8Ksです。Scalaのコーディング量が100Kステップを超えてきておりちょっと感慨深いものがあります。

開発規模
モジュールScala(Ks)Java(Ks)
Goldenport16.89.1
g322.61.7
SimpleModeler82.1N/A
SimpleModelerService0.1N/A

SimpleModelerService on g3

SimpleModelerServiceでは、クラウドアプリケーション用フレームワークとして開発してきたg3を実応用に初めて適用することができました。

g3はScala DSLでアプリケーションを記述しますが、SimpleModelerServiceは以下のものになります。

class SimpleModelerService extends G3Application {
  title = "SimpleModeler"
  summary = 
SimpleModeler service produces various artifacts from a SimpleModeling model.
port("/diagram", Description( "Diagram", "SimpleModeler Diagram Service",
SimpleModeler diagram service produces a class diagram from a mindmap modeled by MindmapModeling.
, Schema( (Symbol("source.package"), XString, MZeroOne), ('_1, XBase64Binary))) ) agentpf { case p: Post => Post("diagram", p) } invoke('sm) goldenport('sm, SimpleModelerDescriptor) }

port("/diagram")はHTTPリクエストを受け取るポートを指定しています。
URIの断片"/diagram"にマッチしたHTTPリクエストがこのポートで受信され、パイプラインで以下の処理が実行されていきます。

  1. agentpfでPostリクエストを加工するPartialFunctionを実行。
  2. invokeでチャネルsmを呼出し。

その下にあるgoldenport('sm, SimpleModelerDescriptor)は、チャネルsmをGoldenportアプリケーションSimpleModelerに割り当てる設定です。SimpleModelerのGoldenport DSLであるSimpleModelerDescriptorを指定しています。

port("/diagram")からinvokeでこのsmチャネル経由でSimpleModelerが実行され、その実行結果がport("/diagram")の実行結果としてクライアントに返されます。

HTTPのプロトコル処理、FormやJSON、AtomPub, MIMEといったデータ入出力、ファイルのアップロード処理はg3フレームワークが行います。アプリケーションは、GetやPostといったメッセージに対する関数型的な転換処理の連鎖として、アプリケーションロジックを記述することができます。また、Goldenport上に構築したスタンドアロンアプリケーションは、シームレスに接続できるようになっており、ほぼそのままRESTサービス化することができます。

SimpleModeler on Goldenport

モデルコンパイラはアプリケーションフレームワークGoldenport上に構築しています。そのアーキテクチャを定義するDSLが以下のSimpleModelerDescriptorです。

class SimpleModelerDescriptor extends GApplicationDescriptor {
  name = "SimpleModeler"
  version = "0.3.0"
  version_build = "20111206"
  copyright_years = "2008-2011"
  copyright_owner = "ASAMI, Tomoharu"
  command_name = "sm"
  //
  classpath("target/classes")
  importers(ScalaDslImporter)
  entities(CsvEntity, XMindEntity, OpmlEntity, ExcelTableEntity, 
      OrgmodeEntity, YamlEntity)
  services(ProjectRealmGeneratorService,
    ImportService,
    ConvertService,
    HtmlRealmGeneratorService,
//    JavaRealmGeneratorService,
    GrailsRealmGeneratorService,
    GaeRealmGeneratorService,
    GaeoRealmGeneratorService,
    GaeJavaRealmGeneratorService,
    AndroidGeneratorService,
    G3GeneratorService,
    AsakusaGeneratorService,
    DiagramGeneratorService)
}

SimpleModelerDescriptorでは概ね以下のような定義を行っています。

  • importersで外部データの移入器としてScalaDslImporterを指定。
  • entitiesでCsvEntityやXMindEntityなどを指定。サフィックスがcsvやxmlになっているファイルを入力すると、自動的にCSVEntityやXMindEntityに変換される。
  • servicesでDiagramGeneratorServiceやAndroidGeneratorServiceなどを指定。パラメタにより自動的に適合するサービスが起動される。

Goldenportが外部入出力のもろもろをハンドリングしてくれるので、アプリケーションロジックの記述に専念することができます。g3を併用することでRESTサービスまでシームレスに接続できるようになりました。

ScalaとDSL

DSLの用途は大きく(1)モデルなどの静的な情報を記述、(2)フレームワークのAPIを記述、の2つに分けられます。

SimpleModelerがモデル記述に使っているScala DSLは前者の例です。

一方、本記事で取り上げたSimpleModelerService(g3)、SimpleModeler(Goldenport)は後者の例です。SimplModeler(Goldenport)のDSLは、Javaでも何とか実現できそうですが、SimpleModelerServiceのDSLはJavaではこのような記述は難しく、DSLを得意とするScalaの美点がよく出ています。

Scalaは元々、JavaでのDSL実現に限界を感じていたため、DSL用の言語として採用したのですが、十分満足できる結果を得られました。Scalaで100K超のコーディングを行った事になりますが、Javaと比べてプログラミングが圧倒的に楽ということも体感できました。

Goldenportやg3のようなDSLベースのAPIを持つフレームワークやコンポーネントがこれからどんどん出てくることが予想されるので、Scalaプログラミングの生産性はますます向上してくることになるでしょう。また、メニーコア時代の並行プログラミングは関数型言語を中心に広がっていきそうです。今回確認できたようにGlassFishのようなJava用のクラウドコンテナ(の候補)にもまったくシームレスに載せることができるのは、Javaとの互換性を軸に据えているScalaならではです。色々考えていくとScalaはかなり便利な言語で、クラウドアプリケーション向けプログラミング言語の最右翼かなと実感しています。

2011年12月6日火曜日

XMindをクラス図に変換するクラウドサービス

XMind形式のマインドマップモデルからクラス図(PNG)を生成するクラウドサービスを作成しました。
次回の横浜モデリング勉強会(1月21日(土)予定)で使用する予定です。 作成中のマインドマップをクラス図に変換して確認することによって、マインドマップモデルとオブジェクトモデルの関係をより深く理解できるようになると思います。

ツールをインストールするのは何かと大変なので、開発中のツールはこういったクラウドサービスで提供していく予定です。

このツールは、CURLコマンドまたはWeb上に置いたガジェット(HTML断片)を使って使用することができます。

Bloggerで、このガジェットが動くのか試すために以下で配置してみました。もしかしたらここから動かすことができるかもしれません。

ファイル名:

クラウドサービスの配備には、さくらインターネットとGlassFishを使ってみました。
この組合せは、かなり簡単にScalaアプリケーションが動きますね。
どちらのUIもこなれていて作業のハードルが低いので、気軽に色々試してみたいと思います。

追記:Bloggerからもガジェット動きました。サービスはまだまだ柔らかいので、変なことをすると落ちるかもしれません。お手柔らかに。

2011年12月2日金曜日

要約エンティティ

きのう「Deep Hadoop Night~誰がHadoopを殺したか?~」を聞いてきました。タイトルからも分かるように刺激的な内容でしたがオフレコが趣旨とのことで内容への感想は差し控えますが、それはともかく、座談会のお話に色々なヒントが満載で、クラウドアプリケーションのモデリングを考える上でとても刺激になりました。

Hadoop的なサービスが普通のインフラとして整備された世界での、クラウドアプリケーションのモデリング手法について考察が進みました。今まで考えていたアイデアが少しまとまってきたので、つらつら、まとめておきたいと思います。

データモデル

以下の図は、ここ数年、クラウドアプリケーションのメタモデルの素案として使用しているものを今回のアイデアに基づき改良したものです。



基本的にはクラウド以前に使っていたメタモデルを自然に拡張したものになっています。概念モデルや分析モデルのあたりは、要するに利用者などのステークホルダー観点でのモデルを構築することになるので、クラウドプラットフォームだからといって、特別な手法を取り込むということにはならないと考えています。

もちろん、クラウドによって今まであったシステム構築上の制約が外れてくるので、そのあたりで新たに考慮に入れる必要がある要因は増えてきます。

このメタモデルは、2008年頃からクラウド向けに徐々に改良してきたもので、クラウド向けのモデル要素として以下のものを追加してきました。

  • アプリケーション利用モデルのUX(User Experience)
  • アプリケーション実現モデルのメッセージフロー
UX(User Experience)は、UCD(User Centered Design)によるUIモデリング技術の発展とGUI技術の向上を取り込む意図です。UXをシステムに落し込む時の技術としてユースケースを組み合わせることを考えています。ユースケースのカバー範囲が少し小さくなるとともに、ユースケースをシステム設計側のモデリング技術として再構築することを想定しています。
メッセージフローは、さまざまなシステム間でのメッセージ交換によって協調動作するクラウドアプリケーションのアーキテクチャを、制御フローとデータフローを統合した形で記述する目的のものです。ただし、システム設計レベルの詳細な記述力は持たないので、プログラムの自動生成といった用途には使用できません。
ここまでは、以前からあちこちでお話ししている内容なのですが、これに加えて、今回新たなモデル要素として「要約エンティティ」を追加することにしました。この「要約エンティティ」がクラウドアプリケーションのモデリングの要になるのでは、と思いついたわけです。

ドメインモデル

分析モデル、設計モデルの軸となるのがドメインモデルです。(以下、正確には振舞いを含んだオブジェクトモデルですが、イメージしづらいのでデータモデルとして考えていきます。)
ポイントとなるのは、一つのドメインモデルでUIから永続データまで直接カバーするのではなく、ドメインモデルの枠組みの中で、目的毎のデータモデルを構築して、これらのデータモデルを連携させて、全体として機能させていく必要があるのではないかという仮定です。
その目的のために、仮にドメインモデルを以下のような要素に分解して実現することを考えます。
  • メンタルデータモデル
  • アプリケーションデータモデル
  • 永続データモデル
  • 集約データモデル
UXの実現は、利用者の目的を達成するためという軸から導きだされるものなので、永続データモデルに引きづられすぎては本末転倒です。UX専用のメンタルデータモデルを構築します。
クラウドアプリケーションでは、複数のデータソースから得られた情報を取捨選択射影して、アプリケーション独自のデータモデルを構築します。アプリケーションの意図に沿っていること、色々なデータソースを融合させるための十分な記述力を持っていること、メンタルデータモデルへの写像が行えること、といった要件を満たす必要があります。

要約エンティティ

利用者側からは利用者中心のメンタルモデル、クラウドプラットフォームからは外部データを集約データモデル、さらにアプリケーションがやりたい事を実現するためのアプリケーションデータモデル。このようないくつかのモデルを連携させて仕事をさせるには、ハブとなるモデル要素を軸に据えるのが常道です。
このモデル要素として、以前からアイデアとして温めていた要約エンティティ(ステレオタイプはsummery)がぴったりハマるのではないかというのが、今回の気付きになります。
要約エンティティは、アプリケーションが必要なさまざまな情報を集計、集約したエンティティです。つまり、元ネタとなるファクトモデルのエンティティではなく、そこから二次的に派生したエンティティとなります。
ある意味では、データベースのビューと同じ目的のものですが、問合せをカプセル化したものではなく、アプリケーションの意志で情報を集積、計算、追加していく点が異なります。

事前計算

クラウドアプリケーションでは、利用者数、データ規模の双方に対するスケーラビリティが問題となるアプリケーションが当然増えてきます。
スマートデバイスからのアクセスでは、より高速なレスポンスが要求されます。
クラウドアプリケーションの場合は、アプリケーションの外部にあるさまざまなデータを取り込んで利用するユースケースも考えられます。この場合は、外部のアプリケーションへの通信が発生するため、相応の遅延が発生することになります。複数の外部アプリケーションと連携する場合はさらに大変なことになります。
いずれにしても、問合せを受けた契機で、アプリケーションロジックとして問合せ処理を行うのでは、実用的な性能がでない可能性が高いでしょう。
この問題へ対応するには、予測される問合せ処理を事前にバックグラウンドで行っておく必要があります。この事前計算の結果を格納する受け皿として要約エンティティが使用できます。
以下の図ではドメインイベントを契機に、ドメインデータの値と合わせて、何らかの要約処理を行い要約エンティティに格納しています。このように、利用者が問合せしたタイミングではなく、イベントが発生したタイミングで要約データを計算しておくことで、利用者からの問合せに対して高速にレスポンスを返すことができるようになります
要約エンティティを導入すると、各種データのスムーズな連携が可能になるのに加えて、問合せ結果を事前計算しておくことによって、問合せのレスポンス時間を向上させるという、クラウドアプリケーションで頻出するユースケースを自然に実現できるようになります。
要約データを用いて問合せの性能を上げる手法は以前からあるわけですが、クラウドアプリケーションの場合はより重要な手法になります。
要約データは、ある意味ではデータモデルの論理的な一貫性を弱くしてしまうデータを導入することになります。このため、今までは、要約データはできるだけ用いずトランザクションの枠組みの中でSQLによって一括に結合して取得するアプローチが好まれていました。
しかし、クラウドアプリケーションのスケーラビリティ要件では、このような強い一貫性を用いるとレスポンス時間に大きな犠牲が出てくるため、逆に一貫性を弱め、いかに適切な事前計算をしておくことによって、実用的なレスポンス時間を確保するのかという点が、重要なアプローチになるわけです。

イベント駆動

一般的には、アクターは端末を操作するエンドユーザーであることが多いわけですが、クラウドアプリケーションでは、事情が変わってきます。
クラウドアプリケーションでは、今まで以上に他システムとの連携が重要になります。他システムが提供するサービスをRESTなどのプロトコルで使用し、コアコンピタンスの処理のみをアプリケーションで実装することになるでしょう。
この他システムもアクターとしてモデル化します。
外部センサーやスマートデバイスもクラウドアプリケーションと連携するアクターです。
また、アクターに外部センサーやスマートデバイスを加えて、ここから上がってくるイベントのコラボレーションをモデルに加えていけばよいでしょう。
アクターが利用者の場合には、基本的には利用者からの問合せや申請に対応するというアプリケーションアーキテクチャになります。
しかし、クラウドアプリケーションでは、利用者からの問合せや申請に加えて、他システムからプッシュされてくるさまざまなイベントを蓄積、集計して刻一刻とアプリケーションデータを更新していくことになります。
また、上で説明したように利用者からの問合せを予測して事前計算しておくことで、より高速に問合せに対するレスポンスを行うというニーズもあります。
このような要件に対応するために、上がってきたイベントをイベントエンティティといったファクトモデルとして記録すると同時に、アプリケーションのユースケースに合わせた要約エンティティにも集計を行い、事前計算をしておくことが有効になると考えられます。
また、最近注目されているストリーミングなども、イベント駆動の粒度が小さくなったものと考えることができます。この場合も要約エンティティが有効に機能するはずです。

まとめ

要約データ(summary)自体は、新しいアイデアではなくて、大きめのシステムでは広く用いられているものと思いますが、データモデルの正規化という観点からは、あまり望ましいものではなく、どちらかというと日陰者的な扱いではないかと思います。これを一級市民として扱って、UXとイベント駆動を結びつけるハブとして使うと、ぴったり収まるのではないか、というのが今回のアイデアです。
設計段階の回避策ではなく、分析段階から積極的に使っていこうということですね。そうすることによって、クラウドにまつわる遅延や障害、スケーラビリティというさまざまな問題をうまく扱えるのではないかということです。こうやって文章にしてみると、地味な改良案ですが、メタモデラー的には面白い気付きになりました。

2011年11月21日月曜日

「崖っぷちの欧州、ローマの炎上を許すな」のマインドマップとクラス図

11月19日(土)に横浜モデリング勉強会を行いました。
参加者6名で、懇親会も盛り上がりました。
また、会場には(株)アットウェア様の会議室をお借りしました。
参加された皆さん、アットウェア様、どうもありがとうございました。

この勉強会で、浅海が作成したモデルを紹介します。
モデルはMindmapModelingの手法で作成しました。(勉強会で使用したチュートリアル)

モデリングの対象は、JB PRESS誌に掲載されたMartin Wolf氏の記事(FT誌の翻訳)「崖っぷちの欧州、ローマの炎上を許すな」です。前回と同様に、時事ネタなのと、いろいろな情報が交錯していて、一見焦点が絞れないところが、モデリングの練習によい感じです。
この記事における現実世界の構造をオブジェクトモデルとして抽出します。

まず、最初の作業で記事中から単語を抜き出します。
単語を抜き出しながら、登場人物、道具、出来事を中心にMindmapModelingの定めた分類に従って仕分けしていきます。
この結果、できた最初のマインドマップが以下のものです。(図をクリックすると拡大します。)


次の段階では、抽出した、登場人物、道具、出来事の洗練を行います。
用語の名寄せ、用語の種類(generalization)や部品構成(aggregation)を整理していきます。
また、この段階で区分(powertype)の抽出を開始します。
以上の作業を行った結果のマインドマップは以下のものです。



このマインドマップをSimpleModelerでクラス図に変換すると以下のものになります。「国」を中心に、ある程度構造化ができていますが、まだまだ中に浮いたクラスがあることが分かります。


次の段階では、対象となる世界の動的な側面を捉える出来事や物語を整理していきます。
出来事や物語を洗練していくことによって、出来事や物語における登場人物や道具の役割が明確化され、より登場人物や道具の構造をさらに洗練させることができます。
出来事や物語で抽出した役割は、役割(role)としてモデル化して「役割」構造枝に配置し、役割の持つ構造を洗練していきます。

今回は、EUの「中心にいる国々」(事実上、ドイツですが)が主役の物語「欧州連合を救う」と、ギリシャやイタリアなど「支援を受ける側」が主役の物語「経済破綻を回避する」の2つの物語を軸にモデルを整理していきました。

今回のモデルでもう一点ポイントになるのがドメイン・ルール(Domain Rule)、制約条件です。
記事からは「緊縮財政政策 ⇒ 内需が弱くなる」、
「景気が回復しない ⇒ 投資家の信頼感を取り戻す可能性は低い」、
「構造改革 ⇒ 労働市場に影響を及ぼす」、
「労働市場に悪影響 ⇒ 社会的・政治的な混乱」、
「社会的・政治的な混乱 ⇒ 投資家の信頼感は揺らぐ」、
「競争力が回復する ⇒ 人員解雇と名目賃金下落をもたらす」、
「人員解雇と名目賃金下落 ⇒ 失業と社会不安、債権者の不安感」といったルールが抽出できます。

救う側であるEUの「中心にいる国々」も、「支援を受ける側」もこれらの制約条項の上で、金融政策、財政政策、労働政策などを立案、遂行していく必要があります。この記事の場合、こういった制約群で合成される非常に厳しい全体制約が物語の遂行をほぼ実現不可能にしているという絶望感が記事の重要なテーマと考えられます。
逆に、針の穴を通すほどの小さいストライクゾーンとなる、実現可能な組合せは何かというのが、テーマとしてあぶり出されてくるわけですね。
この「実現可能な制約条件の組合せ」の抽出は、システムを実現する上での重要なモデル要素なので、MindmapModelingでもモデルを記述することができるようになっています。これが、BOI構造枝「規則」(Domain Rule)というわけです。

MindmapModelingではBOI構造枝「規則」に記述するDomain Ruleの命名法や構造などは特に定めていません。現状では、OCLのような非常に限定した形でしか、制約を記述して実装に落し込む手法が一般的になっていないからです。
(ルールエンジンなどの商用ミドルウェアやJSR 94 Java Rule Engine APIなどは存在していて一部では活用されていると思いますが)

ただ、最近は関数型言語が実装言語として視野に入ってきたので、このあたりとの接続を念頭において、「規則」の記述方法について文法の拡張を行うことを考えています。
今回は、論理結合子「⇒」(ならば, conditional, material implication)を使ってみました。最終的には、実装の自動生成時に関数型言語が扱える論理式を自動生成するのが目標です。

とはいえ、「緊縮財政政策 ⇒ 内需が弱くなる」という論理式は「緊縮財政政策」ならば必ず「内需が弱くなる」ということですが、現実には100%ということはないので、現実世界のモデリングには使いづらい面があります。参考情報に留めるのか、様相論理の可能性演算子(◇, possibility operator)といったものを導入するか、色々と考えてみたいところです。

以上の作業の結果、作成したマインドマップが以下のものです。


このマインドマップをSimpleModelerでクラス図に変換したものが以下の図です。


以上のマインドマップとクラス図が勉強会で作成したものです。

クラス図にしてみると、名寄の甘いところや、モデル要素のステレオタイプの選択などで問題があることが分かります。
そこで、SimpleModelerでのクラス図生成を繰り返しながら洗練したマインドマップと、このマインドマップからSimpleModelerを使って生成したクラス図は以下のものです。



記事における現実世界の構造がそれなりに上手くとらえられていると思います。

このように記事を詳細にモデル化すると、記事の内容がより深く理解できますね。

「支援を受ける側」が取り得る方法は、制約条件の組合せからほぼ:
  • 緊縮財政、構造改革する。
  • 緊縮財政、構造改革による内需の縮小は、外需の取り込みでカバーする。
  • 緊縮財政、構造改革による社会の不安定化により、投資家が投資を引き上げないために不安感を払拭する対策を行う。
しかないことが分かります。

EU圏外の国から外需の取り込みを行うことも、社会の不安定化を払拭する施策も、相当難しそうですが、このあたりが今後のEUの動向を見定めるポイントかもしれません。

緊縮財政、構造改革を行う国が唯一取れる成長戦略が外需の取り込みである以上、自国通貨の切り下げ、相手国関税、非関税障壁の撤廃が最重要戦略となるわけで、(EUは入れてもらえませんが)最近TPPが政治的なイシューになっている背景がなんとなく見えてきますね。(記事の内容からの推論なので、感想の妥当性は無保証ですw)

2011年11月19日土曜日

横浜モデリング勉強会 (第2回)

今日(11/19)のモデリング勉強会の情報です。

http://atnd.org/events/21756

今回モデリングするのは以下のWeb記事です。

テーマ:  崖っぷちの欧州、ローマの炎上を許すな

モデリングは記事の問題領域であるドメインモデルを作成を目標にします。
  • 問題領域の構造を、エンジニアでない人が理解できるで構築している。
  • プログラムに落し込むことができる。
モデリングは各自のお好きなモデリング手法で行います。
一時間に一度程度、作成途中のモデルを見ながら議論します。
初学者の方は、MindmapModelingをお勧めします。

2011年10月31日月曜日

「TPP議論の本質はこれだ!」のマインドマップとクラス図

10月29日(土)に横浜モデリング勉強会を行いました。
札幌サテライトから2名参加があり合計6名の参加でした。
参加された皆さん、どうもありがとうございました。

この勉強会で、浅海が作成したモデルを紹介します。
モデルはMindmapModelingの手法で作成しました。(勉強会で使用したチュートリアル)

モデリングの対象は、田原総一朗氏のブログ記事「TPP議論の本質はこれだ!」です。時事ネタなのと、いろいろな情報が交錯していて、掴みどころがないのが、モデリングの練習によい感じです。
この記事における現実世界の構造をオブジェクトモデルとして抽出するという趣旨です。

まず、最初の作業で記事中から単語を抜き出します。
単語を抜き出しながら、登場人物、道具、出来事を中心にMindmapModelingの定めた分類に従って仕分けしていきます。
この結果、できた最初のマインドマップが以下のものです。(図をクリックすると拡大します。)


次の段階では、抽出した、登場人物、道具、出来事の洗練を行います。
用語の名寄せ、用語の種類(generalization)や部品構成(aggregation)を整理していきます。
また、この段階で区分(powertype)の抽出を開始します。
以上の作業を行った結果のマインドマップは以下のものです。


次の段階では、対象となる世界の動的な側面を捉える出来事や物語を整理していきます。
出来事や物語を洗練していくことによって、出来事や物語における登場人物や道具の役割が明確化され、より登場人物や道具の構造をさらに洗練させることができます。
出来事や物語で抽出した役割は、役割(role)としてモデル化して「役割」構造枝に配置し、役割の持つ構造を洗練していきます。

以上の作業の結果、作成したマインドマップが以下のものです。


モデリング勉強会での作業はここまででした。
その後、このマインドマップをSimpleModelerでクラス図に変換したものが以下のクラス図です。(クリックすると拡大します。)
クラス図にしてみると、まだまだ抜けているところがあることがよく分かります。
モデルとして欠陥があるわけではなさそうですが、名寄せが甘いところや、時間切れでモデルに記述しきれなかったところが明確になりました。


そこで、SimpleModelerでのクラス図生成を繰り返しながら洗練したマインドマップが以下のものです。


このマインドマップをSimpleModelerを使ってクラス図に変換したものが以下のクラス図です。(クリックすると拡大します。)
記事における現実世界の構造がそれなりに上手くとらえられていると思います。
短い記事ですが、丹念にモデル化していると存外大きな構造が背景にあることが分かります。



物語は記事中で一番重要視していると思われる「アメリカのアジア戦略」を抽出しましたが、時間があれば「アメリカの輸出増加戦略」や「日本のTPP外交戦略」というような物語を加えるとより内容が充実してくると思います。

2011年10月29日土曜日

モデリング勉強会

今日(10/29)のモデリング勉強会の情報です。

http://atnd.org/events/20884

今回モデリングするのは以下のブログ記事です。

テーマ:  TPP議論の本質はこれだ!

モデリングは各自のお好きなモデリング手法で行います。
一時間に一度程度、作成途中のモデルを見ながら議論します。

初学者の方は、MindmapModelingをお勧めします。

クラウド時代のデータアーキテクチャとモデリング

クラウド時代のデータアーキテクチャとモデリングについてちょっと思いついたことがあるのでメモ。



クラウドアプリケーションが従来のアプリケーションと異なる点の一つは、クラウド上に遍在する様々なデータをマッシュアップして使用することになるという点です。
自前で管理するデータだけでなく外部データを編みこんだドメインモデルに対して、アプリケーションが操作を行うことになります。
また、スケーラビリティを確保するため、CQRSのように参照系と更新系をアーキテクチャレベルで分割していく形が基本になるでしょう。
これは、自前データのサイズが巨大になる場合への対応にも有効ですが、外部データとの連携では必須のアーキテクチャです。自前データが巨大にならない普通のアプリケーションでも、外部データとの連携を行うのであれば、このアーキテクチャを採るのが得策です。

ドメインモデル

このアーキテクチャの上でドメインモデルは、以下の3つの種類に分類するのがよいのではないかというのがアイデア。

  • Application Domain
  • Actor Domain
  • Fact Domain
Application Domainは、アプリケーションが操作するドメイン。自前データと外部データをマッシュアップして見せます。
従来のドメインモデルとの違いは、外部データをマッシュアップすること。マッシュアップを行う場合、更新系を含めると実現方法が大変になってくるので、参照系を中心にするとよいでしょう。都合のよいことに参照系と更新系を分離するアーキテクチャにもマッチしています。
従来技術でもRDBMSのViewなどでこういった事が可能ですが、これをもっとアーキテクチャレベルで大掛かりにやるイメージです。(そういう意味ではデータベースの3層スキーマが近しいかも。)

外部データは、Actor Domainとしてモデル化します。Actor Objectは、アプリケーション外にあるオブジェクト(の代理オブジェクト)という意味ですが、このドメインモデル版という意図でActor Domainという名前にしてみました。
Actor Objectの場合、自前のオブジェクトでないので、決められた範囲でお願いはできるけど自由に操作できないという処理上の制約が出てきます。
Actor Domainもそういった制約を持ったドメインモデルをイメージしています。

そして、Actor Domainの外側にFact Domainを持ってきました。
Fact Domainは、クラウド上で発生する無数のイベントの生データをイメージしています。たとえばアプリケーションのログや、データベースのジャーナルなどです。

Actor Domainはアプリケーションの外部で、Fact Domainのデータを集約し、意味を付加したモデルとしてモデルインスタンスが公開されています。

Application Domainは、このAcotr Domainのインスタンスを、自前データとマッシュアップして、アプリケーションに取って意味のあるモデルのインスタンスとして、アプリケーションに提供します。

更新系

更新系には、Application Commandというモジュールを用意しました。(Commandというのは仮です。他によい名前があったら取り替えると思います。)
Application Commandは自前のデータの更新を中心に、Application Domainへの更新依頼、Fact Domainの情報提供を行います。
Application Domainへの更新依頼はあまり多くないという仮定で点線にしてみました。

モデリング技術

こういったデータアーキテクチャを採る場合のモデリング技術として、どうも文書モデリング(SGML, XML系)が有効でないかというのが、データアーキテクチャと同時に思いついたアイデア。
従来アプリケーションでは、データモデル/オブジェクトモデルがモデリングの中心でしたが、ここまで説明してきたようなマッシュアップが基本となるデータアーキテクチャでは、異なったセマンティクスのモデルをマッシュアップする基盤として文書モデリングが重要になってくるのではないかということです。
2000年頃にも、こういう話題がよく出たと思いますが、クラウド時代になって改めて、この技術を適用する形が見えてきた感触です。
ただし、今回はXMLという文脈でなく、関数型という文脈ではないかというのが、今回のアイデアの軸。関数型プログラミングと文書モデリングの相性は相当よさそうです。
XMLはデータ表現形式としてはよいのですが、データ操作体系としてはまだまだ不十分でした。ここを関数型言語ですっきりと記述することが可能になってきたのは一つミッシングリンクが埋まってきた感じです。

オブジェクトモデル、データモデル、文書モデル、関数モデル。
Application Domainのメタモデルとして、4つのモデルをどのように配合していくとよいのか。まだまだノーアイデアですが、未開拓の領域だけに色々と面白そうです。
クラウドも現時点では、プラットフォーム技術やフレームワーク、プログラミング言語といった実装よりの技術に焦点があっていますが、次の段階ではこういったモデリング技術の所に焦点が移ってくるのではないかと考えています。

2011年9月19日月曜日

RESTのFeedを一覧表示するActivityの生成

8月27,28日に開催されたクラウド温泉@小樽のセッション、「クラウドアプリケーション(App Engine&Android)自動生成 - SimpleModeler/g3/g4デモ」でのSimpleModelerからAndroidアプリの自動生成のデモの話の続きです。

SimpleModelerでは、アプリケーション開発のベースとなるドメインモデルの実装を「自動コーディング」します。この部分はドメインモデルが決まれば、プラットフォーム上での実装はほぼ決まるので自動生成の格好のターゲットです。逆に、この部分を手組みでコーディングしていると相当の工数が必要となります。プログラム開発の生産性を上げるためには、この部分の生産性向上が重要になってきます。

ただし、エンティティに対するCRUD処理については自動生成である程度のモノを生成可能です。アプリケーション部分のサンプルという意味もこめて、データに対するCRUD処理を行なうActivityを生成する予定で、一部実現しています。

現在SimpleModelerが自動生成しているのは、サーバー上に格納されているエンティティCustomerをREST経由でアクセスして一覧表示を行うプログラムが以下のCustomerRestViewActivityです。

package com.demo;

import android.content.Context;
import android.os.Bundle;
import java.math.*;
import java.util.*;
import org.goldenport.android.*;
import org.goldenport.android.traits.ListViewTrait;

public class CustomerRestViewActivity extends GActivity<DemoController> {
    
    // @LayoutView(R.id.header)
    // TextView mHeader;
    // @ResourceString(R.string.header)
    // String mHeaderLabel;
    // @ResourceColor(R.color.header)
    // Color mHeaderColor;
    // @IntentExtra("message")
    // String mMessage;
    
    public CustomerRestViewActivity() {
        addTrait(new ListViewTrait());
    }
    
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
    }
    
    @Override
    protected int get_Layout_id() {
        return R.layout.customer_rest_view;
    }
    
    @Override
    protected void onStart() {
        super.onStart();
    //    if (mMessage != null) {
    //        mHeader.setText(mMessage);
    //    } else {
    //        mHeader.setText(mHeaderLabel);
    //    }
        set_list_adapter(gcontroller.getCustomerRestFeedAdapter());
    }
}

GActivity


CustomerRestViewActivityは、サーバー上に格納されているエンティティCustomerをRESTでアクセスしてListViewの一覧として表示するActivityです。基底クラスはGActivityで、型パラメータとしてDemoアプリケーションのコントローラであるDemoControllerを指定しています。

org.goldenport.android.GActivityが、g4におけるActivityの基底クラスです。主に以下の機能を提供しています。

  • DI
  • コントローラクラスの自動バインド
  • トレイトによる機能拡張
  • 非同期更新処理

DI(Dependency Injection)

g4では、Google Guice (without AOP)を用いたDIコンテナ機能を提供しています。XMLで定義された各種ViewやリソースをActivityのインスタンス変数に自動インジェクトすることができます。
自動生成されたCustomerRestViewActivityを、自前で拡張するためのヒントとして以下のコメントが入っています。
この機能は次回の記事で説明することにします。
// @LayoutView(R.id.header)
    // TextView mHeader;
    // @ResourceString(R.string.header)
    // String mHeaderLabel;
    // @ResourceColor(R.color.header)
    // Color mHeaderColor;
    // @IntentExtra("message")
    // String mMessage;

コントローラクラスの自動バインド

g4では、画面表示を行うクラスであるGActivity(Activityのサブクラス)から、アプリケーション・ロジックを分離してコントローラとして実装します。コントローラはGControllerのサブクラスになります。
Activityからアプリケーションロジックを分離して、コントローラ側で実現することにより、以下の効果を期待しています。
  • アプリケーション全体のロジックをコントローラに集中させることで、アプリケーションの見通しを良くし、拡張性、保守性を高める。
  • GUIを経由しないで、アプリケーションロジックのユニットを可能にする。
  • アプリケーションロジックを複数のActivityから共用できる。
GActivityでは、コントローラクラスを自動的にインジェクトするので、GActivityの実装では、特に初期化処理なしでコントローラを参照して使うことができます。 
生成されたonStartメソッドにはヒントのコメントがあります。この不要なコメントを削除したonStartメソッドは以下のものになります。
@Override
    protected void onStart() {
        super.onStart();
        set_list_adapter(gcontroller.getCustomerRestFeedAdapter());
    }
「super.onStart()」はお約束。
set_list_adapterメソッドで、コントローラから返されるListAdapterを設定しています。
GActivityで定義しているインスタンス変数gcontrollerから参照しているコントローラ(DemoController)から、ドメインエンティティCustomerをRESTのフィードとしてアクセスするListAdapterを取得しています。
このListAdapterは、バックエンドにページング付きのREST通信と通信結果のキャッシュ機能を持っています。この機能を実現するために、SimpldeModelerが生成するAndroidコードで説明したとおり、ActivityからRESTドライバにいたるまで相当数のコードが自動生成されています。また、g4ベースでサーバサイドのコードも自動生成していることは、SimpleModeler (クラウド温泉@小樽)で触れました。このあたりのメカニズムはいずれ紹介したいと思います。

トレイトによる機能拡張

ActivityにListViewやGralleryなどを操作するロジックをハードコーディングしてしまうと、ロジックを他の目的に再利用させることができません。
これらの機能を持つ基底クラスを作るのが次善策ですが、拡張性や保守性に問題があります。
この問題を解決するために、g4ではScalaのトレイトライクなメカニズムを導入しました。
ListViewTraitは、ListViewを操作する機能を持つトレイトです。GActivityのaddTraitメソッドによりListViewTraitを追加することによりGActivityにListViewを操作する機能が追加されます。
以下のようにコンストラクタでトレイトListViweTraitを追加しています。
public CustomerRestViewActivity() {
        addTrait(new ListViewTrait());
    }

非同期更新処理

ListViewTraitが内部で自動的にハンドリングしているので、CustomerRestViewActivityには直接みえていません。
ListViewTraitが、一覧データのローディング時にプログレスバーを画面上に表示し、ローディングが完了した時点で表示を終了する処理を行っています。

2011年9月1日木曜日

SimpldeModelerが生成するAndroidコード

8月27,28日に開催されたクラウド温泉@小樽では、「クラウドアプリケーション(App Engine&Android)自動生成?SimpleModeler/g3/g4デモ」のセッションで、SimpleModelerからAndroidアプリの自動生成のデモを行いました。

クラウド温泉@小樽に向けてブログに書いてきたものを実演しました。

デモに使ったモデルは以下のCSV、demo.csvをコンバートして生成したDEACustomer.java、DEEBuy.java、DERGoods.javaの3つのScala DSLで記述したものです。

demo.csv
#actor,parts,attrs
customer,,phone
#resource
goods,,note
#event
buy,customer;goods
そのうちの一つ、DEACustomer.javaのソースコードは以下のものです。DEEBuy.javaとDERGoods.javaもだいたい同じコードになります。

DEACustomer.java
package com.demo

import org.simplemodeling.dsl._
import org.simplemodeling.dsl.datatype._
import org.simplemodeling.dsl.domain._
import org.simplemodeling.dsl.domain.values._

case class DEACustomer extends DomainActor {
  term = "customer"
  caption = "customer"
  brief = <t></t>
  description = <text></text>

  id("customerId", DVICustomerId())
  attribute("name", DVNCustomerName())
  attribute("summary", XString)
  attribute("phone", XString)
}

case class DVICustomerId extends DomainValueId {
  term = "customerId"
  caption = "customerId"
  brief = <t></t>
  description = <text></text>

  attribute("value", XString)
}

case class DVNCustomerName extends DomainValueName {
  term = "customerName"
  caption = "customerName"
  brief = <t></t>
  description = <text></text>

  attribute("value", XString)
}
デモでは、このモデルからg4上で動作するJavaソースコードを生成し、Androidアプリケーションとして動作させました。Javaソースコードの生成は、一部デモに間に合わなかったものもあったので、その後、少し改良しました。その改良版では、以下のソースコード(33ファイル、4.7Kステップ)を生成します。
  • BuyRestFeedAdapter.java
  • BuyRestFeedRepository.java
  • BuyRestViewActivity.java
  • CustomerRestFeedAdapter.java
  • CustomerRestFeedRepository.java
  • CustomerRestViewActivity.java
  • DDBuy.java
  • DDCustomer.java
  • DDGoods.java
  • DEACustomer.java
  • DEEBuy.java
  • DERGoods.java
  • DVIBuyId.java
  • DVICustomerId.java
  • DVIGoodsId.java
  • DVNCustomerName.java
  • DVNGoodsName.java
  • DemoAgent.java
  • DemoApplication.java
  • DemoContext.java
  • DemoContract.java
  • DemoController.java
  • DemoErrorModel.java
  • DemoFactory.java
  • DemoG3Driver.java
  • DemoModel.java
  • DemoModule.java
  • DemoProvider.java
  • DemoRepository.java
  • GoodsRestFeedAdapter.java
  • GoodsRestFeedRepository.java
  • GoodsRestViewActivity.java
  • IDemoRestDriver.java
このJavaプログラムをクラス図にすると以下のようになります。
BuyRestViewActivity, CustomerRestViewActivity, GoodsRestViewActivityがAndroidの画面クラスであるActivity(Androidの画面クラス)です。これらのActivityがサーバー上に格納されているデータを画面に表示します。
これらのActivityからDemoControllerを経由して、ドメインモデルを管理するクラスDemoModelにアクセスします。データの管理は、サーバーから取得したフィードデータをメモリ上に保持するクラスBuyRestFeedRepository, CustomerRestFeedRepository, GoodsRestFeedRepositoryで行い、これらのクラスをAndroidのウィジェットであるListView用のアダプタBuyRestFeedAdapter, CustomerRestFeedAdapter, GoodsRestFeedAdapterが使用します。
また、サーバーとの通信はDemoG3Driverが行います。今回のデモではサーバー側はg3なので、g3と通信するためのドライバを使用していますが、ドライバはインタフェースIDemoRestDriverを実装していれば取り替え可能な構造になっています。
クラス図の下に、Documentオブジェクト(DTO)であるDDCustomer, DDBuy, DDGoodsがありますが、これらのオブジェクトでデータのやりとりを行います。
クラス図のざっくりとした説明は以上の通りです。追々詳細な情報を書いていく予定です。
ここで強調したいのは、(トイプログラムではなくて)ある程度本格的なアプリケーションを作成する場合、ごく小さなドメインモデルからでも、これらのクラス群を作成しなければならないということです。一般的なJavaプログラムでも必要なものもありますし、Android特有のクラスもありますが、いずれにしても相当量のコーディングが必要になります。しかも、これらのクラス群はドメインモデルが決まれば、Android上でどう実装するのかというのも、ほとんど決まってしまうので、人力でコーディングするのは、かなりもったいない作業です。こういった定型部分を自動コーディングしてしまうことがSimpleModelerの目的です。

2011年8月31日水曜日

F#考 - クラウド温泉2.0@小樽

8月27,28日にクラウド温泉@小樽が開催されました。今回のクラウド温泉も盛りだくさんの内容で、ボクとしてはF#について詳しいお話を聞けたのが収穫でした。発表者の@bleisさん、MSの荒井さん、参加者の皆様、どうもありがとうございました。

F#はScalaと同じオブジェクト指向と関数型のハイブリッド言語で、ざっくりとはだいたい同じことができると捉えてみてよさそうです。ただし、言語の狙いは異なるので、利用シーンは違ってきます。

以下、F#の知識は当日の聞きかじりを元にしているので、不正確なところが多いと思いますが、ボクの現時点での認識のメモということでご了承ください。

F#の言語のセマンティクスは関数型が主でオブジェクト指向は従のようです。

関数型の世界で扱うデータはレコード/タプルで、構造体的な言語要素となっています。そして、このレコードとは別にクラス/オブジェクトが用意されています。(変数に関数をバインドしてメソッド的に使えるかもしれませんが、この点は未確認。)
つまりデータを記述する言語要素が二系統あって、そのうちの一系統を使うと関数型言語らしいプログラミングが堪能できるということですね。

一方、クラス/オブジェクトは、当日確認しなかったのですが、恐らく.NETのクラス/オブジェクトだろうと思います。クラス/オブジェクトを通じて.NETのクラス/オブジェクトをC#やVBと相互に連携できるのではないかと推測します。

データを記述する言語要素が二系統あるのは、関数型言語としての純粋性を保ちたかったということもあるでしょうし、.NETのクラス/オブジェクトを関数型言語向けに拡張するのが難しかった、という制約に起因するところもありそうです。

また、データは基本的にイミュータブルで、mutableというキーワードを付けるとミュータブルにもできるというアプローチになっています。この点からも関数型の方に軸足があることが分かります。このため、状態の存在を前提としたプログラミングには、あまり向いておらず、つまるところオブジェクト指向プログラミング言語としてはあまり快適ではなさそうです。

F#では、型パラメータの省略ができて簡潔な記述が可能になっていますが、関数をオブジェクトのメソッドとしてポリモーフィックに動かすことをやめることにより(クラス/オブジェクトではなくレコード/タプルを使う効果)、共変/反変まわりの指定を考えなくてもよいので、そのあたりが寄与しているのかなと感じました。これも、関数型側に軸足を持っている効果です。

以上のようにF#は関数型側に大きく機能を倒して、オブジェクト指向的にはほどほどにして、関数型言語の美点を伸ばしていくアプローチのようです。

このアプローチの延長線上で、コンピューテーション式、seq/yieldといったところが、関数型言語の高度な応用で、マニア的に面白いところですね。どちらもDSLの記述力を高めることに大きく寄与する機能です。

F#は関数型言語として素晴らしい言語であるのは確かですが、関数型側に機能を倒しているため、オブジェクト指向言語としては快適というわけではなく、実務へ適用する汎用言語としては考えにくいのかなという印象を持ちました。F#一本ですべての応用を記述する、ということにはならないかなと。

そこで、F#を取り囲む文脈を考えてみましょう。F#は.NET上で動作することを前提とした言語であり、C#やC++といった.NETの他の言語と共存することが前提です。

C#の場合は、VBなどを置き換えていって、.NETの中心言語にしていくという意図もあったかと思いますが、F#の場合は、C#を置き換えるといったことは意図しておらず、C#を補完する立場になるでしょう。そうすると、C#ができることを全部カバーした上で、新たに関数型の機能を追加するといったことをする必要はなく、オブジェクト指向的な文法は.NETのクラス/オブジェクトと連携できるものがあれば十分と割り切ることができます。その上で、C#ではできない関数型のプログラミングモデルを使いやすいものにする方向に力をそそいでいるわけですね。

そういう意味で、F#は.NETワールドを構成するパーツの一つとして、並列プログラミングやDSLといった関数型に適した分野をカバーする専用言語という戦略上の意図を持った言語であると推測されます。
今後の技術の発展の方向は並列プログラミングとDSLが二大潮流になることは確実なので、この2つの分野に強い関数型言語を並行プログラミングとDSL向けを意識してチューニングし、.NETワールド内でのポジションを明確にした上で提供してきているのにはMicrosoftの底力を感じました。

F#は、monoと組合わせてMacやLinuxでも普通に動くみたいなので、DSLにフォーカスした応用ではプラットフォームを問わず実用的に使用できそうです。そういう意味でも面白い存在です。

2011年7月19日火曜日

SimpleModeler (クラウド温泉@小樽)

クラウド温泉@小樽では、モデルコンパイラSimpleModelerからg3フレームワーク上で動作するクラウドサービス、g4フレームワーク上で動作するAndroidクライアントを自動生成し、Androidクライアントとクラウドサービスが連携動作するデモを行う予定です。



まずScala DSLでモデルを記述します。(デモではより効果を狙ってCSVでモデル記述するかもしれません。)
このScala DSLからSimpleModelerを使って、Android用クライアントとGoogle App Engine/Tomcat(Glassfish)の両方で動作するRESTサービスを生成します。
生成された、AndroidクライアントとRESTサービスは連携して、データに対するCRUD処理を行うことができます。

Android用クライアントはg4フレームワーク上で動作します。g4フレームワークはGoogle GuiceによるDIをベースにしたAndroidアプリケーションフレームワークです。

サーバープログラムはg3フレームワーク上で動作します。g3フレームワークはGoogle App Engineと、TomcatやGlassfishなどの通常のServletコンテナ上で動作します。ServletコンテナはEC2を始め、一般的なクラウドプラットフォーム上で動作します。このため、g3フレームワークを用いることでクラウドプラットフォーム上で可搬性のあるアプリケーションを作成することができます。

デモの目的

デモでは、見栄えや分りやすさの点からプログラミングレスで自動生成したアプリケーションがそのまま動作することを主眼とします。
しかし、このデモはモデル駆動開発でアプリケーションプログラムが自動生成されるということを主張するのが趣旨ではありません。現在の所、CRUDのような特定の定形処理以外はアプリケーションの自動生成は、まだまだ実用化には程遠いからです。

それでは、SimpleModelerによるプログラムの自動生成は何をコンセプトとしているのでしょう。
それは、ひとことで言うと「ドメインライブラリの自動コーディング」です。

ドメインモデル

オブジェクトモデリングでは大きく分析モデル(またはPIM, Platform Indenpendent Model)と設計モデル(またはPSM, Platform Specific Model)の2つのモデルを作成します。これはモデルの抽象度が分類の軸になっています。
分析モデルに対して、動作ターゲットのプラットフォームと非機能要求を加えて、実際に動作するプログラムの設計図となる設計モデルとなります。

モデルの分類の軸は他にも色々ありますが、筆者が有効と考えているものにアプリケーションモデルとドメインモデルの軸があります。
ドメインモデルは、アプリケーションの問題領域の構造をモデル化したものです。主に静的モデルにフォーカスしたモデルで、静的構造中心、モデルのライフサイクルが長い、自動生成の対象になりやすいという特性があります。
一方、アプリケーションモデルはドメインモデルを使用して、アプリケーション利用者の目的を達成するための仕組みをモデル化したものです。主に動的モデルにフォーカスしたモデルで、アプリケーションモデルは、振舞い中心、モデルのライフサイクルが短い、自動生成の対象になりにくいという特性があります。



ドメインモデルは、静的構造(クラス図)中心となりますが、動的モデルとして状態機械、ルールモデル、アクション言語を併用します。(アプリケーションモデルでは、ユースケース、インタラクション、コラボレーションなどを使用します。)

静的構造図が中心のモデルの場合、オブジェクト指向言語を使うと設計モデルとプログラムのセマンティクスギャップが非常に少ないので、設計モデルを飛ばして直接プログラミングすることも可能です。短期開発では、その方が効率もよく、プログラムの品質もよくなるでしょう。また、持続性を重視する製品開発でも、静的構造のみでよければER図などのデータモデリングで十分です。
一方、状態機械やルールモデルなど、プログラミング言語とのセマンティクスギャップが大きいモデルを使用する場合は短期開発であっても引き続き設計モデルが有効です。

ドメインライブラリ

現状では、ドメインモデルを作成しても手作業でプログラミングすることが多く、モデルを作成しても作業量は減りません。プログラミング側での改修がモデルに反映されなくなり、長期的にはモデルが死んでしまうという問題もあります。

ドメインモデルからRDBMSのDDLや、O/Rマッパーのコード生成などを行うツールはあるので、データモデリングの範囲では自動生成を取り込んだ運用を行うことは可能です。
しかし、動的モデルを包含したオブジェクトモデル全体のスコープでは、ドメインモデルに限定しても、プログラムの自動生成はまだまだ発展途上といえるでしょう。
しかし、ドメインモデルを実用的に記述できる範囲でドメインモデルのプロファイルを定めておくことは可能です。筆者はこの目的でドメインモデル(とアプリケーションモデル)のプロファイルをまとめています。詳しくはこちらをどうぞ。(『上流工程UMLモデリング』、『マインドマップではじめるモデリング講座』)

ドメインモデルの重要な特性の一つは、自動生成に適しているということです。決められたパーツを使ってドメインモデルを記述するという運用にすれば、分析モデルから、設計モデルをバイパスしてほぼ完全に実装を自動生成することができます。また設計モデルも仕様書という形で生成可能です。もちろん、100%完全は難しいでしょうが、適切な拡張ポイントを生成することで、部分的なプログラミングで補完可能にできるでしょう。

SimpleModelerは、この実現を目指しています。

ドメインライブラリとアプリケーションフレームワーク

ドメインライブラリを単体で生成するのも十分に有効ですが、適切なアプリケーションフレームワークがあれば、この枠組みの中に組み込んでしまえば、アプリケーション開発をより効率的に行うことができます。
アプリケーションフレームワークの上にドメインライブラリをビルトインしたものを土台にしてアプリケーション開発を進めることができます。

クラウドアプリケーション向けにg3フレームワーク、Androidアプリケーション向けにg4フレームワークを開発したので、これらのフレームワーク上にビルトインできるドメインライブラリを生成します。







自動コーディング

モデル駆動開発について以下のような疑問をしばしば耳にします。
  • モデルからプログラム全体を生成するのは無理ではないか。
  • モデルから自動生成したプログラムを直接修正した時に、モデルに反映できないと、以降の開発にモデルを使うことができない。
  • モデルレベルでデバッグできないとバグ修正ができない。
つまりモデル駆動に対してプログラミング言語のコンパイラ的な運用を期待しているわけです。これは一つの理想ではあるのですが、現段階の技術レベルでは難しく、ここに判断基準を置いてしまうと、自動生成は時期尚早という判断になってしまいます。
しかし、ドメインモデルに範囲を限定すれば、相当量のコードの自動生成が可能なので、これをまったく無視するのはあまりにももったいない。
そこで、コンパイラモデルに代わって、プログラムの自動生成をソフトウェア開発の枠組に取り込む切り口として考えているのが『クラスライブラリの「自動コーディング」』というコンセプトです。

言うまでもなくソフトウェア開発では、多数のクラスライブラリを併用して開発を進めるのが普通です。多くのクラスライブラリはオープンソースであり、APIリファレンスとソースコードの両方を参照して利用できます。
基本的にはAPI仕様をみて使いますが、詳細仕様を知りたい時にはソースコードも参照します。デバッグ時にはクラスライブラリのソースも取り込んでブレークポイントを張ったり、変数の値を確認したりします。
クラスライブラリのバグ修正は追加は、短期対応と長期対応の2つのフェーズで行われます。まず、短期対応として、必要に応じてパッチを当てたりして修正します。
その後、バグの修正や機能追加は所定の手続きをへてクラスライブラリに反映されます。長期対応として、この修正が取り込まれた新しいバージョンのクラスライブラリを、改めてアプリケーションに取り込みます。

ここで、発想を少し変えてみましょう。
事前に誰かが用意したクラスライブラリでも、プログラムが自動生成したクラスライブラリでも、利用者からは全く同じです。
つまり、クラスライブラリとしてとして運用するのであれば、プログラムの自動生成を用いても、今までの開発と何ら変わるところはないはずです。

次は、ニーズの面から考えてみましょう。
プログラミングをするときに、特定のドメイン向けのクラスライブラリが整備されているととても効率的です。
しかし、特定のドメイン向けのドメインクラスライブラリは、よほど共通して使用される大きなドメインでないと事前に誰かが用意しているということはありません。
このため、ソフトウェア開発の初期段階では、ドメインモデルをコードとして実装する作業を黙々と続けることになります。あるいは、画面とデータベースをつなぐ処理として、画面のイベントハンドラーの中にドメイン処理が重複して繰り返し生産されることになります。
前者では、ドメインモデルのコーディングが開発全体のボトルネックになりますし、後者では、アプリケーションの保守性、拡張性に問題が出てきます。
この問題を、ドメインクラスライブラリの自動コーディングが解決することができます。前述したようにドメインモデルはほぼ完全な自動生成が可能なので、ドメインライブラリを自動生成するのは実用範囲です。そこで、ドメインモデルをドメインクラスライブラリとして"自動コーディング"してしまおう、というわけです。
出来合いの標準品ではなく、自分向けのクラスライブラリですから、アプリケーションのニーズにもぴったり合います。使い方は、ソースコードが提供されている通常のクラスライブラリと全く同じです。

クラウド温泉@小樽

SimpleModelerのコンセプトが「ドメインライブラリの自動コーディング」であることを説明しました。クラウド温泉@小樽では、SimpleModelerのデモをネタに、ドメインライブラリの自動コーディングというコンセプト、実現性、応用について議論できればと思っています。
また、クラウドアプリケーション、スマートデバイスによって、ドメインモデリングの技術にも色々な影響があることが予想されます。このあたりも面白い議論ができそうです。

2011年7月11日月曜日

g4 (クラウド温泉@小樽)

クラウド温泉@小樽では、Androidアプリケーションの自動生成のデモをしますが、その基盤となるAndroidアプリケーションフレームワークであるg4について説明します。

g4はSImpleModelerによるAndroidアプリケーション自動生成の基盤として新規作成したアプリケーションフレームワークです。

g4の基本構成は以下の通りです。



ボクのAndroid開発経験から、Androidのアプリケーションアーキテクチャとして、この図に示したものがよいのではないかという仮説のものと、このアーキテクチャの基盤となるクラス群を提供してます。

アーキテクチャの基盤となるクラスは以下の3つに分類できます。

  • Android基本ライブラリを拡張
  • Google Guideを拡張
  • g4独自クラス

Android基本ライブラリ

Android基本ライブラリが提供するクラスです。これらのクラスをさらに拡張した基底クラスを用意しています。

Application
アプリケーション

Activity
Android画面

Fragment
Android画面の断片(Android 3.0以降)

View
Android画面の部品

Service
アプリケーション本体を補完するサービス

Intent
Activity/Service間で流通する情報

ContentProvider
情報提供
Activity, Fragment, ViewはCRUDなど特定のユースケース向けのソースコード自動生成が行われるので、それに対応した拡張を行っています。
Service, Intent, ContentProviderは、ソースコードの自動生成の直接の対象です。特にドメインモデルからIntentとContentProviderはほぼ完全な形で自動生成できる見込みです。
Serviceは、ファサード部分を自動生成して、実現部分をコーディングする形になりますが、これに対応した拡張を行う予定です。

Google Guiceを拡張

g4は、DI(Dependency Injection)を用いたアプリケーション組立てをサポートします。DI機能にはGoogle Guice(without AOP)を用いています。
Guiceを使ったDIで、アプリケーションを構築する各オブジェクトの結合度を低くし、オブジェクトの再利用性、拡張性、保守性、テスタビリティを高めます。
このGuiceのモジュールを定義するクラスがModuleです。g4ではGuiceのMiduleをg4用に拡張した基底クラスを提供しています。

Module
Google Guiceのモジュール定義クラス

g4クラス

g4では以下のAndroidが提供する基本クラスに、以下のクラスを追加してAndroidアプリケーションのアプリケーションアーキテクチャを構築しています。

Controller
画面が実行する責務を提供

Agent
モデルの更新を伴う責務を実装

Model
ドメインモデル

ErrorModel
エラーモデル

Driver
外部リソースにアクセスするドライバ

Factory
オブジェクトの生成とインジェクション

Repository
エンティティオブジェクトの管理
Androidでは、画面のインタラクションをActivityが遂行しますが、シンプルな造りの場合、すべての責務をActivity内に実装することになります。
この方法は初期実装は楽ですが、プログラムがある程度大きくなってくると、再利用性、拡張性、保守性、テスタビリティが損なわれることになります。
そこで、持続的に機能拡張を続ける本格的なアプリケーションの場合はきちんとしたアプリケーションアーキテクチャの構成を取るのが得策です。アプリケーションアーキテクチャでは、分散対象となる部品の構成、分散配置の戦略を定めます。そしてユースケースを構成する各種責務を色々なオブジェクトに分散配置していくことになります。
g4では、以下の方針で責務の分散配置を行います。
  • 利用者視点でドメインモデルを操作する責務はControllerに配置
  • ドメインモデル視点でモデルの更新処理を行う責務はAgentに配置
  • ドメインモデルはModelに配置
  • エラー処理の責務はErrorModelに集約
  • 外部リソースへのアクセスはDriverに集約
また、ドメインモデル操作の基本機能であるFactoryとRepositoryも用意しています。
Androidの基本ライブラリのみだと、ここで述べたアプリケーションアーキテクチャを実装することが難しいので、必要なオブジェクトをg4独自クラスとして追加しているわけです。

2011年7月9日土曜日

g3 (クラウド温泉@小樽)

8月27日(土)/28日(日)に開催されるクラウド温泉@小樽に向けて、現時点でのg3/g4/SimpleModelerの技術について整理しておこうと思います。

Scala DSLベースのモデルコンパイラSimpleModelerでは、当初Glassfish(Java EE)、Spring、Google App Engineといったクラウドプラットフォームあるいはその上で利用するフレームワークのAPI上に直接プログラムを生成することを目指していました。

実際に、Google App Engineでのプログラム生成を実装したところ、生成するプログラム側で相当な作り込みが必要となり、ある意味フレームワークそのものを生成するような形になってしまうことが分かりました。結局、共通部品として必要なものは、自動生成ではなくフレームワークとして独立して提供するのが筋ということですね。

クラウドアプリケーション向けのフレームワークとしては、既存のフレームワークを流用するのが有力な選択肢です。Java EEやSpringといったエンタープライズ向けのWebフレームワーク、あるいはMule ESBやCamelといったESBについても比較検討したのですが、ボクが必要としている機能とはまだま距離があるということで、新規に開発することにしました。

こうして開発したのがg3フレームワークです。


目標

g3を一言で表現すると『Scala DSLベースREST指向粗粒度非同期コンポーネントフレームワーク』です。


  • フレームワークのアプリケーションインタフェースにはScala DSLを用いる。
  • 粗粒度のコンポーネントを非同期で連携してアプリケーションを構築する。
  • コンポーネント間の連携にはRESTを用いる。
開発時に念頭においていたのはEIP(Enterprise Integration Patterns)です。EIPによって非同期コンポーネントをAtomPubのフィードメッセージで連携するアーキテクチャがクラウドアプリケーションの一つの形ではないかというのが基本アイデアになっています。

プログラミングモデルとDSL

クラウドアプリケーションのアプリケーションアーキテクチャとしてどのようなものが適切かというのはまだまだ未知の領域ですが、ボクが仮説として考えているのが並行動作する粗粒度コンポーネントをメッセージングで接続する、コンポーネントベースのアーキテクチャです。 粗粒度コンポーネント間の連携プロトコルとしてRESTセマンティクスを用いることで、プログラミングモデル上、インターネット空間とシームレスに接続することができます。

Scala DSL

このアーキテクチャを取るときの論点の一つが、コンポーネントの組立てをいかに簡単に記述するのかという点です。このようなコンポーネントの組立てにXMLを用いるのが従来の手法ですが、以下の問題があります。まず、XMLによる定義は記述が煩雑になってしまうこと。また、プログラミング言語とのシームレスな連携ができないので、プログラミング言語とXMLの二本立ての管理になってしまうという問題もあります。関連して、定義ファイルにプログラミング的な技法、たとえばマクロを導入して定義の共通部をまとめることにできるようにする、いったことを実現することも困難です。 この問題を解決するために採用したのが、Scalaをホスト言語にしたDSLです。専用言語であるDSLを用いることで、メッセージングによるコンポーネントの連携を簡潔に記述することができます。 また、Scalaを通してScalaだけでなくJavaやその他JavaVM上で動作するプログラミング言語で記述されたプログラムと連携することができます。

REST指向

REST指向は、Webプロトコルとのシームレスな連携、フレーム内リソース識別のURI化、コンポーネント連携に用いるメッセージをAtomフィードベースにしていることにより実現しています。

イベント駆動

クラウドアプリケーションは非同期に発生するイベントを受けて動作するイベント駆動で処理する構造がアプリケーションの基本アーキテクチャになるというのがボクの仮説の一つです。 比喩的には、割り込みハンドラーとシステムアクティビティでアプリケーションを構築する組込み機器のような構造をイメージしています。 イベント駆動はまだ実現できていませんが、その前提となるメッセージの到着を起点に処理を駆動するメカニズムは実現しています。エンティティとして永続管理するビジネスイベントと、揮発性のシステムイベントをどのように再構成して、フレームワークのアーキテクチャ上に位置付けていくのか考慮中です。

状態機械

イベントの発生を受けて、発生したイベントとリソースの状態の組に対して、適合するアクションを実行するというのが、オブジェクト指向の動的モデルの基本的な考えです。 イベント駆動型のアプリケーションでは、この状態機械による動的モデルの記述と実行が重要な意味を持ってくることになります。残念ながら現状のオブジェクト指向言語では、状態機械のサポートを行っていないのでプログラムで直接記述することができません。 このメカニズムをフレームワークで実現したいというのもg3の目的の一つです。 プログラミング言語でサポートされていない必須機能をフレームワークで実現するわけですが、Scala DSLによるアプリケーションインタフェースを用いるので、ある意味新しい専用言語を導入するのと同じインパクトがあります。 状態機械の記述方式と上流モデルとの連携方法について、前述のイベント駆動のメカニズム、記述方法と合わせて検討中です。

スケーラビリティ

クラウドアプリケーションでは、スケーラビリティも重要な要件です。すべてのアプリケーションがフルにスケーラビリティを追求する必要はありませんが、必要に応じてスケーラビリティを確保する手段をモデリング、アプリケーションアーキテクチャ、フレームワークといった様々な層の技術の中に事前に織り込んでおく必要があります。 g3はスケーラビリティ確保の手段として非同期メッセージング、KVSのサポートを行っています。 非同期メッセージングとして前述したようにREST指向のメッセージを粗粒度コンポーネント間でパイプライン的に流通させるモデルをとっています。現在はまだ実現できていませんが、CPS(Continuous Passing Style;継続渡しスタイル)的な処理の記述や、メッセージキューを媒介にした非同期処理などを取り込める構造になっています。 また、データストアのスケーラビリティについてはKVSを包含したデータアクセス基盤を用意しました。 KVSも、Redisのように本当のKey/Value対のデータストアもありますしGoogle App Engine DataStoreのようにISAM的なデータ構造を持っているものもあります。以上の点から、KVSといってもISAMデータ構造、要するに単純な表形式のデータ構造を扱うようにしています。 クラウドアプリケーションでは、一貫性重視の用途にはRDBMS、スケーラビリティ重視の用途にはKVSを使い分けることになります。その場合、RDBMSとKVS間でデータを持ちまわる処理も普通に行われるのでKVSとRDBMSに対する統一アクセス法が欲しくなります。 また、このアクセス法はREST指向アーキテクチャ上で使用することになるので、RESTとのシームレースに連携できることが必要です。 g3では上記の要件を満たす統一アクセス法をサポートしました。REST指向アーキテクチャに沿って普通に処理を記述するだけで、特に意識することなくKVSを使用することができます。(もちろん、KVSをスケールさせるためにはキーの選定など特別な考慮が必要になりますが、疎通レベルでは簡単に使えるというのも重要です。) なお、SQLを直接使用したアクセスも可能ですし、Javaを直接使ってJDBCを使用することもできるので、必要に応じて使い分けすることもできます。

各種機能

実用上有用な機能として以下の機能を実装しました。
  • Web Socket
  • Ext-JS対応
  • 各種クラウドサービス用ドライバ(Twitter, Google Calendar, Dropbox, Evernote)
こういった、各種Web技術、クラウドサービスの取り込みは、必要に応じて順次行っていく予定です。

スコープ外の機能

g3の提供する機能という切り口とは別に、g3がスコープ外としている機能、後回しにしている機能が分かれば、g3の目的・意図が明確になると思います。 以下の機能はフレームワークの直接のスコープ外としています。
  • Web MVCフレームワーク
  • テンプレートエンジン
Web MVCフレームワークやテンプレートエンジンは、まだまだ改良すべき点はあるでしょうが、基本的には枯れた技術であり、再発明する必要性が薄いことが一つ。それより重要なのが、HTML5/CSS3によるAjaxやiOS/Androidといったスマートデバイスの興隆によって、技術の重要性が低くなりそうということです。 レガシー技術はそのまま再利用することとして、イベント駆動や非同期コンポーネントフレームワークといった既存の技術ではカバーしていない技術の実現を中心に開発を進めています。 通常のWebアプリケーションを開発する場合にg3を用いる場合には、既存のWebフレームワークと併用することになります。

次の段階で取り組むもの

以下の機能は、次の段階で取り組む予定にしています。
  • DI(Dependency Injection)
  • OSGi
  • JMX
  • Android
  • TDD/BDD
実用化フェーズでは必要なのは分かっているものの、基本機能が定まっていない段階で取り組むのは時期尚早ということで、将来の拡張を意識しつつも当面の開発項目からは落としています。

2011年6月29日水曜日

クラウド温泉@小樽

正式の広報はまだですが、8月27日(土)/28日(日)にクラウド温泉@小樽が開催される予定です。温泉に浸かりながら、クラウドを肴に語り合いましょう、という企画です。

ここ数年取り組んでいた、ScalaによるDSL駆動開発について、開発していたツールが形になってきたので、クラウド温泉でお披露目する予定です。

DSL駆動開発の軸となるのがScala DSLコンパイラSimpleModelerです。当初は、SimpleModelerでモデルからクラウドプラットフォームのAPI上に対して直接クラウドアプリケーションの生成を試行していたわけですが、適切なアプリケーションフレームワークがないと自動生成もままならない、ということが分かりクラウドプラットフォーム向けにRESTメッセージングフレームワークであるg3を開発しました。

g3はAtomPubメッセージによるRESTを軸としてコンポーネントの連携動作を行うイベント駆動フレームワークです。

g3はデータストアアクセスを含めてTomcatなどのWebサーバとGoogle App Engineの両方で動作します。できるだけ同じプログラムを色々なクラウド・プラットフォームで動作させることがg3の目的の一つです。

また、クラウド時代にはUIが、伝統的なWeb UIからAjaxベースのGUIやスマートデバイスに移行することになります。このところ仕事でAndroidに取り組んでいたこともあり、Android向けのプログラム生成の基盤となるフレームワークg4を作ってみました。

Androidのアプリケーションアーキテクチャは、以前「Androidのアーキテクチャ」で考えてみましたが、これをさらに発展させプログラムの自動生成に対応したものを実装しました。

クラウド温泉のセッションでは、SimpleModelerからg3を使ったGoogle App Engineアプリケーションと、g4を使ったAndroidアプリケーションの自動生成のデモを行う予定です。自動生成したAndroidアプリケーションとGoogle App Engineはもちろん連携動作します。

といいつつ、g3アプリケーション(App Engine)とg4アプリケーション(Android)の自動生成はこれから作るので(汗)、実際にデモまで辿りつけるかは分かりませんが、クラウド温泉を励みにして取り組んでいく予定です。

2011年5月31日火曜日

データストアのアクセス特性

4月の中頃に書いていた記事の続きなんですが、ずいぶん間が空いてしまいました。そこでは、すこしモデリングの話題に寄り道しましたが、ドメインモデルとデータストアのマッピングの話題に戻りたいと思います。

ドメインモデルとデータストアのマッピングを考える上で重要なのがデータストアの分類です。データストアの分類軸として、色々なものが考えられますがその一つとして、データアクセス特性があります。

クラウドプラウドプラットフォームにおけるデータストアの性能特性と、アプリケーションのニーズの対応関係を整理し、アプリケーションの用途に合わせた性能特性を持つデータストアを選択することが重要になります。

RDBMS登場後は、データストアの選択もRDBMS一本でよかったのですが、クラウドプラットフォーム、NoSQLといった新しい技術の登場で、データストアの選択、あるいは使い分けがアーキテクチャ選択上、重要な要因になってきました。

そこで、データストア選択のための評価軸を整理しておくことにしました。現時点で思いつくのがアクセスパターン、アクセス方式、アクセスサイズです。他によいものあれば随時追加していきたいと思います。


アクセスパターン


データストアに対するアクセスパターンとして以下の分類を使用します。


read-only
参照のみ。
read-write
参照と更新。
read-mostly
ほとんど参照、まれに更新。
write-only
書き込みのみ。

read-writeが一番汎用的ですが、スケーラビリティの達成が困難になります。

その他の3つのアクセスパターンは、read-writeよりもアクセスの自由度が限定される分、スケーラビリティを高くする事ができます。

read-onlyは文字通り参照のみのアクセスで、更新処理はありません。このパターンでは、データの複製を必要な数だけ用意することができるので、スケーラビリティを高めることができます。

read-mostlyは分散技術でよく用いられる呼び方で、ほとんど参照のみで、たまに更新があるという特性です。この特性と、データの一貫性は厳密に制御しないという方式を組み合わせるとキャッシュを広範囲にわたって配布、共有できるのでスケーラビリティを大幅に向上させることができます。

write-onlyはアプリケーションからは書込みのみを行うパターンです。ログなどが典型例ですが、CQRSといったアーキテクチャではデータ投入にこのアクセスパターンを用います。

read-writeでは、データの一貫性を保つための制御に大きなオーバヘッドあり、write-onlyとすることでこのオーバヘッドを避けることができます。


アクセス方式


アクセス方式として以下の分類を使用します。


sequential
シーケンシャルアクセス
random
ランダムアクセス

sequentialは、データを先頭から最後尾まで連続でアクセスするアクセスパターンです。先頭から最後尾までの連続アクセスも、さらに索引順と格納順に分けることができますが、現在は索引を持つのが普通(あるいはカラムナDB)なので考えないことにします。

randomは、データを一部をランダムにアクセスするアクセスパターンです。

OLTPはランダムアクセスが中心ですが、バッチ処理やOLAP/BIはシーケンシャルアクセスのニーズが高くなります。

データストアを選択する上で重要なのは、ランダムアクセス性能とシーケンシャルアクセス性能は、多くの場合相反する性質ということです。このため、アプリケーションの特性に応じてデータストア製品を選択するが必要となります。あるいは、RDBMSにおけるインデックスのチューニングといった作業で同じデータベースシステムをシーケンシャルアクセス向けやランダムアクセス向けにチューニング仕分けることも可能です。いずれかの手段で、適切なデータストアを確保することになります。


アクセスサイズ


アクセスサイズとして以下の分類を使用します。


record
データをレコード単位でアクセス
portion
データの一定範囲をアクセス
bulk
データの一定範囲/全体を一括してアクセス

recordはレコード単位でのデータアクセスです。1レコードから数レコードの範囲を想定しています。

portionは一定範囲でのデータアクセスです。データの選択を行った後、一回の転送で取得できる範囲(1000件程度)を想定しています。

bulkはデータの一定範囲あるいは全体を一括アクセスします。データ全体スキャンあるいは選択条件に合致する多量のレコードを一括してアクセスすることを想定しています。

2011年4月15日金曜日

ユースケース

コラボレーションタスク監視起票は、Twitterでの注目発言の発生を監視し、注目発言が発生したときに、この発言に対応するためのタスクを起票するコラボレーションです。



アプリケーション開発におけるモデリング作業の中でこのコラボレーションをどのように抽出してくるのかというのが論点の一つになります。これは、利用者の要求とコラボレーションの結びつけるモデル要素が必要なことを意味しています。

いうまでもなく、このモデル要素とはユースケース(use case)です。ユースケースは、システムの振舞いモデルであると同時に利用者の目的と、目的を達成するための物語を記述したモデル要素です。

ユースケースは、アクターの目的をアクターとシステム間のインタラクションの列として記述します。通常は、自然言語によるシナリオとしてこのインタラクションを記述します。

UMLでは、ユースケースを具象化したものがコラボレーション、コラボレーションを抽象化したものがユースケースという関係になります。


以下の図はコラボレーションイベント監視タスク起票を利用者の要求と結びつけるユースケースであるユースケースイベントを監視して対応するタスクを起票するを追加したものです。ユースケースイベントを監視して対応するタスクを起票するは、利用者視点での利用者とシステムのインタラクションをシナリオとして記述します。

そして、コラボレーションイベント監視タスク起票はこのユースケースイベントを監視して対応するタスクを起票するを実現(realize)する、という関係(relationship)になるわけです。(UMLでは、ユースケースをコラボレーションが実現するという関係を、実現シンボル(典型的にはインタフェースをクラスが実現の用途で使用)で記述できるのですがJudeではサポートしていないようなので、依存(dependency)で記述しています。)



開発の手順としては、問題領域のドメインモデルをざっくりと作りつつ、利用者の要求をユースケースとしてモデル化。ユースケースを実現するためにドメインモデルを調整、という流れになります。

ここで、ユースケースとドメインモデルをつなぐモデル要素としてコラボレーションが登場することになります。このコラボレーションをコードの自動生成の枠組みに結びつけることができれば、自動生成のターゲットが大きく広がります。

SimpleModelerでは、ユースケース、コラボレーションをScala DSLで記述して、(同じくScala DSLで記述した)ドメインモデルと結びつけるような方向でユースケースからコラボレーションを経てドメインモデルに至るトータルなモデリングプラットフォームを提供したいと考えています。

ユースケース周りは、ロバストネス分析など色々とおもしろい話題があるので、SimpleModelingでの扱い、SimpleModelerでのサポート方針といった切り口でブログでも取り上げていきたいと思います。

2011年4月14日木曜日

コラボレーションと拡張点

コラボレーションイベント監視タスク起票は、Twitterでの注目発言の発生を監視し、注目発言が発生したときに、この発言に対応するためのタスクを起票するコラボレーションです。




話のそれついでにコラボレーションに関連するモデリング上の話題を続けます。
コラボレーションの導入はコードの自動生成が重要な目的です。
コード生成を行うためには、模型的な意味で図形が配置されているだけでは情報量は不十分で、より詳細な情報を記述しなければなりません。
このような情報の一つに前回の記事で説明した「ドメインルールはTemplateMethodの骨組みのスロットに差し込む拡張部。アプリケーションの構成定義画面でパラメタの変更が可能」といったものがあります。
つまり、ドメインルールのところで、カスタマイズが可能なわけですが、コラボレーション側でもカスタマイズが必要な場合があります。
この目的に、UML標準部品である拡張点(extension point)を用いることができます。拡張点は(通常はユースケースに使うのですが)コラボレーションのインタラクションの一部を拡張するためのスロット的なポイントです。拡張点名を記述し、それに対する具体的な拡張のコラボレーションを拡張(extend)関係で記述します。
拡張点を用いて、コラボレーションイベント監視タスク起票のより詳細な情報を記述した図が以下のものです。


コラボレーションイベント監視タスク起票は、拡張点イベント監視を持っており、ここに拡張を実現するコラボレーションとしてコラボレーション注目発言拡張を指定しているので、注目発言を監視するコラボレーションとして動作する、というわけです。(補足ですが、この図の趣旨からは、注目発言ピックアップの対象がTwetterとなっているのは、DoainRule注目発言ピックアップの設定、ということになります。
この他にもコラボレーションをカスタマイズするメカニズムとしてステレオタイプ、タグ付き値を使用することができます。
UMLでカスタマイズを記述するとこの図のような形になるわけですが、これでは情報が不足ではないか、と思われるかもしれません。
実際のところ、その通りで、本当にコードの自動生成を行うということであれば、相当量の情報をタグ付き値などを使って設定しなければなりません。
つまるところ、UMLでモデルを記述する場合でも、結局のところ情報の過半数はテキスト情報として入力しなければならないわけです。そうであれば、始めからテキスト形式の言語で記述したほうがよい、というのがSimpleModelerのアプローチです。
テキスト情報から、上記の図レベルのものはプログラムで生成することが可能です。どのコラボレーションにどのような拡張点があるのか、といった情報をざっくり見るにはツールが生成した図を見れば十分であり、わざわざモデリング時に手で入力する必要はないわけです。
SimpleModelerでは、コラボレーションとドメインルールによって、アプリケーションの振舞いのプレースホルダーを記述し、ここから実行フレームワーク上で動作するコード(フレームワークの設定情報になるかもしれない)を生成するという方向で、アプリケーションの振舞いをコードに落とすことを考えています。

2011年4月13日水曜日

コラボレーション

ドメインモデルにおけるロジックの抽出、記述方式について検討しています。
前々回にドメインモデルの例として以下の図を作成しました。これは、ServiceEvent、ServiceResourceの導入がクラウド向けの工夫ですが、基本的には従来からあるドメインモデルの静的構造側面を記述しています。



前回は、ドメインオブジェクトとしてDomainRuleを導入することで、ドメインロジックをドメインモデルとして記述できるようにしました。それが以下の図です。



ドメインルールはあくまでもルールを記述するオブジェクトなので、記述するロジックも受動的なものになります。何かの処理の中で、ドメインが内包しているルール(たとえば消費税の計算)を使用するというのが、ドメインルールの基本的な使い方になります。
つまり、受動的なロジックであるルールを駆動する能動的なロジックが必要になるわけです。この能動的なロジックの記述はドメインモデルではなく、アプリケーションモデルとしてモデル化するのがSimpleModelingのアプローチです。アプリケーションモデルは、アプリケーションの利用者が目的を達成するための構造、振る舞いを記述するモデルです。アプリケーションモデルは、アプリケーションの文脈(プロダクトライン、ユースケースファミリなど)として定義されているドメインモデルを操作して、利用者の目標(目的を具体化した物)を達成していきます。SimpleModelingのモデリングアーキテクチャにおけるドメインモデルとアプリケーションモデルの位置付け、関係についてはいずれ説明したいと思います。
ここでは、アプリケーションを構成する能動的なロジックは基本的にアプリケーションモデルで定義するのが基本的な考え方として検討を進めます。
アプリケーションロジックの網羅的な抽出や詳細化はアプリケーションモデルを作成する段階で行うとしても、ドメインモデルの段階でもある程度の抽出は可能です。また、ドメインモデルの構造もアプリケーションモデルから利用可能な形に調整する必要があるので、ドメインモデルを作成する段階で代表的なアプリケーションロジックを記述しておくのは有用です。
アプリケーションロジックの抽出をこの段階で行うのはやや早い感もありますが、代表的な物を明確にしておくことは、モデルの目的を明確化することになるので、分かっている範囲で行っておくのがよさそうです。
アプリケーションロジックについては、ユースケース分析の中で網羅的に抽出し、ドメインモデルに反映していくことになります。
この目的で導入するモデル要素がコラボレーション(collaboration,協調)です。コラボレーションは、UMLの標準モデル要素でオブジェクト間のメッセージの送受信であるインタラクション(interaction)を(目標を達成する単位で)束ねたものです。(UML1とUML2でコラボレーションの定義が変わってしまったのですが、ここではUML1的な意味で使っています。)
コラボレーションイベント監視タスク起票を追加したドメインモデルは以下の図となります。ボクの使っているJudeではコラボレーションシンボルが使えないみたいなのでユースケースシンボルにステレオタイプcollaborationを記述して代用しています。(UMLのコラボレーションシンボルは破線の楕円です。)



コラボレーションタスク監視起票は、Twitterでの注目発言の発生を監視し、注目発言が発生したときに、この発言に対応するためのタスクを起票するコラボレーションです。このコラボレーションがアプリケーションロジックに落とし込まれていくことになります。
この段階でコラボレーションを記述する目的の一つは、ドメインモデルの精度を高めるという目的に加えて、自動生成の対象を抽出という目的もあります。このあたりは、ドメインルールを導入するのと同じ目的です。
つまり、コードの自動生成のプレースホルダーとしての役割、さらにフレームワークと連動できるコラボレーションは、実行可能レベルのコード生成が可能になるでしょう。
以上のようにドメインモデルを振る舞いの視点でみるとコラボレーション、ドメインルール、ドメインエンティティの3階層で構成されます。この階層をモデル要素の関係を実装レベルで考えてみると以下のようなイメージになります。

  • コラボレーションはTemplateMethodパターン的な処理部。
  • ドメインルールはTemplateMethodの骨組みのスロットに差し込む拡張部。アプリケーションの構成定義画面でパラメタの変更が可能。
  • ドメインエンティティはデータベースに格納されるデータまたはサービスとやり取りするデータ。

2011年4月12日火曜日

ドメインルール

前回はエンティティ間の静的な関係(relationship)を定義する以下のドメインモデルを作成しました。モデル駆動開発によるコードの自動生成でも、この範囲の自動生成はすでに実用化または実用化の射程距離内です。



ドメインモデルの重要な軸は(このモデルが典型的な例ですが)問題領域の静的な構造の記述です。従来のアプローチは、データベースに格納するデータとデータ間の構造が中心でしたが、ServiceEventやServiceResourceの導入によって、クラウド上のイベントやリソースもスコープ内に取り込もうというのがここまでの試みです。
クラウドアプリケーションのニーズに対応した形で自動生成の対象範囲が広がりますが、データの範囲をスコープにしているという意味では従来路線を踏襲した漸進的なアプローチともいえます。もう一段、コードの生成範囲を広げられないでしょうか。
モデル駆動開発の問題点の一つは、オブジェクトモデルとしてロジックを宣言的、形式的に記述する方法が確立されていない点です。UMLできちんと記述できるのは状態機械図まで。シーケンス図/コミュニケーション図(旧コラボレーション図)は一応演繹的にも記述できることになっていますが、実用上はインタラクションのインスタンスを具体例として記述するものと考えた方がよさそうです。OCL(Object Constraint Language)は有用ですが、文字通り制約を記述するための言語なので適用範囲が限られます。MDA(Model Driven Architecture)の基盤となるxUML(Executable UML)はこの部分をAction Language (MDA的にはAction Semanticsとその実現言語)で補完していますが、Action Languageは状態機械との連動がオブジェクト指向的には進化であるとはいえ(大多数のオブジェクト指向型言語がそうであるように)手続き型折衷オブジェクト指向プログラミング言語みたいなものであり、現時点の技術では手続き型プログラミング言語を導入しないとロジック記述の問題は解決しないと考えてよさそうです。(いずれこの部分を関数型言語や論理型言語が埋めていくことになるでしょうが、まだまだ先の話です。)
どちみちプログラミング言語的な記述が必要なのであれば、ActionLanguageを使うより、使い慣れているScalaやJava(その他お好みの言語)でロジックは書きたいところです。
そこで、コードの自動生成の対象範囲を広げるための仕掛けとしてSimpleModelingが用意しているドメインモデルのモデル要素がドメインルール(ステレオタイプDomainRule)です。ドメインルールは、ドメインに存在するロジック、責務の中でルールとして扱うと適切なものをオブジェクト化したものです。ドメインルールは以前から導入済みですが、クラウド時代でも引き続き有効なドメインオブジェクトです。
ドメインモデルの中でロジックを記述する方法としては、エンティティオブジェクトのオペレーションとして定義する方法もあります。ドメインルールとはケースバイケースで使い分けることになります。
オペレーションとドメインルールの選択は以下の項目を目安にするとよいでしょう。

  • アプリケーションのカスタマイズ項目になりそうなものはドメインルール。
  • 複数のドメインオブジェクトを横断的に操作するものはドメインルール。
ドメインルールを追加したドメインモデルは以下の通りです。


以下の3つのドメインルールを追加しています。
注目発言ピックアップ
Twitterからツイートを検出するルール
タスク生成
タスクを生成するルール
対応選択
Twitterのツイートから対応を選択するルール
DomainRule対応選択は、DomainRuleタスク生成と合成(composition aggregation)の関係になっており、タスク生成の一部として動作します。
この例では、ドメインルールからドメインイベントやドメインリソースに対する使用(use dependency)を行っていますが、逆にドメインイベントやドメインリソースからドメインルールへの使用(use dependency)もありえます。例えば、消費税計算ルールを請求書発行イベントから使用するといったケースです。
ドメインルールは、ドメインモデルの中でルールを特定するプレースホルダーの役割を担います。前述したようにUMLではロジックを記述できないので、ドメインモデルでできることはここまでという割り切りです。
ただし、ステレオタイプやタグ付き値、関連端(association end)といった修飾パラメタによって生成するコードを特定することができる可能性があります。
たとえば、DomainRuleタスク生成はfactoryというステレオタイプが付いていますが、適切なパラメタを与えればファクトリオブジェクトの実装に落とし込むことは技術的に可能でしょう。
実行プラットフォーム上で動作するフレームワークによって、相当な種類のドメインルールに対応する機能の受け口を実現できるはずです。このようにプラットフォームで対応済みのルールをドメインモデル上で使用することで、ドメインモデルからコード生成できる範囲が格段に広がるでしょう。
また、自動生成には至らなかった場合でも、ロジックのプレースフォルダーとしての存在意義は十分にあります。少なくても、アプリケーションに組み込むためのSPI(Service Provider Interface)といったものの自動生成は可能で、これだけでもずいぶん違うはずです。
ボクが開発しているDSLコンパイラSimpleModelerとマッシュアップフレームワークg3では、このような連携ができることを目指しています。

2011年4月11日月曜日

ドメインモデルを具体的に考えてみる

ドメインモデルとデータストアのマッピングについて検討を始めたところで、話がだいぶそれてきましたが、もう少しそれてみたいと思います。
DomainResourceとServiceResourceを導入したので、試しにDomainResourceとServiceResourceが混在するドメインモデルについて具体的に考えてみました。
以下の図はTwitterのタイムラインを監視して、何らかの基準に沿ったツイートを検出し、何かの対応をすることを管理するタスクを起票するアプリケーションのドメインモデルです。タスクはNozbeのタスクとBacklogの課題として2つのサービスに同時に作成します。



こういったNozbeのタスクやBacklogの課題といった「概念」上は抽象の中に隠蔽されるオブジェクトをPIM段階のドメインモデルでどう扱うのか、というのが論点の一つ、というのがここまでの話でした。DomainResourceとServiceResourceを導入したメタモデルでは、抽象「概念」として隠蔽したいケース、具象「概念」として明示したいケースの両方に対応できるというのがミソです。
この図は具象概念として明示したいケースになります。
ドメインモデルでタスクとして扱うものは、実際には主にNozbeとBacklog上で利用者からの操作を行うことを想定しています。しかし、NozbeとBacklog間の同期やアプリケーション側での操作の目的で、自前のデータベースで管理するオブジェクトも必要になります。このオブジェクトがDomainResourceタスクです。そして、タスクからNozbeTaskBacklog課題を集約(aggregation)します。
一方、アプリケーションが扱うイベントとしてDomainEventタスク起票とServiceEventTweet発生を定義しています。よりモデルの汎用化を進めるならServiceEventTweet発生に対応するDomainEventTweet発生を定義して抽象化しておくというアプローチも考えられます。
ドメインモデルの記述対象のスコープにも色々な切り口がありますが、一般的なのはデータベースに格納するデータの範囲です。この図の場合、DomainEventタスク起票, DomainResourceタスクがこれに当たります。
モデル駆動開発によるコードの自動生成でも、データベースに格納するオブジェクトの範囲の自動生成はすでに実用化されており、安定状態にある技術です。つまり、DomainEventタスク起票, DomainResourceタスクは自動生成のスコープ内に入ってきます。
しかし、ドメインモデルの記述がこの範囲であれば、自動生成の対象範囲もここまでということになります。
ServiceResourceやServiceEventといった外部サービスが提供するドメインオブジェクトを導入する目的は、もちろんドメインモデルの精度を上げることもありますが、実利的にはサービスが生成するイベントやリソースに対応するコードの自動生成を行える可能性が出てくることが大きいでしょう。
ServiceResourceやServiceEventをドメインモデル上で扱うことによって、twitterのストリームを監視したり、RESTで各種サービスのリソースに対してアクセスをしたりといったコードを生成することは技術的には十分射程範囲です。こういった、アプリケーションの部品として何回も何回も繰り返し作り続けられている処理を定型化して、自動生成の対象にできることが、モデリングという要求と実装の中間に位置する作業を行う具体的な価値であり、大きな動機です。このスコープを広げるために、ドメインモデルのメタモデルの拡張と、メタモデルと実装とのマッピングを進めていくことが重要となってきます。