2015年4月27日月曜日

Finagle+Karaf+Dockerでmicroservices

流行りなのでmicroservicesという用語を使ってみましたが、意図としては(microservicesも含む)マルチサーバー構成によるクラウド・システムを構成するサービス群がターゲットです。

この実行基盤としてFinagle、Karaf、Dockerの組合せが有力ではないかということで、試しにサンプルプロジェクトを作ってみました。このサンプルプロジェクトは、新しいクラウド・システム内サービスを作る時の雛形を想定しています。

このサンプルプロジェクトを実際に作ってみることで、Karafがどのぐらいの手間で使えるのか、ScalaやFinagleとの相性はどうなのか、といったことを試してみるのが目的です。この手のプロジェクト構築ではライブラリ間の依存関係の解決が難しいのですが、Finagleをリンクしても大丈夫な設定を発見できたので、この問題はクリアできていると思います。

以下ではクラウド・システム内サービスをサービスの粒度によらずmicroserviceと呼ぶことにします。

Apache Karaf

Apache Karafは軽量OSGiコンテナです。

Karafを導入する目的は大きく以下の3つです。

  • microserviceの環境設定共有
  • dependency hell対策
  • CamelによるEIP(Enterprise Integration Patterns)
環境設定共有

第一の目的はクラウド・システムを構成するmicroservice群の環境設定を共通化することです。

microserviceをmainメソッドを作るなどして自前でデーモン化すると各種設定もすべて自前で行わなければならなくなります。

たとえば、SLF4J+fluentedなどによるロギング、JMX(Java Management Extensions)による運用監視、JNDI(Java Naming and Directory Interface)によるディレクトリ管理といった各種設定をサービス毎に設定する必要があります。

これは(1)設定そのものが大変な手間、(2)クラウド・システム内の共通設定情報の共有の手間、(3)設定漏れの危険、(4)ハードコーディングによるカスタマイザビリティの欠如、といった問題があります。

microserviceをコンテナ内で動作させることで、各種設定はコンテナに対する設定として共通化する上記の問題を解消することができます。

この目的で導入するコンテナとして、軽量OSGiコンテナであるKarafがよいのではないか考えています。

dependency hell対策

Java VM上での開発における未解決問題の一つにdependency hellがあります。

この問題はJava 9でModule機能として解決される見込みですが、当面の対策としてはOSGiコンテナを使用するのが現実解となっています。

最近はちょっとしたライブラリをリンクしても、その裏でApache Commons、Spring、JBossといった巨大なライブラリがついてくることが多いので、思ったより切実な問題です。

CamelによるEIP

Karafを採用したボーナスのようなものですが、KarafからはCamelを簡単に使えるようになっています。(KarafはESB(Enterprise Service Bus)のServiceMixのOSGiコンテナ部を切り出して製品化したものです。)

Camelを使うと、EIPのパターンを使用して様々な通信プロトコルを使って外部サービスと連携する処理を簡単に作成することができます。またCamelにはScala DSLも用意されています。

Twitter Finagle

Twitter Finagleはmicroservices指向のRPCシステムです。

FinagleはFutureモナドによる非同期実行、耐故障性の抽象化を行っており、Monadic Programmingとの相性がよいのもFunctional Reactive Programming指向のmicroservicesの通信基盤として魅力です。

準備

Karafを使うためのベースとしてKarafの実行環境をDockerイメージ化しました。

Karaf Docker Image

Dockerイメージを作成するプロジェクトは以下になります。

Dockerfileだけの簡単なプロジェクトです。

実際にmicroserviceをインストールして使うことを想定しているので、余分な設定は行わずプレインな簡単なものにしています。

Docker Hub

このDockerイメージをDocker Hubに登録しました。

以下のようにして利用できます。

$ docker pull asami/karaf-docker

サンプル・プロジェクト

サンプル・プロジェクトは以下のGitHubプロジェクトとして作成しました。

サンプル・サーバー

Finagleによるサンプル・サーバーは以下のものです。

package sample

import com.twitter.finagle.{Http, Service, ListeningServer}
import com.twitter.util.{Await, Future}
import java.net.InetSocketAddress
import org.jboss.netty.handler.codec.http._

object Server {
  val service = new Service[HttpRequest, HttpResponse] {
    def apply(req: HttpRequest): Future[HttpResponse] =
      Future.value(new DefaultHttpResponse(
        req.getProtocolVersion, HttpResponseStatus.OK))
  }

  def start(): ListeningServer = {
    Http.serve(":8080", service)
  }

  def stop(server: ListeningServer) {
    server.close()
  }

  def main(args: Array[String]) {
    val server = start()
    Await.ready(server)
  }
}
ビルド

ビルドは以下の手順で行います。

  • Karaf KAR
  • Dockerfile
  • Dockerイメージ
Karaf KAR

Karafに配備するKARファイルの作成にはkarafタスクを使用します。

$ sbt karaf

karafタスクはbuild.sbtで定義しています。

Dockerfile

KARファイルを配備したKaraf実行環境のDockerイメージを作成するためのDockerfileの作成にはdockerタスクを使用します。

$ sbt docker

dockerタスクはbuild.sbtで定義しています。

Dockerイメージ

Dockerイメージの作成は以下になります。

$ docker build -t sample-finagle-karaf-docker .

dockerタスクを実行するとDockerfileが作成されるので、このDockerfileでDockerイメージをビルドします。この時、前述したDockerイメージasami/karaf-dockerを内部的に使用します。

実行

作成したDockerイメージsample-finagle-karaf-dockerを実行してみましょう。

$ docker run -t -i --rm -p 1099:1099 -p 8101:8101 -p 44444:44444 -p 8080:8080 sample-finagle-karaf-docker

ポートは1099, 8101, 44444, 8080の4ポートを使用します。

ポート1099, 8101, 44444はKarafが使用するポートです。

ポート用途
1099JMX RMI registry
8101SSHコンソール
44444JMX RMI server

ポート8080はサンプルのFinagleサービスが使用するポートです。

確認

Dockeイメージを実行するとKarafコンテナ内で自動的にサンプルのFinagleサービスが起動されます。

curlコマンドでサンプルのFinagleサービスにアクセスすると以下の結果が得られます。無事動作していることが確認できました。

$ curl http://192.168.59.103:8080 -v
* Rebuilt URL to: http://192.168.59.103:8080/
* Hostname was NOT found in DNS cache
*   Trying 192.168.59.103...
* Connected to 192.168.59.103 (127.0.0.1) port 8080 (#0)
> GET / HTTP/1.1
> User-Agent: curl/7.37.1
> Host: 192.168.59.103:8080
> Accept: */*
> 
< HTTP/1.1 200 OK
< Content-Length: 0
< 
* Connection #0 to host 192.168.59.103 left intact

感想

現時点だとSBTからKaraf向けのKARファイルの作成のための設定が大変なので、まだまだKarafは気軽には使えないような感じです。

ただAPPREL CLOUD級の規模になってくると設定の大変さよりも運用管理上のメリットの方が大きくなるのでそろそろ導入を考えてもよさそうです。

諸元

  • Scala 2.10.5
  • Finagle 6.25.0
  • Karaf 4.0.0.M2
  • Docker 1.3.2
  • sbt 0.13.7

2015年4月20日月曜日

[scalaz-stream] ストリーミングで状態機械

Functional Reactive Programming(FRP)の眼目の一つはMonadic Programming(MP)によるストリーミング処理です。

MPでFRPを記述できることで安全なプログラムを簡単に書くことができるようになります。

そこで今回は「Scala的状態機械/FP編」で作成したProcessモナド版CSVパーサーをストリーミング処理に適用してみます。

準備

Scala的状態機械/OOP編で作成した状態遷移を記述した代数的データ型ParseStateをストリーミング用に一部手直しします。

package sample

sealed trait ParseState {
  def event(c: Char): ParseState
  def endEvent(): EndState
}

case object InitState extends ParseState {
  def event(c: Char) = c match {
    case ',' => InputState(Vector(""), "")
    case '\n' => EndState(Nil)
    case _ => InputState(Nil, c.toString)
  }
  def endEvent() = EndState(Nil)
}

case class InputState(
  fields: Seq[String],
  candidate: String
) extends ParseState {
  def event(c: Char) = c match {
    case ',' => InputState(fields :+ candidate, "")
    case '\n' => EndState(fields :+ candidate)
    case _ => InputState(fields, candidate :+ c)
  }
  def endEvent() = EndState(fields :+ candidate)
}

case class EndState(
  row: Seq[String]
) extends ParseState {
  def event(c: Char) = c match {
    case ',' => InputState(Vector(""), "")
    case '\n' => EndState(Nil)
    case _ => InputState(Nil, c.toString)
  }
  def endEvent() = this
}

case class FailureState(
  row: Seq[String],
  message: String
) extends ParseState {
  def event(c: Char) = this
  def endEvent() = sys.error("failure")
}

具体的にはEndStateが完全終了ではなく、次のイベントが発生したら入力受付け状態に復帰するようにしました。

Scala的状態機械/FP編で作成したParserStateMonadは変更ありません。今回はこの中で定義しているモナディック関数であるactionを使用します。

package sample

import scalaz._, Scalaz._

object ParserStateMonad {
  def action(event: Char) = State((s: ParseState) => {
    (s.event(event), event)
  })

  def parse(events: Seq[Char]): Seq[String] = {
    val s = events.toVector.traverseS(action)
    val r = s.run(InitState)
    r._1.endEvent.row
  }
}

ストリーミング版

それではストリーミング版の作成に入ります。

EventProcessor

まずストリーミング処理の動作環境として、scalaz-streamが提供する非同期キュー(scalaz.stream.async.mutable.Queue)を作成します。

package sample

import scalaz.concurrent.Task
import scalaz.stream._

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

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

EventProcessorは、scalaz.stream.async.unboundedQueue関数で作成したQueueによって、ストリームに対するイベントと、イベントをハンドリングするProcessモナドを接続します。

Queueに対して送信されたイベントは、Queueのdequeueメソッドで取得できるProcessモナドに転送されます。

StreamingParser

ストリーミング処理用のCSVパーサーは以下になります。

package sample

import scala.language.higherKinds
import scalaz.concurrent.Task
import scalaz.stream._

object StreamingParser {
  def createParser[F[_]](source: Process[F, Char]): Process[F, ParseState] = {
    source.pipe(fsm(InitState))
  }

  def fsm(state: ParseState): Process1[Char, ParseState] = {
    Process.receive1 { c: Char =>
      val s = ParserStateMonad.action(c).exec(state)
      Process.emit(s) fby fsm(s)
    }
  }
}

まずパーサーはProcessモナドに組み込んで使用する必要があるので、組込み可能なモナディック関数fsmを用意します。これは、Scala的状態機械/FP編で作成したParseProcessMonadStateMonadのfsm関数と同じものです。

その上で、このfsm関数をpipeコンビネータでProcessモナドに組み込む関数createParserを用意しました。この関数は便利関数という位置付けのものです。

つまりScala的状態機械/FP編で作成した部品はそのままストリーミング処理にも適用できるということになります。

使い方

動作確認のためのプログラムは以下になります。

package sample

import scalaz.concurrent.Task
import scalaz.stream._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

object StreamingParserSample {
  def main(args: Array[String]) {
    val stream = EventProcessor.eventStream
    build(stream)
    execute()
  }

  def build(stream: Process[Task, Char]) {
    Future {
      val parser = StreamingParser.createParser(stream).map {
        case EndState(row) => report(row)
        case x => ignore(x)
      }.run.run
    }
  }

  def report(row: Seq[String]) {
    println(s"report: $row")
  }

  def ignore(s: ParseState) {
    println(s"ignore: $s")
  }

  def execute() {
    val queue = EventProcessor.q
    val a = "abc,def,ghi\n"
    val b = "jkl,mno,pqr\n"
    a.foreach(queue.enqueueOne(_).run)
    Thread.sleep(2000)
    b.foreach(queue.enqueueOne(_).run)
    Thread.sleep(2000)
  }
}

まずイベントの送信ですが、EventProcessorのeventStreamメソッドで取得したProcessモナドに対してbuild関数でパーサーの組込みを行っています。パーサーはStreamingParserのcreateParser関数で作成しています。さらにmapコンビネータでパース結果のコンソール出力処理を追加しています。

execute関数は、EventProcessorのメソッドで取得した非同期キューに対してenqueueOneメソッドでイベントを送出しています。

非同期キューに対して送出したイベントが、非同期キューと連動したProcessモナドに対して送られます。

この例では、プログラム内に埋め込まれたデータを使用していますが、WebサーバーのHTTPリクエストやAkkaのメッセージの受信処理でこの非同期キューに転送することで、簡単にProcessモナドによるストリーミング処理を行うことができます。

実行

実行結果は以下になります。

ignore: InputState(List(),a)
ignore: InputState(List(),ab)
ignore: InputState(List(),abc)
ignore: InputState(List(abc),)
ignore: InputState(List(abc),d)
ignore: InputState(List(abc),de)
ignore: InputState(List(abc),def)
ignore: InputState(List(abc, def),)
ignore: InputState(List(abc, def),g)
ignore: InputState(List(abc, def),gh)
ignore: InputState(List(abc, def),ghi)
report: List(abc, def, ghi)
ignore: InputState(List(),j)
ignore: InputState(List(),jk)
ignore: InputState(List(),jkl)
ignore: InputState(List(jkl),)
ignore: InputState(List(jkl),m)
ignore: InputState(List(jkl),mn)
ignore: InputState(List(jkl),mno)
ignore: InputState(List(jkl, mno),)
ignore: InputState(List(jkl, mno),p)
ignore: InputState(List(jkl, mno),pq)
ignore: InputState(List(jkl, mno),pqr)
report: List(jkl, mno, pqr)

「ignore: InputState(List(),a)」といった形のパース処理の途中結果が流れた後に、「report: List(abc, def, ghi)」といった形のパース結果が流れてくることが確認できました。アプリケーション側ではパース結果のみをmatch式で拾いだして処理をすることになります。

フロー制御

StreamingParserのfsm関数はパース処理の途中結果もすべてストリームを流れてきます。

これはちょっとクールではないので、簡単なフロー制御を入れて対処することにしましょう。

この目的でStreamingParserを改修してStreamingParserRevisedを作成しました。

package sample

import scala.language.higherKinds
import scalaz.concurrent.Task
import scalaz.stream._

object StreamingParserRevised {
  def createParser[F[_]](source: Process[F, Char]): Process[F, Seq[String]] = {
    source.pipe(fsm(InitState))
  }

  def fsm(state: ParseState): Process1[Char, Seq[String]] = {
    Process.receive1 { c: Char =>
      val s = ParserStateMonad.action(c).exec(state)
      s match {
        case EndState(row) => Process.emit(row) fby fsm(InitState)
        case FailureState(row, message) => log(message); fsm(InitState)
        case x => fsm(s)
      }
    }
  }

  def log(msg: String) {
    println(s"error: $msg")
  }
}

fsm関数ではaction関数から返されるStateモナドの実行結果によって以下のように処理を切り替えています。

EndStateの場合
パース結果の文字列をストリームに送出し、状態機械は初期状態に戻す
FailureStateの場合
エラーをログに出力し、状態機械は初期状態に戻す
その他
パース途中結果の状態に状態機械を遷移させる

上記の処理を行うことでEndStateの場合のみ、ストリーム上にデータを送出するようになっています。

これは一種のフロー制御ですが、このようなフロー制御が簡単に記述できることが確認できました。

使い方

StreamingParserRevisedの使用方法は以下になります。

package sample

import scalaz.concurrent.Task
import scalaz.stream._
import scala.concurrent.Future
import scala.concurrent.ExecutionContext.Implicits.global

object StreamingParserRevisedSample {
  def main(args: Array[String]) {
    val stream = EventProcessor.eventStream
    build(stream)
    execute()
  }

  def build(stream: Process[Task, Char]) {
    Future {
      val parser = StreamingParserRevised.createParser(stream).
        map(row => report(row)).run.run
    }
  }

  def report(row: Seq[String]) {
    println(s"report: $row")
  }

  def execute() {
    val queue = EventProcessor.q
    val a = "abc,def,ghi\n"
    val b = "jkl,mno,pqr\n"
    a.foreach(queue.enqueueOne(_).run)
    Thread.sleep(2000)
    b.foreach(queue.enqueueOne(_).run)
    Thread.sleep(2000)
  }
}

StreamingParserの場合はストリーミングに処理途中、処理結果を問わずParseStateが流れてくるので、このParseStateのハンドリングを行っていました。

StreamingParserRevisedでは、処理結果の文字列のみが流れてくるので、処理結果に対する処理のみを記述する形になっています。

実行

実行結果は以下になります。

report: List(abc, def, ghi)
report: List(jkl, mno, pqr)

まとめ

ストリーミング処理をProcessモナドで記述するメリットは、Processモナド用に作成した部品やアプリケーション・ロジックをそのままストリーミング処理に適用することができる点です。

今回の例でも簡単な改修で適用することができました。

ただしProcessモナド用に作成した部品もストリーミング処理で使用すると効率的でない部分も出てくるので、必要に応じて最適化を行っていくことになります。

ストリーミング処理での最適化ではフロー制御が重要な要因となります。

フロー制御を実現するためには、ストリーミング処理内で状態機械を記述できる必要があります。Processモナドでは、この状態機械の記述が可能なので、フロー制御も簡単に実現できることが確認できました。

諸元

  • Scala 2.11.4
  • Scalaz 7.1.0
  • Scalaz-stream 0.6a
  • Scalatest 2.2.4

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での実践で得られた経験をもとに体系化を進めていきたいと思います。