Movatterモバイル変換


[0]ホーム

URL:


Uploaded byTanUkkii
PDF, PPTX23,512 views

Isomorphic web development with scala and scala.js

isomorphic tokyo meetupで発表した資料です

Embed presentation

Download as PDF, PPTX
Isomorphic Webdevelopment with Scala & Scala.jsTanUkkiiisomorphic tokyo meetup2015/4/30
I am ...• @TanUkkii007 on Twitter• Web frontend engineer• だったけどゲーム開発が辛くてサーバーサイドをScalaで開発する人に• Scala業務歴4ヶ月
Agenda1. 開発環境を共有する2. コードを共有する3. アーキテクチャを共有するクライアントーサーバー間で
Motivation• Scala + Akka + SprayでAPIサーバーを開発• StrongLoopのApi Exprolerみたいなのを作りたい• Scala.jsでisomorphicにつくる!REST/HTTP server build on Akka Actors
Why Scala.js?• クライアントーサーバーで同一の開発環境• クライアントーサーバーでコードの共有• 片手間クライアント開発
1. Sharing developmentenvironment: Building applications with sbt• プロジェクト定義• Scalaのバージョン• 依存ライブラリ• コンパイルオプションJava/Scalaのビルドツール設定 タスクname := “your_project_name”

scalaVersion := "2.11.6"

libraryDependencies ++= Seq(
"com.typesafe.akka" %% “akka-actor" % "2.3.10"
)!scalacOptions in ThisBuild ++= Seq("-feature")build.sbt• 依存ライブラリの解決• コンパイル• テスト• REPLの起動
Multi-project build- client- server- rootimport sbt._
import Keys._

object IsomorphicBuild extends Build {

lazy val root = project.in(file(“."))
lazy val server = Project(“server", file(“server"))

lazy val client = Project("client", file(“client”))!}project/Build.scalasbtではサブプロジェクトを複数定義できる↓サーバーもクライアントもサブプロジェクトとして定義相似のプロジェクト構造ができる→
import sbt._
import Keys._
import org.scalajs.sbtplugin.ScalaJSPlugin
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._


object IsomorphicBuild extends Build {

lazy val root = project.in(file(".")).aggregate(server, client)

lazy val server = Project(“server", file(“server"))


lazy val client = Project("client", file(“client")).enablePlugins(ScalaJSPlugin).settings(
persistLauncher in Compile := true,
skip in packageJSDependencies := false
)
}project/Build.scalaMake Scala.js project!addSbtPlugin(“org.scala-js" % "sbt-scalajs" % "0.6.2")project/plugins.sbtScala.jsプラグインを追加clientプロジェクトでScala.jsプラグインを有効化
Make Scala.js projectisomorphic!import sbt._
import Keys._
import org.scalajs.sbtplugin.ScalaJSPlugin
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._

object IsomorphicBuild extends Build {

lazy val root = project.in(file(".")).aggregate(server, client)

lazy val server = Project(“server", file(“server"))


lazy val client = Project("client", file(“client")).dependsOn(server).enablePlugins(ScalaJSPlugin).settings(
unmanagedSourceDirectories in Compile +=(sourceDirectory in server).value/"main"/"scala"/"jp.isomorphic.example" / “shared",packageJSDependencies in Compile := {
val base = (packageJSDependencies in Compile).value
IO.copyFile(base, (baseDirectory in server).value / "src/main/resources/js" / base.getName)
base
},
persistLauncher in Compile := true,
skip in packageJSDependencies := false
).settings(Seq(fastOptJS, fullOptJS) map { packageJSKey =>
crossTarget in (Compile, packageJSKey) :=
(baseDirectory in server).value / "src/main/resources/js"
})
}project/Build.scalaサーバーからクライアントにクラスパスを通す(Scalaコンパイルが可能に)コンパイル対象にサーバー側のソースの一部を追加(Scala.jsコンパイルが可能に)Scala.jsのコンパイル結果をサーバー側にコピー
Using CrossProject to buildisomorphic project structure-shared- js- jvmimport sbt.Keys._
import sbt._import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
!!object ApplicationBuild extends Build {

lazy val root = project.in(file("."))


lazy val sharedProject = crossProject.in(file("."))
.settings()
.jvmSettings()
.jsSettings()
)


lazy val js: Project = sharedProject.js.settings()


lazy val jvm: Project = sharedProject.jvm.settings()
}
!ScalaJSPluginのCrossProjectを使えば簡単にisomorphicなプロジェクト構造を作れるscalajs-spa-tutorialを参照scalajs-cross-compile-example,
2. Sharing codes betweenClient and Server• Scala.jsで利用可能なライブラリ• シリアライゼーションによるScalaデータ型の通信• 型安全なAPIの呼び出し• マクロ
Available Libraries• DOM• jQuery• React.js• AngularJSwww.scala-js.orgにもっと多く載っているJSライブラリの型付けされたインターフェース• Scalaz• NICTA/rngScalaライブラリのポート• Scala.Rx• Monifu• autowire• uPickle最初からクロスコンパイル前提で作られたライブラリ※Scalaは型だけ提供。実装はJS。※本家のクロスコンパイルできない部分を修正してJSを提供※ScalaとJSを提供
Pickling (serialization)• クラス階層情報の喪失Scala.jsの大問題:可逆的なJSONのシリアライズリフレクションを使わずにコンパイル時に少ないコードで解決しなければならない。サーバー クライアントJSON{"fruits": [{"color": "yellow"}, {"color": "red"}]}{"points": [{"x": 1, "y": 2}, {"x": 1, "y": 2}]}null [[]]val fruits: List[Fruit] = List(Banana("yellow"), Apple("red"))val p = Point(1,2); val points = List(p, p)val option: Option[Option[Int]] = None通信• 参照同一性の喪失• Optionの扱いScalaデータ型 Scalaデータ型
Cross-compiled pickling librariesきれいなJSON形式クラス階層の保持参照同一性の保持Anyの解決uPickle ○難しいことは忘れてきれいなJSONを吐くことに注力Optionが配列として表現されるPrickle ○ ○可逆性を高めるためメタ情報をJSONに保持させているScala.jsPickling○ ○型の登録処理が事前に必要サポートがあまりよくない
Binary serialization withBooPickle and XHR2def request(method: String, url: String, body: ArrayBuffer): Future[ByteBuffer] = {
val promise = Promise[ByteBuffer]
val xhr = new XMLHttpRequest()
xhr.onreadystatechange = { (e: Event) =>
if (xhr.readyState == 4) { if (xhr.status >= 200 && xhr.status < 300) {
val byteBuffer = TypedArrayBuffer.wrap(xhr.response.asInstanceOf[ArrayBuffer])
promise.success(byteBuffer)
} else promise.failure(AjaxException(xhr))
}}
xhr.open(method, url)
xhr.responseType = "arraybuffer"
xhr.setRequestHeader("Content-Type", "application/octet-stream")
xhr.setRequestHeader("Accept", "application/octet-stream")
xhr.send(body)
promise.future
}val byteBuffer = Pickle.intoBytes(SampleRequest("Hello"))
request(method, url, byteBuffer.arrayBuffer()).map(Unpickle[SampleResponse].fromBytes(_))• JSONではなく、バイナリにシリアライズ• XHR level2のバイナリサポートを利用• JSのArrayBufferを使う• メディアタイプは application/octet-stream• クラス階層、参照の同一性も復元可能
Client-server communicationwith Autowireobject AutowireClient extends autowire.Client[String, Reader, Writer]{
override def doCall(req: Request): Future[String]
}trait MyApi {
def sampleRequest(id: Int): SampleResponse
}object MyApiImpl extends MyApi{
def sampleRequest(id: Int) = SampleResponse(id)
}AutowireClient[MyApi].sampleRequest(1).call().map(println)path("api" / Segments){ s =>
extract(_.request.entity.asString) { e =>
complete {
AutowireServer.route[MyApi](MyApiImpl)(
autowire.Core.Request(s, upickle.read[Map[String, String]](e)))
}}}object AutowireServer extends autowire.Server[String, Reader, Writer]{
val routes = AutowireServer.route[MyApi](MyApiImpl)
}Autowireマクロマジック!!AutowireはAjaxにおけるクライアントーサーバー間のAPIの煩雑さをRPCスタイルのメソッド呼び出しで解決するライブラリ• なぜかRPCは自動で解決される• API呼び出しにおける間違いはコンパイル時に発見される1. sharedでRPCインターフェースを定義2. serverでRPCインターフェースを実装3. serverでルーティング部分関数をマクロにより生成4. serverでルーティング部分関数を呼び出してレスポンスを返す5. clientでAjaxの通信の仕方を実装6. clientでRPC関数を呼び出す
Macroperforming macro expansion AutowireServer.route[jp.isomorphic.example.MyApi](<empty> match {
case autowire.Core.Request(Seq("jp", "isomorphic", "example", "MyApi", "sampleRequest"), (args$macro$1 @_)) => autowire.Internal.doValidate({
<synthetic> <artifact> val x$2 = autowire.Internal.read[String, Int](args$macro$1,scala.util.Left(autowire.Error.Param.Missing("id")), "id", ((x$1) => AutowireServer.read[Int](x$1)));
Nil.$colon$colon(x$2)
}) match {
case scala.$colon$colon((id @ (_: Int @unchecked)), Nil) =>scala.concurrent.Future.successful(MyApiImpl.sampleRequest(id)).map(((x$3) => AutowireServer.write(x$3)))
case _ => $qmark$qmark$qmark
}
}: autowire.Core.Router[String])種明かし:-Ymacro-debug-liteコンパイルオプションでマクロを展開するpackage jp.isomorphic.exampletrait MyApi {
def sampleRequest(id: Int): SampleResponse
}マクロは←から、関数のシグニチャに対してパターンマッチをかける部分関数を作っていた※Scalaのマクロは抽象構文木を操るすごいやつです
3. Sharing ApplicationArchitectureScalaでフロントエンドのアプリケーションをいざ書くときに今までやってきたことをScalaでどう表現するとよいか迷うオブザーバーパターン → ?アーキテクチャをシェアしてコンテクストスイッチのコストを減らそう!
Common Practice:Unidirectional Data Flow• Tell, don t ask.• Fire and forget.FluxScalajs SPA Tutorialがこの共通点からアプローチしている• unidirectional data flow• message passing複雑さに対抗する手段のコンセプトは同じ
Flux in ScalaつまりStoreがActorになったScalajs SPA Tutorial* image from
trait Actor {
type Receive = PartialFunction[Any, Unit]

def receive: Receive

def !(message: Any) = {
receive(message)
}
}trait Dispatcher {
var actors = Set.empty[Actor]

def dispatch(message: Any) = {
actors.foreach { actor =>
actor ! message
}
}

def register(actor: Actor) = {
actors = actors + actor
}

def unregister(actor: Actor) = {
if (actors.contains(actor)) {
actors - actor
}
}
}DispatcherはActorプログラミングにおけるRecipient Listパターンvar Dispatcher = {_listeners: [],register: function(callback) {this._listeners.push(callback);return this;},unregister: function(callback) {var index = this._listeners.indexOf(callback);if (index !== -1) {delete this._listeners[index];}return this;},dispatch: function(...args) {this._listeners.forEach(callback =>callback.apply(this, args));return this;}};※Recipient List: メッセージを複数のアクターに拡散し仕事を分散して処理する際に、拡散先の受信者であるアクター参照の一覧を保持しているもの
object SampleDispatcher extends Dispatcher

object SampleActorProtocol {
case class Foo(message: String)
case class Bar(message: String)
}

object SampleActor extends Actor {
import SampleActorProtocol._

SampleDispatcher.register(this)

def receive: Receive = {
case Foo(message) => println(message)
case Bar(message) => println(message)
}
}!import SampleActorProtocol._
SampleDispatcher.dispatch(Foo("Hello"))Dispatcher.register(function(payload) {if (payload.type === "FOO") {console.log(payload.message);} else if (payload.type === "BAR") {console.log(payload.message);}});!Dispatcher.dispatch({type: "FOO", message: "Hello"});!!!!!!!!!!!• Dispatcherに関数ではなくActorオブジェクトを登録する• payloadを処理する関数はActorのReceive部分関数に相当• payloadはメッセージ
Conclusion• Scala + Scala.jsでクライアントーサーバーを高度に統合できる• (ただし他のシステムとのinteropが犠牲になるかも• みんなScala.jsを使おう!!!

Recommended

PPTX
React.js + Reduxで作るSPA
PDF
サーバサイドエンジニアが 1年間まじめにSPAやってみた
PDF
コンポーネント指向による、Reactのベストプラクティスとバッドプラクティス
PPTX
今からでも遅くない! React事始め
PDF
React.jsでクライアントサイドなWebアプリ入門
PPTX
React を導入した フロントエンド開発
PDF
こんなに使える!今どきのAPIドキュメンテーションツール
PDF
One Time Binding & Digest Loop
PPTX
2016/12/17 ASP.NET フロントエンドタスク入門
PDF
アメブロ2016 アメブロフロント刷新にみる ひかりとつらみ
PDF
ECMAScript6による関数型プログラミング
PDF
おれおれブログシステムにServiceWorkerを導入してみた #serviceworker
PDF
Service worker が拓く mobile web の新しいかたち
PDF
Swagger 入門
PDF
Flux react現状確認会
PDF
ReactをRailsとどっぷり使ってみた話と、フロントエンド×AWSのこれから
PDF
Closure CompilerのES6対応 あるいはES6時代のAltJS生存戦略
PDF
RailsでReact.jsを動かしてみた話
PDF
angular1脳で見るangular2
PDF
React+TypeScriptもいいぞ
PDF
なぜ人は必死でjQueryを捨てようとしているのか
PDF
SIROK技術勉強会 #1 「Reactってなんだ?」
PDF
PHP Application E2E with Capybara
PDF
Ember コミュニティとわたし
PPTX
20160927 reactmeetup
PDF
Closure Compiler Updates for ES6
KEY
capybara で快適なテスト生活を
PDF
Rendr入門: サーバサイドで(も)動かす、Backbone.js
PDF
Railsのエラーログとの付き合い方
PDF
ng-japan 2015 TypeScript+AngularJS 1.3

More Related Content

PPTX
React.js + Reduxで作るSPA
PDF
サーバサイドエンジニアが 1年間まじめにSPAやってみた
PDF
コンポーネント指向による、Reactのベストプラクティスとバッドプラクティス
PPTX
今からでも遅くない! React事始め
PDF
React.jsでクライアントサイドなWebアプリ入門
PPTX
React を導入した フロントエンド開発
PDF
こんなに使える!今どきのAPIドキュメンテーションツール
PDF
One Time Binding & Digest Loop
React.js + Reduxで作るSPA
サーバサイドエンジニアが 1年間まじめにSPAやってみた
コンポーネント指向による、Reactのベストプラクティスとバッドプラクティス
今からでも遅くない! React事始め
React.jsでクライアントサイドなWebアプリ入門
React を導入した フロントエンド開発
こんなに使える!今どきのAPIドキュメンテーションツール
One Time Binding & Digest Loop

What's hot

PPTX
2016/12/17 ASP.NET フロントエンドタスク入門
PDF
アメブロ2016 アメブロフロント刷新にみる ひかりとつらみ
PDF
ECMAScript6による関数型プログラミング
PDF
おれおれブログシステムにServiceWorkerを導入してみた #serviceworker
PDF
Service worker が拓く mobile web の新しいかたち
PDF
Swagger 入門
PDF
Flux react現状確認会
PDF
ReactをRailsとどっぷり使ってみた話と、フロントエンド×AWSのこれから
PDF
Closure CompilerのES6対応 あるいはES6時代のAltJS生存戦略
PDF
RailsでReact.jsを動かしてみた話
PDF
angular1脳で見るangular2
PDF
React+TypeScriptもいいぞ
PDF
なぜ人は必死でjQueryを捨てようとしているのか
PDF
SIROK技術勉強会 #1 「Reactってなんだ?」
PDF
PHP Application E2E with Capybara
PDF
Ember コミュニティとわたし
PPTX
20160927 reactmeetup
PDF
Closure Compiler Updates for ES6
KEY
capybara で快適なテスト生活を
2016/12/17 ASP.NET フロントエンドタスク入門
アメブロ2016 アメブロフロント刷新にみる ひかりとつらみ
ECMAScript6による関数型プログラミング
おれおれブログシステムにServiceWorkerを導入してみた #serviceworker
Service worker が拓く mobile web の新しいかたち
Swagger 入門
Flux react現状確認会
ReactをRailsとどっぷり使ってみた話と、フロントエンド×AWSのこれから
Closure CompilerのES6対応 あるいはES6時代のAltJS生存戦略
RailsでReact.jsを動かしてみた話
angular1脳で見るangular2
React+TypeScriptもいいぞ
なぜ人は必死でjQueryを捨てようとしているのか
SIROK技術勉強会 #1 「Reactってなんだ?」
PHP Application E2E with Capybara
Ember コミュニティとわたし
20160927 reactmeetup
Closure Compiler Updates for ES6
capybara で快適なテスト生活を

Viewers also liked

PDF
Rendr入門: サーバサイドで(も)動かす、Backbone.js
PDF
Railsのエラーログとの付き合い方
PDF
ng-japan 2015 TypeScript+AngularJS 1.3
PPTX
Akka Clusterの耐障害設計
PDF
これからのJavaScriptー関数型プログラミングとECMAScript6
PDF
minne の API 改善
PDF
カヤックHTMLファイ部のUI・UX (第57回 HTML5とか勉強会 / 2015.5.19)
PDF
AngularとOnsen UIで作る最高のHTML5ハイブリッドアプリ
PDF
CRDT in 15 minutes
PDF
ReactとImmutable.jsで関数型を体験してみて思ったこと #scripty06
PDF
React Canvasで作るFlappy Bird
PPTX
32 Strategies for Building a Positive Learning Environment
PPTX
Scala.js for large and complex frontend apps
PDF
プログラミング言語Scala
PPTX
Scala.js: Next generation front end development in Scala
PDF
Rails5とAPIモードについての解説
PDF
Scala.js
PDF
Deploy to Lobi
PDF
WebID and eCommerce
PDF
チームでつくるUIデザイン
Rendr入門: サーバサイドで(も)動かす、Backbone.js
Railsのエラーログとの付き合い方
ng-japan 2015 TypeScript+AngularJS 1.3
Akka Clusterの耐障害設計
これからのJavaScriptー関数型プログラミングとECMAScript6
minne の API 改善
カヤックHTMLファイ部のUI・UX (第57回 HTML5とか勉強会 / 2015.5.19)
AngularとOnsen UIで作る最高のHTML5ハイブリッドアプリ
CRDT in 15 minutes
ReactとImmutable.jsで関数型を体験してみて思ったこと #scripty06
React Canvasで作るFlappy Bird
32 Strategies for Building a Positive Learning Environment
Scala.js for large and complex frontend apps
プログラミング言語Scala
Scala.js: Next generation front end development in Scala
Rails5とAPIモードについての解説
Scala.js
Deploy to Lobi
WebID and eCommerce
チームでつくるUIデザイン

Similar to Isomorphic web development with scala and scala.js

PDF
Scalaでのプログラム開発
PDF
Scala Warrior and type-safe front-end development with Scala.js
PDF
汎用apiサーバの構築
PDF
Scalaの現状と課題
PDF
SmartPhone development guide with CoffeeScript + Node + HTML5 Technology, for...
PDF
実装(3) 【クラウドアプリケーションのためのオブジェクト指向分析設計講座 第32回】
PDF
[Japanese] Skinny Framework で始める Scala #jjug_ccc #ccc_r24
PDF
Scala + Finagleの魅力
PDF
Play framework 2.0のちょっとした紹介
ODP
Next Language Scala
PDF
Scala EE 7 Essentials
 
PPTX
Monacaでつくるハイブリッドアプリ
 
PDF
Yesod(at FPM2012)
PPTX
fluxflex meetup in Tokyo
PPTX
Fluxflex meetup 2011 in Tokyo
PPT
about Thrift
PPT
Thrift
KEY
Dev love関西 forslideshare
PDF
kibayos-PIAX & SkipGraph-071207
PDF
Sencha Touch working with AWS
Scalaでのプログラム開発
Scala Warrior and type-safe front-end development with Scala.js
汎用apiサーバの構築
Scalaの現状と課題
SmartPhone development guide with CoffeeScript + Node + HTML5 Technology, for...
実装(3) 【クラウドアプリケーションのためのオブジェクト指向分析設計講座 第32回】
[Japanese] Skinny Framework で始める Scala #jjug_ccc #ccc_r24
Scala + Finagleの魅力
Play framework 2.0のちょっとした紹介
Next Language Scala
Scala EE 7 Essentials
 
Monacaでつくるハイブリッドアプリ
 
Yesod(at FPM2012)
fluxflex meetup in Tokyo
Fluxflex meetup 2011 in Tokyo
about Thrift
Thrift
Dev love関西 forslideshare
kibayos-PIAX & SkipGraph-071207
Sencha Touch working with AWS

More from TanUkkii

PDF
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
PDF
Distributed ID generator in ChatWork
PPTX
JSON CRDT
PPTX
スケールするシステムにおけるエンティティの扱いと 分散ID生成
PDF
すべてのアクター プログラマーが知るべき 単一責務原則とは何か
PDF
Scalaによる型安全なエラーハンドリング
PPTX
Architecture of Falcon, a new chat messaging backend system build on Scala
PDF
Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...
PDF
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
PPTX
Akka HTTP
PDF
ディープニューラルネット入門
PDF
WaveNet
プログラミング言語のパラダイムシフトーScalaから見る関数型と並列性時代の幕開けー
Distributed ID generator in ChatWork
JSON CRDT
スケールするシステムにおけるエンティティの扱いと 分散ID生成
すべてのアクター プログラマーが知るべき 単一責務原則とは何か
Scalaによる型安全なエラーハンドリング
Architecture of Falcon, a new chat messaging backend system build on Scala
Non-blocking IO to tame distributed systems ー How and why ChatWork uses async...
プログラミング言語のパラダイムシフト(ダイジェスト)ーScalaから見る関数型と並列性時代の幕開けー
Akka HTTP
ディープニューラルネット入門
WaveNet

Isomorphic web development with scala and scala.js

  • 1.
    Isomorphic Webdevelopment withScala & Scala.jsTanUkkiiisomorphic tokyo meetup2015/4/30
  • 2.
    I am ...•@TanUkkii007 on Twitter• Web frontend engineer• だったけどゲーム開発が辛くてサーバーサイドをScalaで開発する人に• Scala業務歴4ヶ月
  • 3.
    Agenda1. 開発環境を共有する2. コードを共有する3.アーキテクチャを共有するクライアントーサーバー間で
  • 4.
    Motivation• Scala +Akka + SprayでAPIサーバーを開発• StrongLoopのApi Exprolerみたいなのを作りたい• Scala.jsでisomorphicにつくる!REST/HTTP server build on Akka Actors
  • 5.
    Why Scala.js?• クライアントーサーバーで同一の開発環境•クライアントーサーバーでコードの共有• 片手間クライアント開発
  • 6.
    1. Sharing developmentenvironment:Building applications with sbt• プロジェクト定義• Scalaのバージョン• 依存ライブラリ• コンパイルオプションJava/Scalaのビルドツール設定 タスクname := “your_project_name”

scalaVersion := "2.11.6"

libraryDependencies ++= Seq(
"com.typesafe.akka" %% “akka-actor" % "2.3.10"
)!scalacOptions in ThisBuild ++= Seq("-feature")build.sbt• 依存ライブラリの解決• コンパイル• テスト• REPLの起動
  • 7.
    Multi-project build- client-server- rootimport sbt._
import Keys._

object IsomorphicBuild extends Build {

lazy val root = project.in(file(“."))
lazy val server = Project(“server", file(“server"))

lazy val client = Project("client", file(“client”))!}project/Build.scalasbtではサブプロジェクトを複数定義できる↓サーバーもクライアントもサブプロジェクトとして定義相似のプロジェクト構造ができる→
  • 8.
    import sbt._
import Keys._
importorg.scalajs.sbtplugin.ScalaJSPlugin
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._


object IsomorphicBuild extends Build {

lazy val root = project.in(file(".")).aggregate(server, client)

lazy val server = Project(“server", file(“server"))


lazy val client = Project("client", file(“client")).enablePlugins(ScalaJSPlugin).settings(
persistLauncher in Compile := true,
skip in packageJSDependencies := false
)
}project/Build.scalaMake Scala.js project!addSbtPlugin(“org.scala-js" % "sbt-scalajs" % "0.6.2")project/plugins.sbtScala.jsプラグインを追加clientプロジェクトでScala.jsプラグインを有効化
  • 9.
    Make Scala.js projectisomorphic!importsbt._
import Keys._
import org.scalajs.sbtplugin.ScalaJSPlugin
import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._

object IsomorphicBuild extends Build {

lazy val root = project.in(file(".")).aggregate(server, client)

lazy val server = Project(“server", file(“server"))


lazy val client = Project("client", file(“client")).dependsOn(server).enablePlugins(ScalaJSPlugin).settings(
unmanagedSourceDirectories in Compile +=(sourceDirectory in server).value/"main"/"scala"/"jp.isomorphic.example" / “shared",packageJSDependencies in Compile := {
val base = (packageJSDependencies in Compile).value
IO.copyFile(base, (baseDirectory in server).value / "src/main/resources/js" / base.getName)
base
},
persistLauncher in Compile := true,
skip in packageJSDependencies := false
).settings(Seq(fastOptJS, fullOptJS) map { packageJSKey =>
crossTarget in (Compile, packageJSKey) :=
(baseDirectory in server).value / "src/main/resources/js"
})
}project/Build.scalaサーバーからクライアントにクラスパスを通す(Scalaコンパイルが可能に)コンパイル対象にサーバー側のソースの一部を追加(Scala.jsコンパイルが可能に)Scala.jsのコンパイル結果をサーバー側にコピー
  • 10.
    Using CrossProject tobuildisomorphic project structure-shared- js- jvmimport sbt.Keys._
import sbt._import org.scalajs.sbtplugin.ScalaJSPlugin.autoImport._
!!object ApplicationBuild extends Build {

lazy val root = project.in(file("."))


lazy val sharedProject = crossProject.in(file("."))
.settings()
.jvmSettings()
.jsSettings()
)


lazy val js: Project = sharedProject.js.settings()


lazy val jvm: Project = sharedProject.jvm.settings()
}
!ScalaJSPluginのCrossProjectを使えば簡単にisomorphicなプロジェクト構造を作れるscalajs-spa-tutorialを参照scalajs-cross-compile-example,
  • 11.
    2. Sharing codesbetweenClient and Server• Scala.jsで利用可能なライブラリ• シリアライゼーションによるScalaデータ型の通信• 型安全なAPIの呼び出し• マクロ
  • 12.
    Available Libraries• DOM•jQuery• React.js• AngularJSwww.scala-js.orgにもっと多く載っているJSライブラリの型付けされたインターフェース• Scalaz• NICTA/rngScalaライブラリのポート• Scala.Rx• Monifu• autowire• uPickle最初からクロスコンパイル前提で作られたライブラリ※Scalaは型だけ提供。実装はJS。※本家のクロスコンパイルできない部分を修正してJSを提供※ScalaとJSを提供
  • 13.
    Pickling (serialization)• クラス階層情報の喪失Scala.jsの大問題:可逆的なJSONのシリアライズリフレクションを使わずにコンパイル時に少ないコードで解決しなければならない。サーバークライアントJSON{"fruits": [{"color": "yellow"}, {"color": "red"}]}{"points": [{"x": 1, "y": 2}, {"x": 1, "y": 2}]}null [[]]val fruits: List[Fruit] = List(Banana("yellow"), Apple("red"))val p = Point(1,2); val points = List(p, p)val option: Option[Option[Int]] = None通信• 参照同一性の喪失• Optionの扱いScalaデータ型 Scalaデータ型
  • 14.
    Cross-compiled pickling librariesきれいなJSON形式クラス階層の保持参照同一性の保持Anyの解決uPickle○難しいことは忘れてきれいなJSONを吐くことに注力Optionが配列として表現されるPrickle ○ ○可逆性を高めるためメタ情報をJSONに保持させているScala.jsPickling○ ○型の登録処理が事前に必要サポートがあまりよくない
  • 15.
    Binary serialization withBooPickleand XHR2def request(method: String, url: String, body: ArrayBuffer): Future[ByteBuffer] = {
val promise = Promise[ByteBuffer]
val xhr = new XMLHttpRequest()
xhr.onreadystatechange = { (e: Event) =>
if (xhr.readyState == 4) { if (xhr.status >= 200 && xhr.status < 300) {
val byteBuffer = TypedArrayBuffer.wrap(xhr.response.asInstanceOf[ArrayBuffer])
promise.success(byteBuffer)
} else promise.failure(AjaxException(xhr))
}}
xhr.open(method, url)
xhr.responseType = "arraybuffer"
xhr.setRequestHeader("Content-Type", "application/octet-stream")
xhr.setRequestHeader("Accept", "application/octet-stream")
xhr.send(body)
promise.future
}val byteBuffer = Pickle.intoBytes(SampleRequest("Hello"))
request(method, url, byteBuffer.arrayBuffer()).map(Unpickle[SampleResponse].fromBytes(_))• JSONではなく、バイナリにシリアライズ• XHR level2のバイナリサポートを利用• JSのArrayBufferを使う• メディアタイプは application/octet-stream• クラス階層、参照の同一性も復元可能
  • 16.
    Client-server communicationwith AutowireobjectAutowireClient extends autowire.Client[String, Reader, Writer]{
override def doCall(req: Request): Future[String]
}trait MyApi {
def sampleRequest(id: Int): SampleResponse
}object MyApiImpl extends MyApi{
def sampleRequest(id: Int) = SampleResponse(id)
}AutowireClient[MyApi].sampleRequest(1).call().map(println)path("api" / Segments){ s =>
extract(_.request.entity.asString) { e =>
complete {
AutowireServer.route[MyApi](MyApiImpl)(
autowire.Core.Request(s, upickle.read[Map[String, String]](e)))
}}}object AutowireServer extends autowire.Server[String, Reader, Writer]{
val routes = AutowireServer.route[MyApi](MyApiImpl)
}Autowireマクロマジック!!AutowireはAjaxにおけるクライアントーサーバー間のAPIの煩雑さをRPCスタイルのメソッド呼び出しで解決するライブラリ• なぜかRPCは自動で解決される• API呼び出しにおける間違いはコンパイル時に発見される1. sharedでRPCインターフェースを定義2. serverでRPCインターフェースを実装3. serverでルーティング部分関数をマクロにより生成4. serverでルーティング部分関数を呼び出してレスポンスを返す5. clientでAjaxの通信の仕方を実装6. clientでRPC関数を呼び出す
  • 17.
    Macroperforming macro expansionAutowireServer.route[jp.isomorphic.example.MyApi](<empty> match {
case autowire.Core.Request(Seq("jp", "isomorphic", "example", "MyApi", "sampleRequest"), (args$macro$1 @_)) => autowire.Internal.doValidate({
<synthetic> <artifact> val x$2 = autowire.Internal.read[String, Int](args$macro$1,scala.util.Left(autowire.Error.Param.Missing("id")), "id", ((x$1) => AutowireServer.read[Int](x$1)));
Nil.$colon$colon(x$2)
}) match {
case scala.$colon$colon((id @ (_: Int @unchecked)), Nil) =>scala.concurrent.Future.successful(MyApiImpl.sampleRequest(id)).map(((x$3) => AutowireServer.write(x$3)))
case _ => $qmark$qmark$qmark
}
}: autowire.Core.Router[String])種明かし:-Ymacro-debug-liteコンパイルオプションでマクロを展開するpackage jp.isomorphic.exampletrait MyApi {
def sampleRequest(id: Int): SampleResponse
}マクロは←から、関数のシグニチャに対してパターンマッチをかける部分関数を作っていた※Scalaのマクロは抽象構文木を操るすごいやつです
  • 18.
  • 19.
    Common Practice:Unidirectional DataFlow• Tell, don t ask.• Fire and forget.FluxScalajs SPA Tutorialがこの共通点からアプローチしている• unidirectional data flow• message passing複雑さに対抗する手段のコンセプトは同じ
  • 20.
  • 21.
    trait Actor {
typeReceive = PartialFunction[Any, Unit]

def receive: Receive

def !(message: Any) = {
receive(message)
}
}trait Dispatcher {
var actors = Set.empty[Actor]

def dispatch(message: Any) = {
actors.foreach { actor =>
actor ! message
}
}

def register(actor: Actor) = {
actors = actors + actor
}

def unregister(actor: Actor) = {
if (actors.contains(actor)) {
actors - actor
}
}
}DispatcherはActorプログラミングにおけるRecipient Listパターンvar Dispatcher = {_listeners: [],register: function(callback) {this._listeners.push(callback);return this;},unregister: function(callback) {var index = this._listeners.indexOf(callback);if (index !== -1) {delete this._listeners[index];}return this;},dispatch: function(...args) {this._listeners.forEach(callback =>callback.apply(this, args));return this;}};※Recipient List: メッセージを複数のアクターに拡散し仕事を分散して処理する際に、拡散先の受信者であるアクター参照の一覧を保持しているもの
  • 22.
    object SampleDispatcher extendsDispatcher

object SampleActorProtocol {
case class Foo(message: String)
case class Bar(message: String)
}

object SampleActor extends Actor {
import SampleActorProtocol._

SampleDispatcher.register(this)

def receive: Receive = {
case Foo(message) => println(message)
case Bar(message) => println(message)
}
}!import SampleActorProtocol._
SampleDispatcher.dispatch(Foo("Hello"))Dispatcher.register(function(payload) {if (payload.type === "FOO") {console.log(payload.message);} else if (payload.type === "BAR") {console.log(payload.message);}});!Dispatcher.dispatch({type: "FOO", message: "Hello"});!!!!!!!!!!!• Dispatcherに関数ではなくActorオブジェクトを登録する• payloadを処理する関数はActorのReceive部分関数に相当• payloadはメッセージ
  • 23.
    Conclusion• Scala +Scala.jsでクライアントーサーバーを高度に統合できる• (ただし他のシステムとのinteropが犠牲になるかも• みんなScala.jsを使おう!!!

[8]ページ先頭

©2009-2025 Movatter.jp