ラベル ofad の投稿を表示しています。 すべての投稿を表示
ラベル ofad の投稿を表示しています。 すべての投稿を表示

2020年8月31日月曜日

SimpleModel

簡単なWebサイトの開発などはDBスキーマ設計+プログラミングで十分なケースも多いと思いますが、複雑な業務や高度なユーザー経験を実現する場合は、何らかの分析設計を行い、その結果を元に実装に落とし込む必要があります。この分析設計手法の最右翼がOOAD(Object-Oriented Analysis and Design)です。

しかし、OOADは難しい技術で独学でマスターするのはなかなか大変です。

独学でマスターすることが大変な理由として、そもそもOOADがソフトウェア工学の集大成という位置付けのものなのでカバーする技術分野の範囲も広く難易度が高いということがあります。その上で独学に適したよい教科書がないということもあります。また、モデリングをアシストするためのツール類の不足も大きな問題です。

以前、大学でモデリングを教えていた時、授業やゼミで使えそうな教科書がないことが悩みのタネでした。

一つは具体的なモデリングの手順を解説した本。UMLの文法解説の本は多いのですが、最上流から実装までの流れを順を追って解説している本がなかなかありません。

また、UMLはすべての応用に適用できるように仕様が巨大でチューニング項目も沢山ありますが、ターゲットのシステム開発向けに必要最小限の大きさにカスタマイズして使用することが想定されています。この「ターゲットのシステム開発向けに必要最小限の大きさにカスタマイズ」してあるUMLのカスタマイズ仕様、いわゆるプロファイルが定義されている本はほぼないと思います。つまり、UMLの記法を学んだだけではUMLを活用することはできないわけです。

そこで中小規模の業務アプリケーションをターゲットに整備したメタモデルとしてSimpleModelを設計しました。

SimpleModelをベースにこれらの問題を解決するために授業やゼミで使える教科書の目的で以下の2つの本を書きました。

UMLを記述言語として書いたものが『上流工程UMLモデリング』、マインドマップを記述言語としたものが『マインドマップではじめるモデリング講座』です。

『上流工程UMLモデリング』が教科書、『マインドマップではじめるモデリング講座』がゼミでのワークショップ用という位置付けです。

ソフトウェア構築技術の進化

SM2008を作成したのが2008年前後ですが、すでに12年の月日が流れており、ソフトウェアの構築技術にも大幅な変化がありました。

特に以下の3つの分野での大きな進展があります。

  • クラウド
  • AI
  • DX(Digital Transformation)
クラウド

クラウドの登場によって大きく変わったのは、大規模アクセス・大規模データのアプリケーションを簡単に構築できるようになった点だと考えています。大規模アクセス・大規模データのアプリケーション構築するための部品が多数用意され、運用管理コストを含めたコストが安価に提供されるようになりました。

逆に言うと大規模アクセス・大規模データの業務アプリケーションを開発する必然性が生まれたわけで、スケールアウトや非同期処理・分散処理を達成するための技術力が求められるようになりました。

それに伴い、以下のようなアーキテクチャが登場してきました。

  • CQRS
  • Event Sourcing
  • Microservice

これらのアーキテクチャに対して、業務アプリケーションのモデリング上でどのように対峙していくのかが論点になるでしょう。

AI

業務アプリケーションの開発においてもAIは無視できない存在になってきました。

ただ、業務アプリケーションそのものがAIのエンジンを開発するということはなく、既存のAIエンジンを使用し、数理モデルを新規開発または既存モデルの改良という形で利用していくことになるケースがほとんどだと思います。

AIも大きく従来型の知識ベースや推論エンジンによるAIと、最近流行の機械学習型の2種類があります。

前者については、AIシステムをコンポーネントやサブシステムとしてモデル化し、利用方法を考えていく形になるでしょう。後者については利用方法を設計するのに加え、学習に必要な情報の収集方式についても検討する必要がでてきます。

DX

DXはもともと「ITの浸透が、人々の生活をあらゆる面でより良い方向に変化させる」という意味とのことですが、ソフトウェア開発の文脈では、企業活動のあらゆる側面をIT化して、企業の形態をIT中心に変換する、というようなニュアンスで語られることが多いと思います。

DXの文脈では、単なる業務アプリケーションの開発ではなく、企業活動全体を俯瞰した企業システムの構築の一環として業務アプリケーションを開発することになります。業務アプリケーションの設計ではなく、企業システム全体をモデル化した上で、その中に組み込まれる1コンポーネントという形での設計になってくるでしょう。

こうなってくると、プログラミング主導の開発では不十分で、全体像を俯瞰し、ターゲットの開発スコープを明確にするモデルの作成が必要なのは明らかです。

SimpleModel 2020

このように、SimpleModelを開発した2008年当時から見るとシステム開発を取り巻く要素技術は大幅に進化しています。この技術進化を取り込むためにSimpleModelの拡張を行うことにしました。その内容については本ブログでこれから検討していくことにします。

前述の書籍で使用しているSimpleModelをSimpleModel 2008年版と呼ぶことにします。そして、このブログでこれから検討していく新しい時代背景に対応したSimpleModelをSimpleModel 2020年版と呼ぶことにします。また簡易表記はSimpleModel 2008年版をSM2008、SimpleModel 2020年版をSM2020とします。

記述言語

前回説明したとおり、モデルコンパイラであるSimpleModelerの記述言語はSmartDoxのDSL基盤上に構築されています。すなわち、節による木構造、表による表構造を使用してのモデル定義と、自然言語による説明文を自然な形で混在させることができます。

文芸的プログラミング(Literature Programming)ならぬ文芸的モデリングということができます。

ツール

OOADによるモデリングを実務に適用するために重要なことは使いやすくて実効性のあるツールによるアシストです。

従来からUMLエディアなどのツールは存在しましたが、実務にモデリングを適用させたり、モデリング教育を効果的に行うといった目的には十分な機能を提供しきれていなかったともいます。

モデリングで作成するモデルは大きく以下の2つに分けることができます。

  • 実行可能モデル
  • 青写真モデル

実行可能モデルは、作成したモデルがプログラムとしてそのまま動いたり、プログラムを自動生成できる、といった形でプログラム開発に直接連携できるモデルです。

一方、青写真モデルはあくまで参考として使える情報であり、実際のプログラムには手作業で開発することになります。

従来モデリングが活用されてこなかった理由の一つは、せっかく苦労してモデルを作っても青写真モデル止まりだったことが大きいと思います。

モデリング活用の阻害要因であるこれらの問題に対応するため、2008年以降、Scalaを使って以下のプロダクトを整備してきました。

SmartDox
文書処理系
SimpleModeler
モデルコンパイラ
Kaleidox
アクション言語
Arcadia
Webフレームワーク
PreferCloudPlatform
クラウド・アプリケーション・プラットフォーム

これらのツールは現在SM2008向けになっていますが、SM2020のメタモデルをベースに拡張していく予定です。

まとめ

業務アプリケーション向けメタモデルであるSimpleModelの最新版SimpleModel 2020について紹介しました。

SM2020の具体的な内容については本ブログで順次検討していきたいと思います。

次回はSM2020を検討する上での論点を整理したいと思います。

2016年11月30日水曜日

よこはまクラウド勉強会: OFP & OFAD Deep Dive with Reactive Streams

Qcon Tokyo 2016で「オブジェクト‐関数型プログラミングからオブジェクト‐関数型分析設計へ~クラウド時代のモデリングを考える」と題してOFAD(Object-Functional Analysis and Design)についてお話させていただきました。

テーマはOFADですが、OFADの前提としてモダンなFP(Functional Programming)を前提としたOFP(Object-Functional Programming)の知識、さらに基本的なOOADの知識が必要なので、非常に広範囲の内容を圧縮して詰め込んだ形になってしまいました。

そこで、もう少し時間を取って説明することができればよいと思っていたのですが、横浜クラウド勉強会 で機会を頂くことができました。

QCon Tokyoでは50分に収めましたが、こちらの方は少し脱線しながら2時間程度のセッションになりました。その後、Reactive Streamsのハンズオンという構成です。

Reactive Streamsの立ち位置

QCon向けの資料をまとめていて感じたのはReactive StreamsがOFP(Object-Functional Programming)のキーテクノロジーではないか、ということです。

Reactive Streamsは以下の2つの方向性があると考えています。

  • 即応性、スケーラビリティのためのメカニズム
  • FP(Functional Programming)の適用範囲を広げる

ここでのFPは純粋関数型プログラミングを意図しています。

前者は今後のクラウドアプリケーションの方向としては当然の方向性です。この方向性を追求する場合には、FPの純粋性を犠牲にしても実行時性能やバックプレッシャーなどのプロトコル拡張を追求することになります。

一方、後者はFPの純粋性は守りながら、FPを外部入出力や大規模データ処理、ストリーミング処理に適用することを目指すものです。

Reactive Streamsは即応性やスケーラビリティという点で注目されていますが、ボクの関心事はそのことよりもFPの適用範囲を広げることに大きく寄与するのではないかという点です。ボクは現時点では後者を重視しているので、(Akka Streamsではなく)scalazと組み合わせて使えるscalaz-streamを愛用しています。

セッションでもお話したOFPのコツはつまるところOOP(Object-Oriented Programming)とFPを適材適所で、ということですが整理すると以下の方針になります。

  • アルゴリズム系の副作用を伴わない処理はFP
  • コンポーネントのファサード部(API/SPI)はOOP
  • OOADのモデリングの実現部はOOP
  • その他はできる限りFP

「できる限りFP」にしたい理由は以下のものです。

  • バグの発生率が圧倒的に少ない。
  • アルゴリズムを効率よく記述できる。
  • 並列/並行/分散処理時代への助走。
  • 将来の証明プログラミングへの備え。

現時点での最大の魅力は「バグの発生率が圧倒的に少ない」がリファクタリングに大きく寄与する点です。長期間持続的に開発を続けるシステムはこの理由だけでOFPを採用する価値があると思います。そして、長期的には「並列/並行/分散処理時代への助走」、「将来の証明プログラミングへの備え」という観点から必須のプログラミング・スタイルになるとすると、先取りして取り入れておきたいところです。

ここで問題となるのは「その他はできる限りFP」です。

FPには外部入出力処理や状態を持った処理の記述が大変という問題があります。スライドに書いたように、モナドの登場で外部入出力処理や状態を持った処理の記述が「困難」から「可能」になったのは大きな前進ですが、OFPの観点からはかならずしも「便利」とは言えないと思います。またScalaの特殊事情として文法的な制約でHaskell程簡明には書けないということもあります。

「できる限りFP」とはいえ便利でないものは使わなくなるのが道理で、対策をとらないと結局OOPの部分が大きく残ってしまう事になってしまいます。

Reactive Streamsがキーテクノロジーではないか、という期待はまさにこの問題の解消に大きく寄与するのではないかという点です。

scalaz-streamが提供するProcessモナドも一種のモナドですが、概念的に分かりやすいのとリソース管理やフロー制御という伝統的なOOPによる外部入出力でも難しかった問題が解決されています。実際に製品開発で使ってみてこの便利さを体感しました。

このような経験からReactive Streamを開発の基盤とすることで、OFPの多くの部分をFP側に倒すことが可能になるのではないかと期待しているわけです。

Reactive Streamsハンズオン

そんな思いもあり、セッションの後半はReactive Streamsのハンズオンにしました。以下にソースコードがあります。

このハンズオンのソースはセッションの朝に急ごしらえで作ったものなので、動作確認などはあまりできておらず、当日会場で調整しながら使用したものです。その点はご留意下さい。

ここでの趣旨は、(即応性、スケーラビリティではなく)FP成分を重視したReactive Streamsについて、プログラミングレベルでのイメージをつかんでいただくことです。

以下簡単に説明します。

ステップ1: Hello World

ステップ1はscalaz-streamのHello World的なプログラムです。scalaz-streamの最小限の使い方を体験することが目的です。

mainメソッドの第1引数に入力ファイル名を、第2引数に出力ファイル名を指定すると入力ファイルを出力ファイルに複写するプログラムです。

ハンズオンの問題は以下になります。

package handson.reactive

import scalaz._, Scalaz._  
import scalaz.concurrent.Task  
import scalaz.stream._

object Step1 {
  def main(args: Array[String]) {
    val in = args(0)
    val out = args(1)
    val t = converter(in, out)
    t.run
  }

  def converter(in: String, out: String): Task[Unit] = ???

  def converterProcess(in: String, out: String): Process[Task, Unit] = ???
}

converter関数とconverterProcess関数を実装します。

実装例

ステップ1の実装例です。

package answer.reactive

import scalaz._, Scalaz._  
import scalaz.concurrent.Task  
import scalaz.stream._

object Step1 {
  def main(args: Array[String]) {
    val in = args(0)
    val out = args(1)
    val t = converter(in, out)
    t.run
  }

  def converter(in: String, out: String): Task[Unit] =
    converterProcess(in, out).run

  def converterProcess(in: String, out: String): Process[Task, Unit] =
    io.linesR(in).pipe(text.utf8Encode).to(io.fileChunkW(out))
}

converter関数はconverterProcess関数から返されたProcessモナドをrunしてTaskモナドにします。

converterProcess関数はscalaz-streamのパイプラインをProcessモナドという形で構築して返します。

ステップ2: Monadic API

ステップ1はscalaz-streamの最小限の使い方でした。

ステップ2ではscalaz-streamのパイプラインが、通常のMonadic APIとして使えることを体験します。ここでいうMonadic APIはJavaでいう所のStream APIで、FunctorやMonadによるパイプラインをベースにしたAPIです。(Streamは色々な意味付けがされていてミスリーディングな面もあるのでここでは本ブログではMonadic APIと呼んでいます。)

package handson.reactive

import scalaz._, Scalaz._  
import scalaz.concurrent.Task  
import scalaz.stream._

object Step2 {
  def main(args: Array[String]) {
    val in = args(0)
    val out = args(1)
    val player = args(2)
    val t = converter(in, out, player)
    t.run
  }

  def converter(in: String, out: String, player: String): Task[Unit] =
    converterProcess(in, out, player).run

  def converterProcess(in: String, out: String, player: String): Process[Task, Unit] =
    io.linesR(in).
      map(toRecord).filter(isPlayer(player)).map(toYear).map(_ + "\n").
      pipe(text.utf8Encode).to(io.fileChunkW(out))

  def toRecord(s: String): Vector[String] = s.split(",").toVector

  def isPlayer(player: String)(record: Vector[String]): Boolean = ???

  def toYear(record: Vector[String]): String = ???
}

ステップ1との違いは以下の3つの関数をmapコンビネータ、filterコンビネータでパイプラインに組み込んでいることです。

  • toRecord関数
  • isPlayer関数
  • toYear関数

これらの関数が通常の関数であること、そして簡単にProcessモナドのパイプラインに組み込むことができる点を体験するのが目的です。

実装例

ステップ2の実装例です。

package answer.reactive

import scalaz._, Scalaz._  
import scalaz.concurrent.Task  
import scalaz.stream._

object Step2 {
  def main(args: Array[String]) {
    val in = args(0)
    val out = args(1)
    val player = args(2)
    val t = converter(in, out, player)
    t.run
  }

  def converter(in: String, out: String, player: String): Task[Unit] =
    converterProcess(in, out, player).run

  def converterProcess(in: String, out: String, player: String): Process[Task, Unit] =
    io.linesR(in).
      map(toRecord).filter(isPlayer(player)).map(toYear).map(_ + "\n").
      pipe(text.utf8Encode).to(io.fileChunkW(out))

  def toRecord(s: String): Vector[String] = s.split(",").toVector

  def isPlayer(player: String)(record: Vector[String]): Boolean =
    record.lift(0) == Some(player)

  def toYear(record: Vector[String]): String =
    record.lift(1) getOrElse "Unknown"
}

toRecord関数、isPlayer関数、toYear関数はごく普通の関数です。これらを簡単にProcessモナドのパイプラインに組み込んで動作することが確認できました。

ステップ3: フロー制御

ステップ3は一種のフロー制御であるチャンク化の体験です。

通常のMonadic APIは構造上フロー制御を行うことができませんが、Processモナドではpipeコンビネータなどの仕組みによってフロー制御を可能にしています。

ここが通常のMonadic APIに対するReactive Steramsの優位点で、外部入出力を効率的に処理することを可能にする拡張となっています。

package handson.reactive

import scalaz._, Scalaz._  
import scalaz.concurrent.Task  
import scalaz.stream._

object Step3 {
  def main(args: Array[String]) {
    val in = args(0)
    val out = args(1)
    val t = converter(in, out)
    t.run
  }

  def converter(in: String, out: String): Task[Unit] =
    converterProcess(in, out).run

  def converterProcess(in: String, out: String): Process[Task, Unit] =
    io.linesR(in).
      map(toRecord).
      chunk(1000).map(groupByYear).pipe(process1.unchunk).
      map(toYear).
      pipe(text.utf8Encode).to(io.fileChunkW(out))

  def toRecord(s: String): Vector[String] = s.split(",").toVector

  def toYear(record: Vector[String]): String = ???

  def groupByYear(records: Vector[Vector[String]]): Vector[Vector[String]] = ???
}

注目するポイントはパイプライン中の「chunk(1000).map(groupByYear).pipe(process1.unchunk)」の部分です。パイプライン中のchunk関数とpipeコンビネータで適用したprocess.unchunk関数によって、パイプラインを流れるデータを1000個単位でチャンク化しています。

mapコンビネータで指定されているgroupByYear関数はチャンク化したデータに対する処理を行うようになっています。

チャンク化は入出力処理で性能向上するための必須処理なので、これを自動的に行なってくれる部品が提供されていることは生産性に大きく寄与します。

実装例

ステップ3の実装例です。

package answer.reactive

import scalaz._, Scalaz._  
import scalaz.concurrent.Task  
import scalaz.stream._

object Step3 {
  def main(args: Array[String]) {
    val in = args(0)
    val out = args(1)
    val t = converter(in, out)
    t.run
  }

  def converter(in: String, out: String): Task[Unit] =
    converterProcess(in, out).run

  def converterProcess(in: String, out: String): Process[Task, Unit] =
    io.linesR(in).
      map(toRecord).
      chunk(1000).map(groupByYear).pipe(process1.unchunk).
      map(toYear).
      pipe(text.utf8Encode).to(io.fileChunkW(out))

  def toRecord(s: String): Vector[String] = s.split(",").toVector

  def toYear(record: Vector[String]): String =
    record.lift(1) getOrElse "Unknown"

  def groupByYear(records: Vector[Vector[String]]): Vector[Vector[String]] = {
    case class Z(years: Set[String] = Set.empty, result: Vector[Vector[String]] = Vector.empty) {
      def +(rhs: Vector[String]) =
        rhs.lift(1).fold(this) { year =>
          if (years.contains(year))
            this
          else
            Z(years + year, result :+ rhs)
        }
    }
    records.foldLeft(Z())(_ + _).result
  }
}

toYear関数、groupByYear関数を普通に実装するだけです。データをチャンク化する処理はscalaz-streamが提供している部品が自動的に行なってくれます。

フロー制御の例としては以下の記事が参考になると思います。

ステップ4: ストリーム

ステップ4はReactive Streamsでストリーム処理を行う課題です。

まず準備としてストリームを生成する部品EventProcessorを作ります。

package handson.reactive
  
import scalaz.concurrent.Task
import scalaz.stream._  

object EventProcessor {
  val q = async.unboundedQueue[String]

  val eventStream: Process[Task, String] = q.dequeue
}

buildメソッドはscalaz-streamによるパイプラインを作って、これをバックグラウンドで起動します。

最後に呼んでいるstimulusメソッドはEventProcessorを使って入力ファイルの内容を1行づつ読み込み1秒のインターバルでストリームに送出します。

package handson.reactive

import scalaz._, Scalaz._  
import scalaz.concurrent.Task  
import scalaz.stream._
import scala.concurrent.Future  
import scala.concurrent.ExecutionContext.Implicits.global
import scalax.io._
import scalax.io.JavaConverters._
import java.io.File

object Step4 {
  def main(args: Array[String]) {
    val in = args(0)

    val stream = EventProcessor.eventStream
    build(stream)
    stimulus(in)
  }

  def build(stream: Process[Task, String]) {
    Future {
      val t = converterProcess(stream).to(io.printLines(System.out))
      t.run.run
    }  
  }  

  def converterProcess(source: Process[Task, String]): Process[Task, String] =
    source.map(toRecord).map(toYear)

  def toRecord(s: String): Vector[String] = s.split(",").toVector

  def toYear(record: Vector[String]): String = ???

  def stimulus(in: String) {
    val queue = EventProcessor.q
    val input = new File(in).asInput
    input.lines() foreach { line =>
      queue.enqueueOne(line).run
      Thread.sleep(1000)
    }
  }
}

scalaz-streamのパイプラインはconvertProcess関数で作成します。このパイプラインにはmapコンビネータでtoYear関数を接続しています。

本課題ではこのtoYear関数を実装します。

toYear関数は通常の関数です。この通常の関数をscalaz-streamのパイプラインに組み込むだけでFPによるストリーム処理が記述できるのを体験するのが本課題の趣旨です。

実装例

ステップ4の実装例です。

package answer.reactive

import scalaz._, Scalaz._  
import scalaz.concurrent.Task  
import scalaz.stream._
import scala.concurrent.Future  
import scala.concurrent.ExecutionContext.Implicits.global
import scalax.io._
import scalax.io.JavaConverters._
import java.io.File

object Step4 {
  def main(args: Array[String]) {
    val in = args(0)

    val stream = EventProcessor.eventStream
    build(stream)
    stimulus(in)
  }

  def build(stream: Process[Task, String]) {
    Future {
      val t = converterProcess(stream).to(io.printLines(System.out))
      t.run.run
    }  
  }  

  def converterProcess(source: Process[Task, String]): Process[Task, String] =
    source.map(toRecord).map(toYear)

  def toRecord(s: String): Vector[String] = s.split(",").toVector

  def toYear(record: Vector[String]): String =
    record.lift(1) getOrElse "Unknown"

  def stimulus(in: String) {
    val queue = EventProcessor.q
    val input = new File(in).asInput
    input.lines() foreach { line =>
      queue.enqueueOne(line).run
      Thread.sleep(1000)
    }
  }
}

ストリーム処理の例としては以下の記事が参考になると思います。

ステップ5: ストリーム&フロー制御

ステップ5はステップ4で作成したストリームに、自前のフロー制御を組み込むという課題です。

この課題はちょっと難しいので、時間が余った人向けを想定しています。

package handson.reactive

import scalaz._, Scalaz._  
import scalaz.concurrent.Task  
import scalaz.stream._
import scala.concurrent.Future  
import scala.concurrent.ExecutionContext.Implicits.global
import scalax.io._
import scalax.io.JavaConverters._
import java.io.File

object Step5 {
  def main(args: Array[String]) {
    val in = args(0)

    val stream = EventProcessor.eventStream
    build(stream)
    stimulus(in)
  }

  def build(stream: Process[Task, String]) {
    Future {
      val t = converterProcess(stream).to(io.printLines(System.out))
      t.run.run
    }  
  }  

  def converterProcess(source: Process[Task, String]): Process[Task, String] =
    source.map(toRecord).pipe(toYear)

  def toRecord(s: String): Vector[String] = s.split(",").toVector

  def toYear: Process1[Vector[String], String] = ???

  def stimulus(in: String) {
    val queue = EventProcessor.q
    val input = new File(in).asInput
    input.lines() foreach { line =>
      queue.enqueueOne(line).run
      Thread.sleep(1000)
    }
  }
}

ステップ4のようにscalaz-streamのパイプラインにmapコンビネータで関数を合成する場合は、通常の関数を使うことができますが、フロー制御を組み込むことはできません。

フロー制御を組み込むためにはProcessモナドを引数にして、Processモナドを返す関数を作成し、pipeコンビネータでパイプラインに組込みます。

Processモナドを直接操作するので少し難しいプログラミングになりますが、フロー制御を自分で操作できるので色々な用途に適用することができるようになります。

ストリーム処理&フロー制御の例としては前項と同様に以下の記事が参考になると思います。

この課題は時間切れでボクも実装例を作ることができませんでした。

まとめ

OFPにおけるReactive Streamsの位置付けについて、FPを重視する方向性から整理してみました。

その上でプログラミングレベルでReactive Streamsを把握するための仕掛けとしてハンズオンの資料を紹介しました。

もちろんOFPのReactive StreamsはOFADにも、少なからず影響するはずです。この点はまだ考えが整理できていませんが、ブログで継続して検討していきたいと思います。

2016年10月28日金曜日

QCon Tokyo 2016

QCon Tokyo 2016で「オブジェクト‐関数型プログラミングからオブジェクト‐関数型分析設計へ~クラウド時代のモデリングを考える」と題してお話させて頂きました。


上記の個人用のSlideShareは文字化けが取りきれないので、きちんと読みたい方は会社のSlideShareの方を見ていただくとよいと思います。上記スライドもPDFをダウンロードしたものは文字化けしていません。

Reactive Streams

今回のテーマであるOFADについては別の記事で考えたいと思いますが、今回スライドを作っていて改めて以下のことを感じました。

  • Reactive Streamsは次のブレークスルーの起点になるかも

FP(Functional Programming)でI/Oを扱う技術としてIOモナドがありますが、その発展形として以下の2つの技術があります。

  • Operationalモナド(scalazではFreeモナド+α)
  • Processモナド(scalaz-streamの場合)

製品開発の中で、どちらの技術も使ってみましたがProcessモナドの方が圧倒的に楽なんですね。

Processモナドでは入出力などの作用に対する処理部は始端(source)と終端(sink)をパターンにしたがって実装すれば簡単に実現できます。source, sinkの抽象度が適切なので一度作った部品は色々な用途で再利用できます。また、(フロー制御用の)状態を管理する処理をProcessモナド内に組み込むためのメカニズムも持っていますが、これの実装もそれほど難しくありません。

一方、Operationalモナドは作用に対する処理はインタープリタとして実装して、自然変換のメカニズムでOperationalモナドの実行時に割り当てる必要があります。このメカニズムによりDI(Dependency Injection)の機能も実現できるので、その点では素晴らしいのですが、インタープリタという大きな仕掛けを作らないといけないので、単に入出力をしたいという目的には重たすぎると感じました。Scalaの場合はOOP側で自由に入出力できるので、このメカニズムを使ってまでFP化をすすめるニーズはなかなかないかも、という感触です。

このような感触が得られている中で、スライドページ「OOPとFPの協業」をまとめながら考えたのは「簡単に使えるReactive Streamsを使えば、多くの処理をFP化できる」ということです。

セッション内で説明しましたが、FPの方がOOPに対して、高品質(バグが出にくい)というメリットがあり、さらに持続的開発の中核作業であるリファクタリングで圧倒的な優位性を発揮する、というのがボクの主張点です。

このような観点からFPの範囲を大きく広げるReactive StreamsはOFP(Object-Functional Programming)にとって本質的に重要な技術なのではないかと感じました。

Reactive Streamsは大規模分散処理やストリーミング処理向けの専用機能という観点で取り上げられることが多いと思いますが、それだけではなく日常的なOFPにとって中軸となるプログラミング・モデルとなりうる点がより重要であると感じました。ここにさらに大規模、高頻度、ストリーミングがおまけでついてくるという切り口でのアプローチがよいと思います。

フィードバック

セッション後に2つほどフィードバックを頂きました。

OOPとFPの関係

スライドページ「OOPとFPの関係」ではFPでは以下のことが実現できないと説明しました。

  • 状態の更新
  • 動的束縛によるポリモーフィズム
  • 大規模開発(?)

この点について専門家の方から以下のような趣旨のフィードバックを頂きました。(文意はボクの理解によるものなので、正確な意図とはずれている可能性があります。)

  • 「動的束縛によるポリモーフィズム」と「大規模開発」は理論的に解決されており実用言語での実績もある。

「動的束縛によるポリモーフィズム」については、ボクの理解ではここがOOPとFPの違いだと思っていたので、理論的に解決されていて、さらに実用言語での実績もあるというという点は意外でした。

コンパイル時に継承関係が全て確定していれば、動的束縛部分を直和(+pattern matching)に落とし込むことはできるとは思いますが、共通ライブラリで定義したクラスの継承や、分割コンパイルといったニーズがOOP的には重要なので、この問題をどのように解決しているのか興味のあるところです。

大規模開発については、セッションではあまり深く触れていませんが、ボクのイメージでは以下のようなことがあってFP的には大変なのではと推測しています。

  • コンポーネント内に状態を持てないので、大規模システムの部品として利用するには大きな制約があるのではないか。
  • 「動的束縛によるポリモーフィズム」の問題でOOP的な継承が使えないとすると、API/SPIといったインタフェースを使ったコンポーネント部品化に制約がおきるのではないか。
  • OSGiなどを使った動的ローディングによるplugin機構は実現可能か。

機会があれば、このような観点から技術評価をしてみたいと思います。

どちらの問題も、Haslkellではできていないようですし、Scalaのロードマップにもないと思うので、Scalaで利用できるようになるのは当面なさそうとはいえそうです。

Reactive Streamsでできること

セッション後の質問時に以下のような指摘を頂きました。(文意はボクの理解によるものなので、正確な意図とはずれている可能性があります。)

  • 「HaskellでReactive Streamsを使って線形代数を行おうとしたがメモリが足りなくて動かなかった。セッションではReactive Streamsを使って大規模演算ができるとしているがいいかげんな主張ではないか?」

まず「HaskellでReactive Streams」についてはボクの経験外の話でもあり、Haskellライブラリの実装上の問題の可能性もあるのでここでは取り上げません。

線形代数に関しては、全データをメモリに展開して大規模な行列演算を行おうとすると、どのような技術を使ってもメモリ不足を起こすはずなので、この点でご質問の意図がよくわかりませんでした。

線形代数による大規模行列演算の応用にはReactive StreamsではなくSparkのMLLibのようなアプローチがよいのではないかと思います。

Reactive Streamsについては、ごくざっくりいうと「作用を始端と終端に切り離し、フロー制御ができるようになったイテレータ」なので、イテレータでできる範囲で大規模データ処理ができるということです。

具体的には、一定のウィンドウサイズの範囲でデータの一部をメモリに読込み、その範囲で処理を行ってメモリを開放する、という処理を繰り返す動きになります。

このような動きなので、原理的にはどのような大規模なサイズを扱っても大丈夫はなず、ということでセッション中は「1TBでも」とお話したのですが、この点に違和感を感じられたようです。

原理的には1TBでも大丈夫と思いますが、実証実験したわけではないので、この点は明らかにしておきます。

製品開発の中でReactive Streams(scalaz-stream)を大規模メール配信、大規模PUSH配信処理の実装で非常に便利に使っており、ほとんど問題も出ていないことから実用上は全く問題ないのでは、というのがボクの実感としてあり、その点を「1TB」として表現したしだいです。このサイズ表現問題が難しいのは10GB程度だと、現在のハードウェアではメモリに載せてしまうことも可能なので、わかりやすいインパクトのある例として使うのにはちょっと難しいことがあります。次の機会があれば、このあたりの表現を工夫したいと思います。

2016年10月11日火曜日

Object-Functional Analysis and Designふりかえり

クラウド時代のアプリケーション開発について、「クラウド・アプリケーション・モデリング」、「クラウド・アプリケーション開発のモデル体系」と考察してきました。

クラウド・アプリケーション開発では、実装時のプログラミングで「関数」が重要な構成要素となってきています。そうなると、この「関数」を上流のモデリングでどのように扱っていくのかということが重要な論点になります。

このような観点から、上流のモデリングから実装時のOFP(Object-Functional Programming)まで、オブジェクトと関数を融合させ一気通貫にまとめた開発方法論をModegramming StyleではObject-Functional Analysis and Design(OFAD)と呼んでいます。

Modegramming Styleでは2012年ぐらいからOFADについて考察を進めてきました。クラウド・アプリケーションのモデリングの検討を進めていく上で、OFADが一つの軸となると思います。このOFADについて、2012年に要求開発アライアンスでのセッション『Object-Functional Analysis and Design: 次世代モデリングパラダイムへの道標』向けにまとめたものがあります。

今回はこの2012年版OFADのふりかえりを行い、検討を進めていくうえでの論点整理を行いたいと思います。

Object-Functional Analysis and Design 2012

クラウド・アプリケーションの開発方法論を整備していくためには「関数」を理解した上で、関数とオブジェクトの関係を整理しないといけないという動機もあり、OFPの有力言語であるScalaを2008年から使い始めました。

ある程度「関数」とOFPについて勘所がつかめてきたところで、2012年に『Object-Functional Analysis and Design: 次世代モデリングパラダイムへの道標』というセッションのタイミングで一度まとめるタイミングがありました。

このセッションの内容は以下にまとまっています。

また関連して以下のような考察を行っています。

ふりかえり

今の目でOFAD 2012をチェックしてみましたが、それほど違和感はなく、以下の基本的な考え方については変更はありませんでした。

  • オブジェクトと関数の使い分け
  • オブジェクトと関数の連携
  • デザインパターン(代数的構造、圏論)
  • Domain-Driven Design (DDDD)

ただ、オブジェクトと関数の連携方法は2012年当時よりも手持ちの選択肢が増えたと思うので、その辺りは反映していきたいところです。

このタイミングで再検討したいのが以下の項目です。Reactive Streamsを始め、2012年以降、要素技術が大きく進化しているのでこれらの技術を取り込んだ上で新しい枠組みで考えてみたいと思います。

  • DSL
  • データフロー

以下のアーキテクチャ的な話題については2012年以降、特に大きな動きはなかったと思います。これらについてはOFADの再検討の中で対応を考えていきたいと思います。

  • EDA
  • DCI
  • CQRS

OFAD 2012以降の技術動向

OFAD 2012以降に起きた技術的な大きなムーブメントとしては以下の2つがあります。後者は我々の提案ですが、最近注目されているServerless Architectureに通じるところがあると思います。

  • Reactive Streams
  • Application Cloud Platform
Reactive Streams

より広い枠組みとしてはFunctional Reactive Programming(FRP)という切り口もありますが、Reactive Streamsの方が現状にあっていると思うのでReactive Streamsの用語を使います。

Modegramming Styleではscalaz-streamを中心にReactive Streamsについても考察を行ってきました。

また幾つかのセッションでお話させていただいたのでスライドとしてもまとめました。

純粋な数学的な計算はよいとして、システムの振る舞いをFunctional Programming(FP)でどのように記述するのかという点がFP実用化の重要な論点だと思いますが、モナドベースのReactive Streamsが一つの解としてブレークスルーの起点となりそうです。

そのような意味でOFADでの関数を考える上でReactive Streamsは重要な論点になります。

分析モデルの段階で、Reactive Streamsに対応するモデル要素を見つけることができればモデルから実装まで一気通貫でつなげるルートを確保することができます。

Application Cloud Platform

OFAD 2012の後、クラウド時代のアプリケーション開発ではクラウド・プラットフォームが重要な構成要素になると考え、その製品化を行う活動をしてきました。その成果として「Prefer Cloud Platform」をリリースすることができました。

Prefer Cloud PlatformのようなクラウドプラットフォームをModegramming StyleではApplication Cloud Platform(ACP)と呼んでいます。Prefer Cloud Platform自体もScalaによるOFPによって実装されていますが、この開発の中でOFPに関するノウハウ、OFADに対するヒントを蓄積することができました。

またACPによってアプリケーション開発の大きな部分を省略することができることが期待できます。こうなると、モデリングの目的はビジネスとアプリケーションの連携方法の分析とシステムの拡張方法の分析設計に絞られます。このような文脈の中での開発方法論ということもクラウド時代の開発方法論であるOFADに求められる点といえます。

まとめ

「クラウド・アプリケーション・モデリング」を起点に進めている考察は『Object-Functional Analysis and Design: 次世代モデリングパラダイムへの道標』によって始動したOFAD 2012を最新技術動向やApplication Cloud Platform(ACP)の活用を前提に、2016年版OFADとして再構築を行うという目的のものです。

この検討を進めるためのベースとしてOFAD 2012を簡単にふりかえり、論点整理を行いました。

このフィードバックを活かして、次回はOFADのモデル体系について考えてみたいと思います。

お知らせ

10月24日に開催される「QCon Tokyo 2016」で以下のテーマでお話させていただくことになりました。

  • オブジェクト‐関数型プログラミングからオブジェクト‐関数型分析設計へ~クラウド時代のモデリングを考える

現在検討しているOFADについてのチュートリアル的な内容になる予定です。

2015年9月30日水曜日

関数型プログラミング技術マップ2015

『圏論の歩き方』を読んで少し理解が進んだので、関数型プログラミング技術マップを更新しました。「関数型プログラミング技術マップ2014」の2015年版です。

以下の点を改良しています。

  • Curry-Howard対応をCurry-Howard-Lambek対応に拡張
  • 直観主義述語論理を追加して直観主義命題論理を包含
  • カルテジア閉圏とトポス(圏)を追加
  • 直観主義命題論理⇔カルテジアン閉圏、単純型付ラムダ計算⇔カルテジアン閉圏間の関係を追加

この図は関数型プログラミング(FP: Functional Programming)を取り巻く理論を整理することを目的としています。

誤解があるといけないので補足しておきますがFPを行うために必須の理論という意図ではありません。

業務アプリケーションをFPで開発するという目的には、圏論も論理学も抽象代数も必須知識ではなく、MonoidやMonadのプログラム上での使い方をパターンとして覚えておけば十分だと思います。代数的データ型もcase classの筋の良い使い方を覚えてしまえば大丈夫です。(もちろんFPとして筋の良いプログラミングをするためには、こういった理論を知っておいた方がよいのは言うまでもありません。)

一方、ビジネス・モデリングや要件定義といった上流のモデリングとFPとの連携を考えていく際には、こういった理論も取り込んでいく必要がありそうです。

OOAD(Object-Oriented Analysis and Design)はUML/MOF(Meta Object Facility)によるようなメタモデルの議論はあるものの、現実的には数学や情報科学とは一定の距離がある現場ベースのベストプラクティスの集大成といえます。OOADによるモデルをOOPで実装するという目的には、数学や情報科学の知識は(あった方がよいのは確かですが)必須スキルという形ではなかったと思います。

しかし、実装技術としてFPが導入されると上流モデルとFPとの連携が論点となってきます。

こういった「FP成分」を取り込んだOOADをOFAD(Object-Functional Analysis and Design)と呼ぶとすると、このOFADでは数学や情報科学をベースとした数理モデルを部分的にでも取り込んでいくことになるかと思います。

一つの切り口としては、OOADのモデルが静的構造モデル、動的モデル、協調モデルから構成されるとすると、(記述力が弱い)協調モデルを数理モデルベースのデータフローで記述し、静的構造モデル、動的モデルを数理モデルとの連続性を担保できるように強化する、といった戦略が考えられます。

このためのモデルとしてどのようなものを採用するのがよいのか分かりませんが、Curry-Howard対応あるいはCurry-Howard-Lambek対応による直観主義命題論理、単純型付ラムダ計算、カルテジアン閉圏によるトライアングルが中心になることが予想されます。

もちろん、一階述語論理/論理プログラミング(Prologなど)や直観主義高階述語論理/証明プログラミング(Coqなど)といった方向性も有力ですが、Scala&ScalazによるFPでは述語論理は(言語機能的には)スコープ外なので、仮に上流モデルで取り入れたとしてもプログラミングとは不連続になってしまいます。

また、一階述語論理/論理プログラミングや直観主義高階述語論理/証明プログラミングが最終的な解であるにしてもその前提として「Curry-Howard-Lambek対応」の理解は必要です。

そういった意味で、まずは「Curry-Howard-Lambek対応」のスコープで色々と考えていくのがよさそうと考えています。

2015年8月17日月曜日

クラウドアプリケーション・モデリング考

8月7日に「匠の夏まつり ~モデリングの彼方に未来を見た~」のイベントが行われましたが、この中でパネルディスカッションに参加させていただきました。パネルディスカッションでご一緒させていただいた萩本さん、平鍋さん、高崎さん、会場の皆さん、どうもありがとうございました。

パネルディスカッションがよいきっかけとなって、クラウドアプリケーション開発におけるモデリングについての方向性について腰を落として考えることができました。このところFunctional Reactive Programmingを追いかけていましたが、ちょうどモデリングとの接続を考えられる材料が揃ってきているタイミングでした。

パネルディスカッションの前後に考えたことをこれまでの活動の振り返りも含めてまとめてみました。

基本アプローチ

2008年頃からクラウドアプリケーション開発の手法について以下の3点を軸に検討を進めています。

  • クラウド・アプリケーションのアーキテクチャ
  • メタ・モデルと実装技術
  • モデル駆動開発

検討結果は以下にあげるスライドとブログ記事としてまとめていますが、基本的な考え方は現在も同じです。

ざっくりいうと:

  • クラウド・アプリケーションのバックエンドのアーキテクチャはメッセージ方式になる。
  • クラウド・アプリケーションのモデリングではOOADの構造モデル、状態機械モデルを踏襲。
  • 協調モデルの主力モデルとしてメッセージフローまたはデータフローを採用。
  • OOADの構造モデル、状態機械モデルはモデル駆動開発による自動生成。
  • メッセージフローまたはデータフローはDSLによる直接実行方式が有効の可能性が高い。

という方針&仮説です。「OOADの構造モデル、状態機械モデルはモデル駆動開発による自動生成」についてはSimpleModeler、「メッセージフローまたはデータフローはDSLによる直接実行方式」についてはg3 frameworkで試作を行っていました。

ここまでが2010年から2012年中盤にかけての状況です。

ブログ

2012年以降のアプローチ

2012年の後半にEverforthに参画してApparel Cloudを始めとするCloud Service Platformの開発に注力しています。

前述の論点の中で以下の3点についてはApparel Cloudの開発に直接取り入れています。

  • クラウド・アプリケーションのバックエンドのアーキテクチャはメッセージ方式になる。
  • クラウド・アプリケーションのモデリングではOOADの構造モデル、状態機械モデルを踏襲。
  • OOADの構造モデル、状態機械モデルはモデル駆動開発による自動生成。

「メッセージフローまたはデータフローはDSLによる直接実行方式が有効の可能性が高い」については当初はg3 frameworkという独自DSLによる実装を考えていたのですが、Object-Functional Programmingの核となる技術であるモナドがパイプライン的なセマンティクスを持ち、データフローの記述にも使用できそうという感触を得られたため、ScalazベースのMonadic Programmingを追求して技術的な接点を探るという方針に変更しました。

2012年以降ブログの話題がScalaz中心になるのはこのためです。

その後、まさにドンピシャの技術であるscalaz-streamが登場したので、scalaz-streamをApparel Cloudの構築技術として採用し、「メッセージフローまたはデータフローはDSLによる直接実行方式が有効の可能性が高い」の可能性を実システム構築に適用しながら探っている状況です。

今後のアプローチ

現在懸案として残っている項目は以下のものになります。

  • 協調モデルの主力モデルとしてメッセージフローまたはデータフローを採用。

前述したようにメッセージングのDSLとしてはscalaz-streamをベースにノウハウを積み重ねている状況なので、この部分との連続性をみながらモデリングでの取り扱いを考えていく予定です。

また、ストリーミング指向のアーキテクチャ&プログラミングモデルとしては以下のような技術が登場しています。

このような新技術の状況をみながら実装技術の選択を行っていく予定です。

参考: スライド

パネルディスカッションでのポジション宣言的なスライドとして以下のものを作成しました。

この中で6ページ目の「Cloud時代のモデリング」が今回パネルディスカッションのテーマに合わせて新規に作成したものです。

このスライドで言いたいことは、伝統的なスクラッチ開発とくらべてクラウドアプリケーションではプログラミング量が大幅に減るので、要件定義やその上流であるビジネスモデリングが重要になる、ということです。

  • アプリケーションの大きな部分はCloud Service Platformが実現
  • モデル駆動開発によってドメインモデル(静的構造)の大部分は自動生成される
  • Scalaで実現されているDSL指向のOFP(Object-Functional Programming)は記述の抽象度が高いので設計レベルのモデリングは不要
  • Scalaの開発効率は高いのでプログラミングの比重は下がる
補足:Featureモデル

後日スライドのキーワードページに入れておくべきキーワードとしてFeatureモデルがあることに気付いたので、上記のスライドには追加しておきました。

スライドの想定する世界では、クラウドアプリケーションはクラウドサービスプラットフォーム上で動作するため、クラウドサービスプラットフォームが提供している機能とクラウドアプリケーションの機能の差分をモデル化し、このモデルを元に実際に開発する所、カスタマイズで済ませる所などを具体化していく必要があります。この目的にはSoftware ProductlineのFeatureモデルが有効ではないかと考えています。

2015年4月13日月曜日

[OFAD]Everforthのモデル体系

前回「クラウド・サービスのモデリング」で、クラウド・サービスを開発する際のモデリングの枠組みについて考えました。

クラウド・サービスの開発はSPaaS(Service Platform as-a-Service), OFP(Object-Functional Programming), OFAD(Object-Functional Analysis and Design)の3つの要素から構成されるという枠組みを提示しました。

今回は、この枠組の中のOFADを実現するためにEverforthが採用しているモデル体系についてご紹介します。

モデリングの参照モデル

クラウド・サービスのモデリングを議論するための参照モデルとして以下のものを使用します。



この参照モデルでは、モデルは大きく以下の2系統に分かれます。

  • ドメイン・モデル
  • アプリケーション・モデル

ドメイン・モデルは、利用者やサービス提供者、開発者といったステークホルダー間で共有する事業ドメインのモデルです。最上流では用語集にまとめられたドメイン・オブジェクトが、最終的にはデータベースで管理されたり、センサーなどの外部デバイスとして実現されます。

アプリケーション・モデルは、各ステークホルダーの要件を定義し、ここからサービスとして実現する方法を定義するモデルです。ビジネス・ユースケースからユースケースとして定義した物語を、サービスに落としこみます。

クラウド・サービスの設計と実装

分析と設計のアクティビティによって以下の2つのモデルが作成されます。

  • ドメイン・モデル
  • サービス・モデル

この2つのモデルから、サービス・プラットフォーム上にクラウド・サービスを構築する際の、設計と実装を行う際の流れを以下にまとめました。


スクラッチのシステム開発の場合には、この2つのモデルからシステム全体を開発するわけですが、サービス・プラットフォーム上でクラウドサービスを開発する場合、サービス・プラットフォームが提供するDSLに載せる形で開発することになります。

ドメイン・モデルについては、業界全体の現時点での技術レベル的に80%程度は自動生成することを前提にするのが合理的です。サービス・プラットフォームを使う場合は、サービス・プラットフォームが提供するDSLに合わせた実装を自動生成することになります。

サービス・モデルはOFPを用いて実装することになります。この場合も、サービス・プラットフォームが提供するDSLに合わせた実装になります。

Everforthでのモデリング

「モデリングの全体像」と「クラウド・サービスの設計と実装」の参照モデルについて説明しました。この参照モデルにそった形でEverforthで採用しているモデリングの枠組みは以下になります。




モデルは大きくサマリ・モデルと詳細モデルの2つに分かれます。

サマリ・モデル

サービス毎にサマリ・モデルとして以下のモデルを作成します。

  • マインドマップ・モデル
  • WireFrame(WF)
  • API利用一覧
  • サービス記述
マインドマップ・モデル

マインドマップ・モデルは拙著「マインドマップではじめるモデリング講座」のものをベースにしています。ざっくりいうとマインドマップでビジネス・ユースケースとドメイン・モデルを記述するための手法です。

マインドマップ・モデルで作成したモデルは基本的にOOADのモデルなので、必要に応じて本格的なOOADモデリングに展開可能です。

WireFrame

WireFrame(WF)はWebやiOS/Androidアプリ開発で一般的に使われているものを採用しました。画面設計を中心にしたモデルです。ただし、画面とクラウド・サービスが提供するAPIの関係を定義する拡張を行っています。

API利用一覧

API利用一覧は、WFで定義したものも含めて、WebやiOS/Androidアプリケーションからクラウド・サービスのAPI利用方法一覧です。

サービス記述

サービス記述は、開発するサービスの概要情報を定義したものです。

このサービス・プラットフォームのカスタマイズ情報を定義することがサービス記述の重要な目的の一つになっています。

レギュラー・モデル

またサービスの要件が複雑な場合は、必要に応じてレギュラー・モデルとして以下のモデルを作成します。

  • ユースケース・モデル
  • ドメイン・モデル

ユースケース・モデルは拙著「上流工程UMLモデリング」で定義したユースケース一覧とユースケース詳細の2つの帳票を用いています。いずれもExcelやGoogleスプレッドシートなどの表計算ソフトで作成します。ユースケース一覧の作成が主で、必要に応じてユースケース詳細を作成するバランスで運用しています。

ドメイン・モデルはモデル・コンパイラEverforthModelerのDSLとして記述します。DSLはemacs-org形式のプレインドキュメントです。EverforthModelerはDSLのモデルから、サービス・プラットフォームが定義するエンティティ管理のDSLを自動生成します。

ユースケース・モデルを作成することで、自然にドメイン・モデルが整備されます。このドメイン・モデルの実装はモデル・コンパイラで自動生成してサービスに組込みます。そして、このドメイン・モデルを操作するサービスをユースケース・モデルをベースにAPI仕様としてまとめOFPによる実装につなげていきます。

まとめ

EverforthでApparel Cloudを開発する際に使用しているモデル体系についてご紹介しました。

実装技術としてはScalaによるOFPを使用していますが、要件定義や分析といった上流工程におけるモデリングの重要性は通常のエンタープライズ開発と変わるところはありません。ベースとなる方法論としてはOOADが引き続き有力です。

ただ、(1)クラウド・サービス・プラットフォームを前提とする、(2)実装技術で関数型成分が重要になる、という2つの要因によってモデリングの具体的な手法には少なからず影響が出てきます。

このいった点も含めて、クラウド・サービスのモデリングの方法論について、実践の経験をベースに今後もブログで検討を進めていきたいと思います。

2015年4月6日月曜日

[OFAD] クラウド・サービスのモデリング

ここ数年Apparel Cloudの開発に携わっています。

Apparel Cloudはアパレル向けのクラウド・サービスを実現するためのサービス・プラットフォームで、サーバーサイドの実装はScala+ScalazによるMonadic Programmingを採用しています。

また、サービス企画からクラウド・サービス向けの仕様策定にはオブジェクト指向開発の伝統的なモデル体系をクラウド・サービス向けにチューニングしたものを用いています。

実システムの構築にこれらの新しい技術を適用して一通り材料も揃ってきたので、クラウド・サービス開発の枠組みとその要素技術について整理していこうと思います。

今回はモデリング体系の枠組みの整理です。この枠組みをベースに、個々の要素技術の詳細化を行っていく予定です。

枠組み

大枠では以下のような枠組みを考えています。

  • Service Platform as-a-Service
  • Object-Functional Programming
  • Object-Functional Analysis and Design

まず、クラウド・サービスの開発はスクラッチ開発ではなく、Service Platform上でのカスタマイズや追加機能をプラグインとして開発する形を想定しています。

また、プログラミング・モデルはオブジェクト指向と関数型を併用したObject-Functional Programmingです。並列、並行、分散、ストリーミングといったクラウド時代の要件を満たすためには関数型の導入が必須となるためです。

スクラッチ開発ではなくサービス・プラットフォーム上でのカスタマイズ+プラグイン、オブジェクト指向と関数型を併用したプログラミング・モデルという2つの大きな枠組みの変更は、当然ながら要件定義から分析・設計の一連の技術にも影響を与えます。

Service Platform as-a-Service

クラウド・サービスの開発では、スクラッチでシステムを組むというより、既存のサービスを組み合わせた上で、必要な部分だけ開発するという形が基本です。

このアプローチの核になるのがService Platformです。さらにService Platformをクラウドサービスとして提供したものをService Platform as-a-Service(SPaaS)と呼ぶことにします。

SPaaSが提供するプラットフォームを利用することで、クラウド・サービスの開発と運用をより簡単に実現することができます。

Apparel Cloudは、アパレル業界でのO2O用途向けのSPaaSということになります。

本稿を起点とする一連のブログ記事では、SPaaSの具体的な紹介や利用方法というよりも、SPaaSの存在を前提としたクラウド・サービス開発のモデリング(Object-Functional Analysis and Design)やプログラミング・モデル(Object-Functional Programming)を整理し、可能な範囲で体系化していきたいと考えています。

Object-Functional Programming

Object-Functional Programming(OFP)はObject-Oriented Programming(OOP)とFunctional Programming(FP)を融合させたプログラミング・モデルです。

FPはアルゴリズム記述の容易性や信頼性、保守性の向上がメリットですがOOPと比べると以下の問題点もあり、エンタープライズ分野では限定的な利用にとどまっていました。

  • 実行性能の低下
  • メモリ消費の増大
  • 難易度の高いプログラミング・モデル
  • (エンタープライズ的な意味で実績のある)安定した実行環境
  • 開発エコシステム(開発環境、クラスライブラリ、コミュニティなど)

しかし、クラウド時代に入って以下のような目的により積極的に採用する必要性が出てきています。

  • 並列・並行・分散プログラミング
  • 大規模データ処理
  • ストリーミング処理
  • 問合せ処理
  • DSL(Domain Specific Language)

特に、モナド(monad)という新しい概念をベースとしたMonadic Programming(MP)、MPをベースにしたFunctional Reactive Programming(FRP)が、本質的なプログラミング・モデルの変革をもたらします。

さらに、既存のOOPとの併用・融合も新しいテーマとなってきます。

本ブログでは今までも、これらのテーマについてScala+Scalazによるソリューションについて検討してきました。今後も引き続きこのテーマについて検討を進めていきますが、可能な範囲で今回提示した枠組みであるSPaaS、OFADとの関係についても考えていきたいと思います。

Object-Functional Analysis and Design

サービス企画の要求をまとめて、サービス開発に落とし込むためのメソッドはObject-Oriented Analysis and Design(OOAD)が基本になりますが、Service Platform as-a-Service(SPaaS)とObject-Functional Programming(OFP)という2つの中核的な技術変革により要件定義から分析・設計に至る一連のアクティビティにも大きなインパクトが出てくることが予想されます。

OOADについては、以前大学で教えていた時の内容を以下の2冊にまとめています。

現時点でもこの内容がボクとしての結論で、大きな枠組としては変わっていません。

ざっくりいうとボキャブラリとなるドメイン・モデルと物語であるユースケースを起点としたアプリケーション・モデルの二系統のモデルを軸に業務モデルからObject-Oriented Programming(OOP)による実装までの一連の開発アクティビティを一気通貫でカバーしています。

このOOADに対して、SPaaSとOFPの成分をどのように織り込んでいくのかという点が論点となります。本ブログでは、これらの要素を織り込んでOOADを拡張した方法論をObject-Functional Analysis and Design(OFAD)と呼ぶことにします。

まとめ

材料が揃ってきたので、クラウド・サービス開発の方法論の整備を始めることにしました。今回は議論のベースとなる枠組みを整理しました。

Monadic ProgrammingやFunctional Reactive Programmingを中心とするObject-Functional Programmingは、今まさに技術革新が進行中のホットな技術分野であり、本ブログの中心的なテーマとして取り上げてきました。今後も基本的には変わらないスタンスですが、今回提示した枠組みであるSPaaS、OFADとの関係についても併せて考えていきたいと思います。

Object-Functional Analysis and Designは、既存のOOADがベースなので既存のものと大きな違いはない想定ですが、SPaaS成分、OFP成分が入ることで相応の拡張が必要になりそうです。Apparel Cloudでの実践で得られた経験をもとに体系化を進めていきたいと思います。

2012年7月13日金曜日

クラウド温泉3.0@小樽

今年もクラウド温泉の季節がやってきました。

今回もかなり濃い内容になりそうで楽しみです。ustreamなしのオフレコなので、かなり踏み込んだ話を聞けるのもクラウド温泉の醍醐味ですね。

今回は2つほどスライドを用意する予定です。

  • Monadicプログラミング・マニアックス
  • OFP & OFADについて(仮)

連休明けぐらいから、この2つのスライドについて:

で行ったような感じで、準備がてらブログに内容をあげていきたいと思います。この時は、以下のようなまとめを作りましたが、今回も作ることになると思います。

Monadicプログラミング・マニアックス

「Monadicプログラミング・マニアックス」というタイトルにしていますが、内容的にはScalazを使った関数型プログラミング入門という感じになると思います。現時点でScalaとScalazを使っている事自体がマニア、ということで。

内容的には、去年のJJUGで使った「楽々Scalaプログラミング」を起点にMonadicプログラミングの技やプログラミング戦略を色々書いていこうと思います。

http://www.slideshare.net/asami224/scala-9728001

キーワード: モナド、モノイド、型クラス、代数的データ型、永続データ構造、並列プログラミング、SQL。

OFP & OFADについて(仮)

こちらは、二日目午後に小樽商大会議室で行うフリーディスカッション「OFP & OFADについて」のネタ投入用のスライドです。基本的には「Object-Functional Analysis and Design: 次世代モデリングパラダイムへの道標」をそのまま使う予定ですが、OFADについて少し書き足したいこともあるので内容を追加できればと思っています。

このスライド起点の議論はOFADよりのものを想定していますが、これとは別にOOPとFP、OFPを軸にした議論もあると思うのでそちらも楽しみです。OOPもJavaのように実用志向で手続き型寄りのものが主流になっていますが、元祖オブジェクト指向でメッセージングのメタファを持つSmalltalkやJavaScriptのようなプロトタイプベースなど色々なバリエーションがあります。

個人的には業務アプリのプログラミングパラダイムはOOPからOFPにパラダイムシフトしていくと思っているのですが、このタイミングでOOPやFPの原点に立ち戻って議論することで、新しいOFP像を垣間見ることができればと期待しています。

2012年5月29日火曜日

Object-Functional Analysis and Designふたたび

5月28日(月)にJJUG CCC 2012 Springで、『Object-Functional Analysis and Design』のセッションを行いました。予定していたセッションがキャンセルになったので、その代わりにお話させていただいた次第です。

スライド: http://www.slideshare.net/asami224/ofad

内容は基本的に3月19日(月)に要求開発アライアンスで行なった『Object-Functional Analysis and Design: 次世代モデリングパラダイムへの道標』の再演です。3月19日版のまとめは「Object-Functional Analysis and Designまとめのまとめ」になります。

要求開発アライアンスとJJUGではオーディエンスが違うので、事実上新規内容に近い形で見ていただけるのではないかということで、このテーマを選択しました。

前回の経験とオーディエンスの違いを勘案して再構成したのに加えて、いくつか内容の修正を行いました。ここでは、その点について記録しておきます。

再構成

要求開発アライアンスの参加者はモデリングが興味の中心と思われるので、OFADという趣旨からもモデリングの所を厚くしていましたが、今回はJavaプログラマが中心と想定されるので関数型言語のあたりを厚くしてみました。モデリングの所のスライドを減らしているのと、関数型言語のスライド数は増やしたわけではないですが、しゃべる時間を長めにしてみました。

とはいえ、JJUG CCCに参加するエンジニアはエンタープライズ系でモデリングにも興味を持っている方が多いと思われるのと、さらにこのセッションに参加される方はその傾向が大きいと思うので、モデリングに関してポイントとなるスライド(オブジェクトの世界と関数の世界, ユースケースと関数)(参考「オブジェクトの世界と関数の世界」)は残しています。さらに詳しくは「メタモデル」、「Domain-Driven Design (DDD)」あたりが面白いのですが、このあたりは省略しました。

クラウドまわりの応用でCQRSやEDAと、OFADの関係もやりたかったのですが、これはセッション時間の兼ね合いで断念しました。DCI (Data Context Interaction)はトレイトや型クラスの素材という面で面白いのですが、アーキテクチャパターンとしてはちょっと採用しづらいというのが現時点の判断なので削除しました。

新しい現実

前回: 新しい現実

今回: 新しい現実

セッションの問題設定の文脈を提示したスライドを修正しました。前回は「クラウド・プラットフォーム」、「メニーコア」、「メモリDB」でしたが、今回は「クラウド・プラットフォーム」、「メニーコア」、「DSL」にしています。

前回のスライドを作っていた時は:

  • メモリDB→I/Oボトルネックが解消→アルゴリズム勝負←メニーコア

から、関数型へのニーズが高まるというような文脈を考えていたのですが、これよりもDSLの方がはるかに影響が大きいと思うので、今回はDSLにしてみました。

関数型言語の系譜

前回: 関数型言語の系譜

今回: 関数型言語の系譜

内容に変更はないですが、図の見方についての注釈を入れました。

ボク自身は、関数型言語は大昔にLispを触って以来20年ほど空白があるので、客観的な意味での関数型言語の発展史はまったく分かりません。この図はあくまでも、Javaプログラマが2008年に関数型言語に再遭遇した時の心象風景における関数型言語の見え方です。

セッションではその旨を口頭でお話しするわけですが、スライドでの流通もあるので注釈で補足しました。

ボクと同じ世代でLispや人工知能などをかじった後、エンタープライズ系の開発を主業にされている方は、恐らくその時点での関数型言語のイメージのフィルターを通して最近の関数型言語の興隆を理解しようとすると思うのですが、モナド、型クラスという新しい言語機能が入っている現代の関数型言語は、全く別物なのでそのあたりの注意を喚起したいというのが、このスライドの趣旨です。

関数型言語の正しい発展史はボクも興味があるので、URLや書籍をお知らせ頂けると助かります。

ユースケースと関数

前回: ユースケースと関数

今回: ユースケースと関数

ユースケースと関数の関係を定義するメタモデルを前回と今回で修正しました。修正点は以下のものです。

  • OOPから関数へのリンクの元を状態遷移からサービスにした。

「EDAとオブジェクトと関数」で説明したように、EDAアーキテクチャをとりつつ状態遷移モデルは深く考えないのが、現実解ではないかというのが最近のボクの考えです。

この点を加味して、サービスから関数を直結し、状態遷移はサービスの専有下にしてみました。このアーキテクチャは、「オブジェクトと関数の連携(2)」にも沿っています。

このスライドの図を、EDAベースで実現すると「EDAとオブジェクトと関数」の最後の図になります。

CQRS/EDAとOFADを合わせてだいたいこんな感じが落としどころかなというのがボクの現時点での結論です。この図を使ってもよかったのですが、EDAの説明などが時間的に難しいので、モデリング段階の抽象的な枠組みのみにしました。

並列プログラミング

今回: 並列プログラミング

関数型言語というと並列プログラミングなので情報を追加しました。

基本的には:

  • shared mutability
  • isolated mutability
  • immutable

の三段階があって、一番下のimmutableが上策ということです。このimmutableを関数プログラミング方式で実現します。

さらにisolated mutabilityをアクター、shared mutabilityをSTMでハンドリングし、どうしてもダメな場合の最後の手段として伝統的な排他制御の技法(Javaのクラスライブラリが充実した機能セットを提供)用いるのが関数プログラミング流ということになります。

参考情報

要求開発アライアンス版に関する参考情報です。

スライド

要求開発アライアンスで使用したスライドは以下のものです。(PDF出力ツールの関係で、当日は非表示にしたスライドも表示されています)

まとめ

セッション後のまとめは以下の記事になります。

まとめのまとめ

最終のまとめは以下の記事になります。

2012年5月11日金曜日

データフローDSL考 (6) - 並行処理

データフローDSLについてのアイデアメモの続きです。データフローDSLでの並行処理についてです。

データフローDSLを考える上で外せない要素が並行処理です。

今後はますます低クロック&メニーコアの方向にスマートデバイス、サーバーともシフトいくことが予想されます。こういったプラットフォーム上でアプリケーションを高速動作させるためには、アプリケーションが、ネイティブな並列処理プログラムである必要があります。外付けで、部分的というのはダメということです。

もちろん、現行のマルチスレッドプログラミングでは、プログラミングの難易度が高くなりすぎて、一般的なエンタープライズアプリケーション開発の文脈でネイティブな並列処理プログラムを書くことは事実上不可能です。

この目的で期待されているのが関数型プログラミングですね。

データフローDSLを設計する際もこの要因を取り込んでいく必要があります。

関数型プログラミングが並行プログラミングの本命であるなら並行処理の記述に独自のセマンティクスを持ち込むより、関数型プログラミングのセマンティクスを自然な形で取り込んでいくのが自然なアプローチになります。

並行プログラミングの2つの形

並行プログラミングを考える上では、以下の2つの異なったユースケースがあることを意識しておく必要があります。

  • 大規模演算を並行処理して実行時間を短縮する
  • 非同期事象を扱う

応用編として、大規模演算を並行処理して実行時間を短縮するしつつ、随時非同期事象を取り込んでいく、というのもありそうですが、話が発散しそうなので、まずは考えないことにします。

並行処理で時間を短縮

並行処理で時間を短縮することを考える上で重要なのは、処理の開始と終了は同期型ということです。処理の内部では、複数のコアを同時に使用した並列処理を行う場合でも、外部からの見かけは同期型となるわけです。

このような処理の場合も、内部実装に並列処理を陽に記述しなければならないのが現行のマルチスレッドプログラミングの問題点です。

この問題の解決に期待できるのが、Monadicプログラミングです。Monadicプログラミングでは、普通のパイプライン的な処理を書くと、内部的に自動的に並行実行され、自動的に同期してくれるような振舞いを実現することができます。プログラマは普通のMonadicプログラミングをするだけで自動的に並列処理を手に入れることができるわけです。

パイプライン的な処理を逸脱した複雑なデータフローはMonadicプログラミングで直接カバーできませんが、サービス・バス的なメカニズムで克服できるのではないかというのが、ここまでの議論でした。モデル記述の場合は、パイプライン+サービス・バスで記述したグラフ構造によるデータフロー・モデル全体を並列処理させるようなアプローチもあります。たとえば、モデルをコンパイルしてAsakusa Frameworkに落とし込めば可能です。

フレームワークAPI DSLの場合は、サービス・バスによる非同期処理の部分で、手作業的な並列プログラミングが出てくることになりそうですが、この部分を極力小さくできれば、実用的に使えるのではないかと思います。

非同期事象

後者の非同期事象は、関数型プログラミングではアクターで扱うのが一般的だと思います。

並列プログラミングでは、非同期事象の扱いも重要な項目ですが、データフローDSLという意味ではスコープ外と考えてよいでしょう。

あえて考えるとすると、データフローDSL的には、パイプラインを合成するためのサービス・バスの部分で接点が出てきそうです。サービス・バスのチャネルに外部事象の入力を認めれば、非同期事象を扱う事が可能になります。

Monadicプログラミング

パイプライン記述でどこまで並列処理を記述できるのかという点が大きな論点になってきます。

そういう観点で考えてみたのが「関数型とデータフロー(5)」です。以下の図では、ListモナドをKleisli圏で合成していますが、こういったアプローチで並列処理をシンプルに記述できないか、ということです。

最近、ちょっと面白そうに思っているのがScalazのPromiseをApplicative演算で使用することで並列処理を記述する方法です。これは、いずれこのブログでも紹介したいと思います。

いずれにしても、Monad、Promise、Kleisli、ApplicativeといったMonadicプログラミングの要素が、並列処理の記述に非常に適していると考えています。

まとめ

今回はデータフローDSLと並列処理の関係について、アイデアレベルでざっくり考えました。

Monadicプログラミングは、普通の処理を簡潔に記述できる点が現時点の逐次プログラミングでも有効ですが、並列プログラミング時代に入ってくると、「Monadicプログラミングなしにはまわっていかない」、といったプログラミングの中心軸になるのではないかというのがボクの予測です。

データフローDSLのパイプライン記述部も、これらの要素を活かしたMonadicプログラミング記述によるアプローチを軸に考えていきたいところです。

2012年5月10日木曜日

データフローDSL考 (5) - アイデアメモ

今回はアイデアメモです。

一連のエントリでデータフローDSLの論点、具体例として私家版Asakusa DSLとg3を見てきました。

  • データフローDSLとして、パイプライン方式が有力
  • パイプライン方式の欠点を補う上でサービス・バス方式が有力

その上で、技術的な論点として以下のものがあります。

  • 静的型付け (フレームワークAPI DSL)
  • 並行プログラミング (フレームワークAPI DSL)
  • ロジックの記述 (モデルDSL)
  • フレームワークAPI DSLの統合

また、新技術の取り込みという課題もあります。

Monadicプログラミング

関数型言語的にデータフローをどう扱うのがよいのかということを調べる目的もあって、ここ半年ほどMonadicプログラミングを調べてきました。

Monadicプログラミングの枠組みでパイプラインを構成するのが関数型的なアプローチということは、ある程度予測はしていましたが、これをデータフロー・モデルの記述に適用する際の細々とした留意事項を、クリアできるのかという点を具体的に把握するのが、調査の目的です。

型クラスを用いることで、ScalaネイティブなMonadicプログラミングを維持しつつデータフロー・モデルに適用する事ができそうなことを確認できました。また、純粋なパイプラインだけではデータフロー・モデルは記述できませんが、これはサービス・バス的なアプローチで克服できそうな目処が立ちました。

この点も含めて、Monadicプログラミングの記述方式がデータフローDSLを構成するパイプラインの記述に直接使用できそうな感触を得ました。

型クラスを使うことで、モデルDSLとフレームワークAPI DSLの統合もできるのではないかという期待も持っています。このアプローチの枠組みで、モデルDSLへのロジックの埋込みを実現できれば理想的です。

Monadicプログラミングの記述力

サービス・バス的アプローチでパイプラインを合成してデータフロー・モデルを記述するという方針を採るので、パイプラインでデータフロー・モデルの全てを記述できる必要はありません。

ただ、そうはいってもパイプラインの範囲内でより複雑な構成を記述できると、データフロー・モデルを記述する際の記述力も上がります。

そういう意味で、Monadicプログラミングでどこまでできるのかが、技術的な関心事となります。

分岐と合流

パイプラインは基本的に直線の単線ですが、Arrowを使って分岐と合流のあるデータフローを記述することができます。(「関数型とデータフロー(2)」)



正常系と異常系

パイプラインにモナドを使用することで、正常系と異常系などの文脈を持ちまわることができます。(「関数型とデータフロー(3)」)




また、この技術の応用編になりますが、フレームワーク側で内部的に行う処理もモナドの裏側(join演算)に隠蔽することもできます。このことでパイプラインの記述力が大幅に向上します。

モナドの合成

クレイスリ圏でモナドを合成することができます。(「関数型とデータフロー(4)」)




パイプラインと部品として用意したモナドを合成して、さらに大きな部品を構築することができます。

ちょっと長くなってきたので、次回に続きます。

2012年5月9日水曜日

データフローDSL考 (4) - g3

データフローDSLという観点で、今までScalaで2つの内部DSLをつくってきました。一つはモデル記述の私家版Asakusa DSL、もう一つはg3向けのフレームワークAPIです。

今回はg3向けのフレームワークAPIについて考えます。

g3については、このブログでも色々書いてきました。詳細は以下のリンクを参照してください。

g3

g3はクラウドアプリケーションのサーバーサイドで動作するサービス部向けのアプリケーションフレームワークです。

元々は、SimpleModelerでクラウドアプリケーションの生成を行う際に、現状では適切なフレームワークが見つからなかったので、「威力偵察」的な意味もあって自分で作ってみることにしたものです。

以下の特徴を備えています。

  • REST指向
  • イベント駆動
  • サービス・バス
  • Atom Publishing指向
  • マルチ・プラットフォーム
  • 運用のスケーラビリティ
  • パイプライン・プログラミング
  • 並行処理
  • HTML生成やRDBMSアクセスをドライバモジュールでモジュール化
  • WebSocket
REST指向、イベント駆動、サービス・バス

クラウドアプリケーション・フレームワークとして、まず欲しかったのはREST指向のイベント駆動&サービス・バスです。

RESTイベントをハンドリングするというより、すべてのイベントをRESTイベントとして抽象化して、この抽象RESTイベントでサービスを駆動します。サービスはサービス・バス上で動作し、チャネルを介して疎結合&非同期に連携します。

エンタープライズの世界ではESB(Enterprise Service Bus)が広く用いられています。ESBはアプリケーションレベルの少し粒度の大きいモジュールをターゲットにしたもので、現状ではXMLを用いて組立て情報を記述するのが一般的です。また、モジュールの連携にはMQを想定しています。EIP(Enterprise Integration Patterns)もこのESB上での構築を想定したものです。

g3の提供するサービス・バスは、ESB的なものをよりシンプルにインメモリ指向で使うことを指向しています。ただし、PaaSと連携することで、クラウドスケールのESBとして使用することもできるようなアーキテクチャにしています。

たとえば、Google AppEngineとの連携は現状でも可能です。Google AppEngineはPaaSの先行事例なので、アーキテクチャの確認の意味もあり、g3では当初から対応しています。

まだ未実装ですが、技術的にはJMS経由でMQを介して他のESBとの連携も可能なはずです。

Atom Publishing指向

抽象REST指向をさらに進めて、抽象Atom Publishing指向の要素を加えています。通常は抽象Atomとしてデータ操作して、必要に応じて抽象度の低い抽象RESTデータを操作することを指向しています。

マルチプラットフォーム&運用のスケーラビリティ

g3はマルチプラットフォームと運用のスケーラビリティも念頭において設計しています。抽象RESTイベントという切り口で、外部イベントと内部処理を疎結合にしているのがポイントです。

  • スタンドアロンコマンドとしても動作
  • Servlet
  • Google AppEngine

g3は、CLIのインタフェースを抽象RESTイベントに変換するフロントエンドを持っているので、スタンドアロンのコマンドでも動作できるようになっています。

g3は、普通のServletとして動作させることができます。このため、TomcatやJava EE環境でそのまま利用できます。

g3は、Google AppEngine上で動作させることもできます。g3は、AppEngineのスケーラビリティを活かすようなアーキテクチャになっています。AppEngineのPaaS機能によって、クラウドスケールでのスケーラビリティを得ることができるようになるはずです。

パイプライン・プログラミング&並行処理

チャネルで抽象RESTイベントが発生しますが、この抽象RESTイベントをパイプライン上上で処理するのがg3の眼目の一つです。狭義では、一連のエントリ「データフローDSL考」で話題にしている項目です。

私家版Asakusa DSLは、モデル記述用とということもあって静的型付けにしていますが、g3はイベント駆動のアプリケーションAPIということもあるので動的型付けにして、パイプラインを上を流れるデータをPartialFunctionでマッチしたもののみ処理を行うプログラミング・モデルにしてみました。このプログラミング・モデルでは、複数種類のイベント(正常系イベントと異常系イベントなど)を同じパイプライン上に流すことが可能になります。

「データフローDSL考」シリーズで説明してきたとおり、単純なパイプラインでは、複雑なデータフロー・モデルを記述することはできません。

g3では、この問題を解決するために、サービス・バスを用います。チャネルから抽象RESTイベントで駆動されたパイプラインは、パイプライン上で加工処理を行いながら、他のチャネルに対して新たな抽象RESTイベントを同期発行または非同期発行していきます。パイプライン上での加工処理が完了後、RESTイベントを同期発行した主体に処理結果を返します。

このように、サービス・バスを経由して他のパイプラインと連携するメカニズムを導入することによってパイプライン・モデルの欠点を克服しています。

サービス・バスによってパイプライン・プログラミング・モデルの問題を解決することを含めて、広義のデータフローDSLと考えることができます。g3では、この広義のデータフローDSLという観点でも、パイプライン・プログラミング・モデルがうまく機能することを確認できました。

問題は、非同期発行した処理を回収するための同期機構です。この同期機構を実現するために、g3の内部で重たい処理をしているのですが、g3のユースケースを勘案した上での全体のバランスからするとちょっと重たすぎた感じがあり、改良ポイントと考えています。

HTML生成やRDBMSアクセスをドライバモジュールでモジュール化&WebSocket

抽象RESTイベント、サービス・バス、パイプライン・プログラミングのメカニズムの上でモジュールによる機能拡張のメカニズムを構築しました。色々な機能をこのセマンティクス&DSL的にうまく埋め込めることが確認できました。

評価

g3は、アプリケーションAPIとしてのデータフローDSLとしては色々チャレンジしてみた項目も含めて、概ねうまくいっているかなというのが自己評価です。ポイントは、パイプライン・プログラミング・モデルとサービス・バスをScala DSLでうまく結び付けることができた点です。

ただ、色々と使ってみて以下の点が問題を認識しています。

  • 非同期同期機構が重たすぎる(前述)
  • 可能であれば静的型付け化したい
  • モデル記述DSL(SimpleModeler)との統合がしたい
新技術

g3を作りはじめた2010年初頭からは、Scalaでも色々な技術革新がありました。g3もこれらの技術を取り込んで、DSLをさらに最適化していく必要があります。

  • 型クラス
  • Monadicプログラミングの進化(Scalaz)
  • Scala DSL技術の進化(Unfiltered など)
  • 並行プログラミングの進化(Akka、Scalaz Promiseなど)
  • マクロ(Scala 2.10予定)
  • Play 2.0

型クラスを用いると、静的型付けのセマンティクスでパイプラインを駆動できるのではないかという期待があります。また、モデル記述DSLとの統合も可能かもしれません。型クラスの登場によって、いろいろな可能性が広がってきました。

Monadicプログラミング、Scala DSL技術、並行プログラミングは地道に進化してきており、2010年年初段階とは別世界の感があります。これらの進化を取り込んで、より強力なプログラミング・モデルを構築する必要があります。

さらに、Scala 2.10ではマクロ機能の導入が予定されています。このマクロはC的なテキスト置換のマクロではなく、ASTレベルでモデル操作ができるものです。これはかなり強力で、DSL技術に大きく寄与することになるでしょう。

プラットフォームとしては、Play 2.0が非常に強力なので、これもターゲットに加えて行きたいところです。

今後の展開

g3も2010年年初段階の技術ベースとしては、いい感じになっていると思いますが、最新技術の視座からみると少し古びてきているようです。

特に、型クラスやMonadicプログラミングの技術を使うことで、より簡潔で強力なプログラミング・モデルを構築できる可能性が高く、この進化を取り込んでいくことが急務です。

並行プログラミングも、フレームワーク内で独自に行うよりもAkkaなどの機構をうまくプログラマが活用できる形に持って行く方が適切です。

また、可能であれば静的型付けやモデルDSLとの統合なども行っていきたいところです。

以上の点から、g3をオーバーホールして、次世代向けのアプリケーション・フレームワークを作ることを考えています。技術的にはScala 2.10のマクロ機構のインパクトが極めて大きそうなので、この技術の評価ができた後に取り組むことになりそうです。

2012年5月8日火曜日

データフローDSL考 (3)

ニコニコ超会議 2012の超エンジニアミーティングのScalaユーザーグループのコマで「Scalaでプログラムを作りました」のセッションを行いましたが、右の図はそのスライドで用いたDSLの分類です。

ここでは、DSLを分類する軸として、用途の軸と実現方法の軸を用いています。

用途の軸の項目は以下の2つです。

  • モデル
  • フレームワークAPI

実現方法の軸の項目は以下の2つです。

  • 内部DSL
  • 外部DSL

一口にDSLといっても2×2で4種類のDSLに分類することができるわけです。各象限には、セッションで取り上げた技術を分類して配置しています。

Scalaは、関数型言語の機能と文法上の工夫で内部DSLの実現に適した言語となっています。またパーサーコンビネータを用いることで外部DSLの実現も簡単に行うことができます。

Scalaの名前の由来である「Scalable Language」は、言語をドメイン向けに拡張していくことができる、ということを意味していると思いますが、そういう意味で、まさにDSL指向言語ということができるでしょう。

データフローDSLという観点で、今までScalaで2つの内部DSLをつくってきました。一つはモデル記述の私家版Asakusa DSL、もう一つはg3向けのフレームワークAPIです。

まず、データフローDSLのモデル記述の実現例である私家版Asakusa DSLについてみていきます。

私家版Asakusa DSL

Asakusa Framework(以下Asakusa)は、基幹業務システムのバッチを高速処理するためのHadoopフレームワークです。単にHadoopを使いやすくしているだけでなく、ジョブ管理などを含めた基幹業務に必要なミドルウェアの基盤を提供している点が特徴です。

Hadoopのベースになった技術はMapReduceですが、mapとreduceという用語からも分かるとおり、関数型的なデータフロー演算が、計算モデルのベースになっています。オブジェクト指向と関数型の接点はデータフローになりそうだというお話をしてきましたが、クラウド・プラットフォームと基幹業務の接点もデータフローということになりそうです。

オブジェクト指向、関数型といった開発手法の方向からも、プラットフォームや業務ドメインからの方向からも、データフローに収斂する構図になっており、データフローは一種のホットスポットとなっています。

そういう意味でも、データフローDSLの果たす役割は今後大きくなりそうです。その一つの応用として、Asakusa向けのScala DSLは格好の例題となります。

Scala DSL

私家版Asakusa DSL(以下Scala DSL)は、現在Javaベースで提供されているAsakusa DSLのScala版の試作品です。去年の3月頃に作成しました。(「Asakusa Scala DSL」)

Scala DSLで記述したモデルは以下のようになります。

package sample

import org.simplemodeling.dsl.domain._
import org.simplemodeling.dsl.flow._

class 仕入明細データ extends DomainResource
class 仕入返品データ extends DomainResource
class 費用振替データ extends DomainResource
class 売価変更データ extends DomainResource

class 修正在庫振替TRN extends DataSet
class 修正未収収益TRN extends DataSet
class 修正在庫移動TRN extends DataSet
class 未払計上TRN extends DataSet

class 仕入TRN extends DataSet
class 在庫振替TRN extends DataSet
class 在庫移動TRN extends DataSet
class 未収収益TRN extends DataSet

class 計上済仕入TRN extends DataSet
class 計上済未収収益TRN extends DataSet
class 計上済未払費用TRN extends DataSet
class 更新済買掛残高TRN extends DataSet

class 請求エラーTRN extends DataSet
class 支払不可消込TRN extends DataSet
class 支払可消込TRN extends DataSet
class 照合済支払費用TRN extends DataSet
class 照合済未収収益TRN extends DataSet
class 照合済仕入TRN extends DataSet
class 照合済請求TRN extends DataSet

class 仕入データ extends DataSource4[仕入明細データ, 仕入返品データ,
                                     費用振替データ, 売価変更データ]
class 修正データ extends DataSource4[修正在庫振替TRN, 修正未収収益TRN,
                                     修正在庫移動TRN, 未払計上TRN]
class 売価変更在庫変更TRN extends DataSet
class 仕入データTRN extends DataSource4[仕入TRN, 在庫振替TRN,
                                        在庫移動TRN, 未収収益TRN]
class 残高更新TRN extends DataSource4[計上済仕入TRN, 計上済未収収益TRN,
                                      計上済未払費用TRN, 更新済買掛残高TRN]
class 請求TRN extends DataSet
class 会計データTRN extends DataSource7[請求エラーTRN, 支払不可消込TRN, 支払可消込TRN,
                                        照合済支払費用TRN, 照合済未収収益TRN,
                                        照合済仕入TRN, 照合済請求TRN]

case class 仕入データ取り込み(cout: Port[売価変更在庫変更TRN]) extends Operator12[仕入データ, 仕入データTRN, 売価変更在庫変更TRN](cout)
case class 残高更新(cin: Port[修正データ]) extends Operator21[仕入データTRN, 修正データ, 残高更新TRN](cin)
case class 照合処理(cin: Port[請求TRN]) extends Operator21[残高更新TRN, 請求TRN, 会計データTRN](cin)

// サブフロー用に追加したオペレーション
case object 売価変更在庫変更TRN修正 extends Operator11[売価変更在庫変更TRN, 売価変更在庫変更TRN]

// サブフロー用に追加したオペレーション
case object 修正データ追加修正 extends Operator11[修正データ, 修正データ]

// 図7 改善された会計処理バッチの処理フロー
case class 会計処理バッチ extends Flow32[仕入データ, 修正データ, 請求TRN,
                                    会計データTRN, 売価変更在庫変更TRN] {
// 追加したサブフロー1
// 処理結果を会計処理バッチの2番目の出力ポートに出力                                      
  val 売価変更在庫変更TRN補正 = new Flow11[売価変更在庫変更TRN, 売価変更在庫変更TRN] {
    start op11(売価変更在庫変更TRN修正) end(会計処理バッチ.this.out2)
  }

// 追加したサブフロー2
// 会計処理バッチの2番目の入力ポートからデータを入力
  val 修正データ補正 = new Flow11[修正データ, 修正データ] {
    start(会計処理バッチ.this.in2) op11(修正データ追加修正) end
  }

// 仕入データ取り込みの2番目の出力先をサブフロー1に変更
// 残高更新の2番目の入力元をサブフロー2に変更
  start op12(仕入データ取り込み(売価変更在庫変更TRN補正.post1)) op21(残高更新(修正データ補正.get1)) op21(照合処理(in3)) end
}

Scala DSLは(仮に)SimpleModelerに組み込んで、データフロー図を表示できるようにしています。このモデルからSimpleModelerで生成したデータフロー図は以下のものになります。


このデータフローDSLは、静的型付けによってデータの入出力関係やパラメタの個数の間違いをエラーチェックできることが特徴です。このこともあり、入出力のデータの定義を詳細に行なっています。

しかし、メインとなるデータフローの記述は以下の一行に収まっています。op12は入力が1つ、出力が2つあるプロセス、op21は入力が2つ、出力が1つのプロセスです。大枠は「仕入データ取り込み」を行った後「残高更新」する処理となります。

「仕入データ取り込み」はメインの出力に加えて「売価変更在庫変更TRN補正」サブフローに出力を行います。また、「残高更新」はメインの入力に加えて「修正データ補正」サブフローからの入力を行います。

start op12(仕入データ取り込み(売価変更在庫変更TRN補正.post1)) op21(残高更新(修正データ補正.get1)) op21(照合処理(in3)) end

データフローを構成するサブフローは以下の「売価変更在庫変更TRN補正」と「修正データ補正」の2つです。それぞれのサブフローも一行に収まっています。

val 売価変更在庫変更TRN補正 = new Flow11[売価変更在庫変更TRN, 売価変更在庫変更TRN] {
    start op11(売価変更在庫変更TRN修正) end(会計処理バッチ.this.out2)
  }
val 修正データ補正 = new Flow11[修正データ, 修正データ] {
    start(会計処理バッチ.this.in2) op11(修正データ追加修正) end
  }
評価

Scalaの内部DSLでデータフローDSLを設計する上での論点としては以下のものが挙げられます。

  • パイプラインの記法を取り入れるか否か(ノード記述方式を採用するか否か)
  • 静的型付けをどこまで使うか
  • 2つのフローの接続方式
  • ロジック記述方式

私家版Asakusa DSLは、実験的な意味もあり、パイプライン記法と静的型付けの方に大きく倒した方式にしてみました。そこそこ、うまくいっているのではないかというのが自己評価で、パイプライン記法と静的型付けの組合せは一応実用化の目処がついたと判断しています。

「2つのフローの接続方法」は、サブフローを直接指定する方式にしてみましたが、今の目で見ると、チャネル方式などを導入してサブフロー間を疎結合にしていくアプローチの方がよさそうです。

「ロジック記述方式」は、今回の試作では入れませんでしたが、非常に重要な項目です。内部DSLを採用するメリットの一つがロジックをホスト言語で直接記述できる点にあります。

実用化

実用化に際しては、サブフローを集めた部品を、他の部品と合成してさらに大きくしていく開発手法が求められるようになると思います。サブフロー部品の汎用部品化も視野に入ってきそうです。サブフローの接続方式はチャネル方式の方がこういった開発手法に適していそうなので、改良候補となります。

静的型付け&ジェネリック型&関数型は、こういった部品の合成時のメカニズムおよび合成時のエラーチェックにも威力を発揮しそうです。そういう意味でも、直接ロジック記述することができるメリットも含めて、Scalaをホスト言語にした内部DSL方式は、非常に有力な方式ですね。

SimpleModeler

私家版Asakusa DSLは、仮にSimpleModelerに組み込んでみましたが、Asakusaに限らず汎用的なデータフローDSLとして利用できそうな感触を持っています。

懸案事項はサブフローの接続方式とロジック記述方式です。

サブフローの接続方式はチャネル方式にするとして、問題はロジック記述方式です。これは、型クラスを用いて実現できそうな気がしているのですが、このあたりが次のチャレンジとなります。

折を見てこれらの改良を施した上で、Asakusaはもちろん、他のプラットフォーム向けのコード生成にもトライしてみたいと考えています。

2012年5月7日月曜日

データフローDSL考 (2)

前回は、データフロー図の例として以下の図を挙げました。テキストDSLでデータフローを記述する場合、こういったモデルを記述できることが必要となります。




最後の手段

データフロー図はグラフ構造を取ります。つまり、テキストDSLで記述する際の最後の手段としてノード記述方式があります。

デーフローに登場する各ノードを個別に記述し、ノード間に存在するリンクを(1)リンクの集合として記述、あるいは(2)ノードの属性として記述、といった方式です。

このノード記述方式を採用すれば絶対に記述できるのは明らかです。グラフ処理のアルゴリズムは行列を使うものが多いですが、これも一種のノード記述方式といえます。

ノード記述方式による最後の手段は確立されているという前提の上で、さらによい記述方式があるのかどうかという点が論点となります。なければ、普通にノード記述方式を採用していけばよいわけです。

パイプライン

データフローをテキストDSLで記述する際の有力な候補がパイプラインです。UNIXのシェルや、このブログでもたびたび取り上げている関数型プログラミングのMonadicプログラミングもパイプラインの例です。パイプラインはセマンティクスの分かりやすさとテキスト記述との相性の良さから、テキストDSLの記述方式の有力な候補となります。

パイプラインの問題点は、木構造やグラフ構造をうまく記述できないことです。木構造については工夫でカバーできる点もありそうですが、グラフ構造については本質的に不可能です。

このため、応用がぴったりはまると非常に強力ですが、汎用的には利用できないという大きな弱点があります。

解決案

この問題を解決するために考えてみたのが、複数のパイプラインを組合わせてグラフ構造を記述する方法です。

たとえば以下のようなデータフロー図を考えてみます。




このデータフローは、上側のデータフローと下側のデータフローを2つ組合わせたものと考えることができます。

この観点で注釈をつけたものが以下の図になります。




上側のパイプラインと下側のパイプラインを分かりやすく枠で囲みました。

また、プロセスが複数入力する場合の多くが、メインの処理対象に補足情報を継ぎ足していきます。たとえば、仕訳伝票や製造指示書といったトランザクションデータに商品番号や部門といったマスターデータを結合するような処理です。こういった用途が限定された軽い枝は、特別な記法の導入で記述することができる可能性があります。これば可能であればパイプラインのセマンティクスを維持したまま、より広範囲のモデルの記述が可能になります。

そして、2つのパイプラインを結合する記述方式の問題が解決すれば、パイプラインを軸にデータフローを記述することが可能になります。

より複雑に

前述の例を少し拡張すると以下のデータフロー図になります。




拡張したポイントを追加した注釈は以下の図です。




この図は、一番最初のデータフロー図と全く同じものです。つまり、少し複雑に見えるデータフロー図でも、細かく見ていくと軸となるパイプラインを組み合わせたものであることが多々あるわけです。

実感的には、現実の応用はこのように軸となるパイプラインを決めることができて、これに小さな演算や小さなパイプラインを加えていく形に落ち着くことが多いのではないかと思います。

こういった性質を持つドメインのモデル化をする上では、パイプラインを軸にしたセマンティクスのテキストDSLが有効ではないかと思います。

具体例

データフロー図、データフローモデルをテキストDSL化する方式はDSL駆動開発の鍵となる重要な要素です。以前からパイプライン拡張方式が有力と考えており、色々と試行錯誤をしてきました。

その成果物としてメッセージングフレームワークg3と私家版Asakusa DSLがあります。次回は、データフローDSLという観点でこれらのDSLについてみていきます。

2012年5月2日水曜日

データフローDSL考

ニコニコ超会議 2012の超エンジニアミーティングのScalaユーザーグループのコマで「Scalaでプログラムを作りました」のセッションを行いました。昨日の「Scalaへのアプローチ」はそのまとめです。このセッションの主張の一つがScalaの最も重要な用途がDSLであるということです。

3月に要求開発アライアンスでオブジェクト指向モデリングと関数型の関係を検討したセッション『Object-Functional Analysis and Design: 次世代モデリングパラダイムへの道標』を行いました。その内容のまとめは「Object-Functional Analysis and Designまとめのまとめ」となります。ここでは、OOADと関数型の接点としてデータフローが重要というのが結論でした。データフローの実装技術についても「データフローの実装技術」として考察しました。

この2つのセッションで共通に使用しているスライドが以下の「SparkとScalding」です。

オブジェクト指向、関数型、クラウド・コンピューティングの交差する技術領域では、データフローをいかにテキストDSLで記述していくのかという点が、非常に重要な論点になっていると考えます。

SparkやScaldingは、関数型言語の普通の記法でそのままHadoopなどの大規模並列分散計算基盤上で動作するフレームワークAPIを提供しています。これはかなり強力で、もしかして決定版かも、と思わせます。

この点について考える前に、この分野の定番技術であるデータフロー図についてみてみましょう。

データフロー図

OOADでは、主流からフェードアウトしてしまったデータフロー図ですが、クラウド・コンピューティングの登場によって、再び第一級のモデルになってきそうです。

古(いにしえ)の基幹システムはバッチ処理の集りなので、バッチ処理をデータフローでモデル化したものをシステム全体の機能モデルとして扱っても十分に機能していました。

当初OOADにおいてもデータフロー図は、機能モデルを記述する目的を期待されていましたが、インタラクション、コラボレーションをうまく記述することはできないので、システム全体の機能モデルを記述するのには力不足で、最終的にはユースケース・モデルに取ってかわられたという成り行きだったと思います。

とはいえ、バッチで実行するような複雑なデータ処理演算のモデル記述するのにはデータフロー図は引き続き有効です。(ただ、最近は基幹系でもデータフロー図がロストテクノロジーになっているという噂も聞きます。)

クラウド・プラットフォームによって大規模データ、大規模演算が可能になったことによって、扱う計算過程も複雑になってきます。データフロー図は、システム全体の機能モデルを記述するには向きませんが、こういった計算過程を記述するのには有力な候補になります。そういう意味で、大規模データ、大規模演算の分野での第一級モデルとして再評価されることになるのではないかと思います。

簡単な例

まず、シンプルなデータフロー図は以下のようになります。



ここでは、外部実体としてメインのRDBMSに格納されているエンティティ、データストアとして中間データの格納庫(カラム指向のNoSQLでの運用をイメージ)、という運用を想定しています。

このように一直線のパイプラインの場合、前述のSparkやScaldingによる関数型プログラミング的な記法がぴったりはまります。

複雑な例

しかし、現実のバッチ処理はもっと複雑です。たとえば、以下のようなデータフロー図は普通にありそうです。



この図のモデルに対して、テキストDSL化のポイントとなりそうな点に注釈を入れたものが以下の図です。



この図では以下の問題を挙げています。

  • 複数エンティティの入力
  • 複数エンティティへの出力
  • データフロー中からの入力
  • データフロー中からの出力
  • データフローの分岐
  • データフローの合流
  • データストアの使い回し(メモ化)

関数型プログラミングでは:

  • (SparkやScaldingの例で挙げた)パイプライン的な記述
  • 引数N個の関数の入れ子

の2つの選択肢があります。

「パイプライン的な記述」は、データフローが「簡単な例」にあるようなシンプルな場合はよいのですが、「複雑な例」にあるような込み入った物なってくると記述できなくなるのは明らかです。

DSLとしてはちょっと煩雑な記述になりますが「引数N個の関数の入れ子」方式では、入力側を枝、出力側を根にした木構造を持つデータフローを記述することができます。さらに出力側にタプルを用いることで出力側を木構造に開いていくことも不可能ではありません。

しかし、この方式は「データフローの合流」が登場してモデルが木構造の範疇を超えて、グラフ構造になってくると記述ができなくなってしまいます。

つまり、データフローをテキストDSLで記述する方式もなかなか簡単にはいかないわけですね。この対策について、次回考えてみたいと思います。

2012年4月4日水曜日

Object-Functional Analysis and Designまとめのまとめ

3月19日(月)に要求開発アライアンスのセッション『Object-Functional Analysis and Design: 次世代モデリングパラダイムへの道標』を行いました。その事前準備、まとめ、回顧の記事が一段落したのでリンクをまとめました。

今回のセッションはちょうど、クラウド・コンピューティングや関数型言語への理解が進んで、開発方法論やアプリケーション・アーキテクチャについて腰を落として考えるちょうどよいタイミングだったので、時間を取って色々な要素技術について改めて考えてみました。

今回50分のセッションで、それらの要素をすべて盛り込むことはできないので、事前資料や回顧の記事を書くことで、スライドを書きながら考えていたことを成果物としてまとめてみました。実際に、文章にまとめてみると自分的にも色々な発見もありますし、フィードバックから新たな情報も得られるので、非常に有益だったと思います。

まとめのまとめ

スライドとブログ記事を通じてひと通り考えてきたことをまとめると以下のようになります。

クラウド・アプリケーション・アーキテクチャ
CQRS/EDAが有力。EIP、DDDの技術も適用できる。
モデリング
引き続きOOモデリングが中心。CQRS/EDAをターゲットにイベントを中心としたモデリングが有効。
関数型モデリング
EDAのイベントコンシュマーでデータフローを記述する部分でOOモデリングを補完。OFPとDSLで実装とつなげる。
DSL
DSL指向フレームワークによるDSL指向プログラミングに移行。
OFP
DSL、並行プログラミングのニーズからクラウド・アプリケーションの主力言語になる。

個人的にはEDAの重要性を再認識したのが収穫で、EDAとOOAD、OFP、DSL、DDD、DCI、EIPといった技術との関係も整理することができました。

その上で、今後の大きな流れを考えてみると:

  • クラウド・アプリケーションはOFPとDSLで記述するようになる
  • EDAが新しい枠組みの上で再構築/再発明されていく

という事になろうかと思います。

スライド

当日使用したスライドは以下のものです。(PDF出力ツールの関係で、当日は非表示にしたスライドも表示されています)

まとめ

セッション後のまとめは以下の記事になります。

2012年4月2日月曜日

CQRS, EDA

3月19日(月)に要求開発アライアンスのセッション『Object-Functional Analysis and Design: 次世代モデリングパラダイムへの道標』を行いましたが、説明を端折ったところを中心にスライドの回顧をしています。

セッションでは、オブジェクト関数型言語が中心的な実装言語となった時のOOADの進化の方向性、という観点でストーリ展開してきました。ここでは、クラウド・プラットフォームは、このストーリを考える上での文脈の一つとして捉えています。

今回は逆に、クラウド・アプリケーション・アーキテクチャを軸にしてセッションの情報を再構成してみたいと思います。

クラウド・アプリケーションのイメージ

今回のセッションでは説明しなかったのですが、従来のアプリケーションとクラウド・アプリケーションはそれぞれこのようにイメージしています。

基本となるのは画面とデータベース間のOLTPの枠組みでの転記で、帳票としての印刷も重要なモジュールです。外部連携や通信は特別扱いの連携モジュールを用いて行います。OLTPで賄えない部分は、夜間などの定刻に起動するバッチ処理で行います。ここが非同期的に動作する部分です。

このあたりの基本構造はメインフレームからオフコン、クラサバを経てWebアプリケーションまで変わっていません。基本的には、画面からのリクエストでジョブがキックされてトランザクション配下で同期型に転記処理が遂行されます。プログラムの作り方はずいぶん変わりましたが、アーキテクチャの根幹のところは案外共通しているわけです。

一方、クラウド・アプリケーションの場合、クラウド内で生起する様々な事象がシグナルとしてクラウド・アプリケーションに上がってくるようになります。このシグナルを自アプリケーションにとって意味のあるイベントであると認識するフィルタ処理から始まってイベント駆動でアプリケーションが動作します。イベントの発生は、クラウド上に遍在する複数のイベント発生源上で同時に起きるので、非同期、並行で処理を遂行していく必要があります。

EDA

実際のところ、エンタープライズアプリケーションも前述のレガシーアプリケーションのままでは時代のニーズに合わないので、地道に進化してきており、SOAの傘の下でさまざまな技術が発展しています。しかしSOAというと、合併した企業間のシステム統合や、業務改革と連動した巨大エンタープライズ・アプリケーションの再構築といった、雲の上の話であることが多く、そういった案件以外での認知度は今ひとつです。

しかし、この中で蓄積されてきた技術は、クラウド・アプリケーションの基盤技術として有効であるというのがボクの認識です。ただし、実装技術は古いので、新しい革袋の中で再構築していく必要があるでしょう。その新しい革袋はRESTであったり、関数型であったり、DSLであったりするはずです。

そういった、イベント駆動のアプリケーション向けのアプリケーション・アーキテクチャとしてEDA(Event-Driven Architecture)が知られています。イベント駆動アプリケーションと関数型による実現は「EDAとオブジェクトと関数」や「DCI (Data Context Interaction)」で考察しました。

EIP

EIP(Enterprise Integration Patterns)もクラウド・アプリケーションに転用できる重要な技術です。

EDAを実現する基盤メカニズムとして、ESB(Enterprise Service Bus)やMOM(Message-Oriented Middleware)があり、これを使用するアプリケーション構築パターンとしてEIPが整備されています。

今回は、Apache CamelによるScala DSLとして右の図を用意しました。この図はデータフローに対する関数型&DSLの記述力の例として用意しましたが、クラウド・アプリケーション・アーキテクチャ上に対する重要なアプローチと考えられるEIPの紹介という意味もあります。

EIPは元々企業アプリケーション・アーキテクチャをインテグレーションするためのパターンで、対象の粒度が企業アプリケーションですから、相当大きなものになります。しかし、このパターンはコンポーネントといった、もっと小さな粒度にも適用できると思われます。Apache CamelによるScala DSLもこういった路線が狙いと思います。

今回のセッションでは関数型&DSLを用いてデータフローを記述するアプローチで、関数型とクラウド・アプリケーションの連携を考えています。EIPも従来型のESBのXML定義ファイルベースではなく、関数型&DSLの枠組みでクラウド・アプリケーションに組み込まれていくことになるでしょう。

モデリング

アプリケーション・アーキテクチャが、EDAベースになっていくとすると、モデリングの軸となるモデル要素としてイベントが非常に重要になってきます。

元々、オブジェクト・モデリングではイベントが重要なモデル要素だったので、この点を素直に伸ばしていけば、クラウド・アプリケーションにも適用できるはずです。

この点を盛り込んだ「メタモデル」をセッションで紹介しました。

また、「EDAとオブジェクトと関数」ではイベントをEDA上で実現するメカニズムについても考えました。

CQRSとEDA

CQRS(Command Query Responsibility Segregation)はクラウド・アプリケーション向けに注目されているアプリケーション・アーキテクチャです。

この記事の先頭に挙げたスライド「CQRS, EDA」はCQRSでのCommandで、イベントを発生させ、イベントを起点にEDA的なメカニズムで処理を実行していく事を説明する目的のものです。セッションでは時間の関係で省略しました。

コマンドの発行をイベントの発行と連動させ、EDA上で実現するとEDAの非同期的な実行がCQRSの意図する振舞いとぴったりはまります。CQRS&EDAの組合せが全体のバランスがよいのではないかという提案が、このスライドの意図です。

イベントは、モデリング上で抽出された"ビジネス・イベント"を中心に考え、実装の必要に応じてシステム・イベントを併用する形になるでしょう。このようにすることで、前述のメタモデルをベースとしたオブジェクト・モデリングともつながってきます。

今回のセッションでの考察では、このアーキテクチャの中で、関数型はイベント発生に対応するイベントコンシュマーの実現で用いられることになります。短いものは関数型言語で直接記述して同期実行、大きなものはSparkのようなDSLで記述して非同期実行という使い分けになるでしょう。

追記
イベントコンシュマーをイベントプロデューサに誤記していたので訂正しました。

諸元

当日使用したスライドは以下のものです。(PDF出力ツールの関係で、当日は非表示にしたスライドも表示されています)

このスライド作成の様子は以下の記事になります。

まとめは以下の記事です。

回顧は以下の記事になります。