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

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

More Related Content

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

What's hot

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

Viewers also liked

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

Similar to Isomorphic web development with scala and scala.js

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

More from TanUkkii

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

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