Node.js vs Play Framework VS
Jan 15, 2015
Node.js vs Play Framework
VS
Node.js: server-side JavaScript runtime environment; open source; single threaded; non-blocking I/O.
Node.js: サーバサイドJSランタイム、OSS、シングルスレッド、非同期 I/O
express.js: the most popular web framework for Node.js.
express.js: Node.js で一番人気のWebフレームワーク
Play Framework: Java/Scala web framework; open source; multithreaded; non-blocking I/O.
Play: Java/Scala Webフレームワーク、OSS、マルチスレッド、非同期 I/O
Former Play Tech Lead at LinkedIn. Long time Node.js user.
Yevgeniy Brikman
元LinkedIn社Play Tech Lead。ベテランNode.jsユーザ
The framework scorecard
Learn
Develop
Test
Secure
Build
Deploy
Debug
Scale
Maintain
Share
1
For each feature we discuss...
Much worse than most frameworks
About the same as most frameworks
Much better than most frameworks
510
1 = 酷い、5 = 平均的、10 = 優秀
The framework scorecard
Learn
Develop
Test
Secure
Build
Deploy
Debug
Scale
Maintain
Share
Node.js: 1-click installers for every OSOSにかかわらずインストーラは万全
The “Hello World” Node app: 1 file, 6 lines of code.
var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello World\n');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
server.js
The “Hello World” Express app: 1 file, 8 lines of code.
var express = require('express');
var app = express();
app.get('/', function(req, res){
res.send('Hello World');
});
var server = app.listen(1337, function() {
console.log('Listening on port %d', server.address().port);
});
server.js
Run using node <filename>. Starts instantly!
Hit http://localhost:1337 to test
Node Beginner Book, Mastering Node.js, Node: Up and Running, Node.js in Action
Express Web Application Development Express.js Guide
And much, much more Tons of resources; very gradual learning curve.
リソースが豊富、緩やかな学習曲線
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10
Play: download from playframework.com, extract, add activator to your PATH
Play:ダウンロードし、展開し、 activator をPATH に追加する
Generate a new app using activator new
The “Hello World” Play app: ~35 files and folders
Run the app using activator run
(Downloading all dependencies can take a while the first time around)
初回はjarのダウンロードに時間がかかる
Hit http://localhost:9000 to test
Play Framework Documentation
Activator Templates
Play for Scala Learning Play Framework 2
Ultimate Guide to Getting Started with Play. Not as many resources; steep learning curve.
リソースが少なめ、急勾配の学習曲線
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
Routing
GET clients/:id Clients.show(id: Long)
def show(id: Long) = Action { request =>
getClient(id).map { client =>
Ok(views.html.clients.show(client))
}
}
app.get('clients/:id', function(req, res) {
getClient(req.params.id, function(client) {
res.render('show', client);
});
});
RESTful routing. Extracts query & path params.
RESTful routing. Extracts query & path params. Type safe. Actions are composable. Reverse routing.
Templates
@(name: String, headline: String)
<div class="client">
<h1>@name</h1>
<div class="headline">
Headline: @headline
</div>
</div>
<div class="client">
<h1>{{name}}</h1>
<div class="headline">
Headline: {{headline}}
</div>
</div>
Many template options: handlebars, mustache, dust, jade, etc. Most support client-side rendering!
Twirl templates are compiled into Scala functions: type safe and composable! Other template types via plugins.
i18n
Translations: i18next-node, i18n-node. Formatting: moment.js, numeral.js.
Translations: Play i18n API. Formatting: Java formatting libraries.
<div class="client">
<h1>{{name}}</h1>
<div class="headline">
{{t "headline.label" headline=headline}}
</div>
</div>
@(name: String, headline: String)
<div class="client">
<h1>@name</h1>
<div class="headline">
Messages("headline.label", headline)
</div>
</div>
Form binding and validation
Forms, node-formidable, validator.js. Play form binding and validation API.
var regForm = forms.create({
name: fields.string({required: true}),
age: fields.number({min: 18})
});
regForm.handle(req, {
success: function(form) { ... },
error: function(form) { ... }
});
val regForm = Form(mapping(
"name" -> nonEmptyText,
"age" -> number(min = 18)
)(UserData.apply)(UserData.unapply))
regForm.bindFromRequest.fold(
err => BadRequest("Validation error"),
data => Ok(s"Hi $data.name!")
)
JSON, XML, File Upload
bodyParser, xml2js, node-formidable. Play JSON, XML, File Upload APIs.
// Automatically parse application/json body
app.use(bodyParser.json());
app.post('/clients', function (req, res, next) {
var name = req.body.name;
var age = req.body.age;
res.send(name + " is " + age + " years old.");
});
case class Person(name: String, age: Int)
implicit val prsnFmt = Json.format[Person]
def create = Action(parse.json) { request =>
val person = request.body.as[Person]
Ok(s"$person.name is $person.age years old")
}
POST /clients Clients.create
Data
Slick, Anorm, Ebean, JPAMySQL, MariaDB, PostgreSLQ, SQLite, Oracle, SQL Server,
DB2, Derby, H2SQLSequelize, Bookshelf.js, node-orm2
MySQL, MariaDB, PostgreSQL, SQLite
NoSQLmongojs/mongoose, cassandra-client, cradle/nano, node_redis, node-neo4j
MongoDB, Cassandra, CouchDB, Redis, Neo4j
ReactiveMongo, DataStax, sprouch, play-plugins-redis, Neo4j-play, JPA
MongoDB, Cassandra, CouchDB, Redis, Neo4j
Cachingnode-memcached, connect-cachememcached, in-memory (not recommended)
play2-memcached, memcontinuationed, Play Cache, ehcache, Guava
memcached, in-memory
Schemasnode-db-migrate, node-migrate Play database evolutions
Real-time web
socket.io: server & client APIs; WebSockets, Flash Sockets, polling, etc.
Play WebSockets, Comet, and EventSource APIs. Server-side only.
// server code
io.on('connection', function (socket) {
socket.emit('msg', 'Server says hi!');
socket.on('msg', function (msg) { … });
});
def chat = WebSocket.acceptWithActor {
request => out => Props(new Chat(out))
}
class Chat(out: ActorRef) extends Actor {
def receive = {
case m: String => out ! s"Got msg: $m"
}
}
// client code
socket.emit('msg', 'Client says hi!');
socket.on('msg', function (msg) { … });
● Play is a full stack framework● Express.js is a minimal framework
● You need plugins for most tasks
● Finding good plugins takes time
● Gluing plugins together takes time
● There are defaults for most tasks
● Defaults are mostly high quality
● All defaults can be replaced
Express.js:ミニマル、プラグインを多く使う。 Play:フルスタック
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
Unit testing: Jasmine, Mocha, QUnit, nodeunit, Expresso or Vows
Functional testing: use supertest or call server.listen directly.
var request = require('supertest')
, app = require('express')();
app.get('/user', function(req, res){
res.send(200, { name: 'tobi' });
});
request(app)
.get('/user')
.expect('Content-Type', /json/)
.expect(200);
UI testing: phantom.js or zombie.js
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10
Unit testing: junit, ScalaTest, specs2, or testng
Functional testing: use Play’s built-in functional test helpers.
"respond to the index Action" in new App(FakeApplication()) {
val Some(result) = route(FakeRequest(GET, "/Bob"))
status(result) mustEqual OK
contentType(result) mustEqual Some("text/html")
charset(result) mustEqual Some("utf-8")
contentAsString(result) must include ("Hello Bob")
}
UI testing: use Play’s built-in integration test helpers and built-in Selenium support.
class ExampleSpec extends PlaySpec with OneServerPerSuite with OneBrowserPerSuite {
"The OneBrowserPerTest trait" must {
"provide a web driver" in {
go to (s"http://localhost:$port/testing")
pageTitle mustBe "Test Page"
click on find(name("b")).value
eventually { pageTitle mustBe "scalatest" }
}
}
}
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
Security
CSRFFilter(not enabled by default!)
CSRFConnect CSRF Middleware(not enabled by default!)
XSSDepends on template engine Twirl escapes correctly
InjectionVulnerabilities: eval, setTimeout, setInterval, new Function
Few vulnerabilities of this sort
HeadersHelmet middleware SecurityHeadersFilter
Authpassport.js, everyauth SecureSocial, deadbolt, play-authenticate
AdvisoriesNode Security Project Play Security Vulnerabilities
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
Node.js uses NPM to manage dependencies and basic build commands
{
"name": "Hello World App",
"version": "0.0.3",
"dependencies": {
"express": "4.8.0",
"underscore": "1.6.0"
},
"scripts": {
"test": "node tests/run.js"
}
}
Node.jsは依存性管理や簡単なコマンド定義にNPMを使う
Many options available for more complicated builds: grunt.js, gulp.js, or broccoli
より複雑なビルドの場合、 grunt.js, gulp.js, broccoliのようなツールを使う
Thousands of plugins for all common build tasksプラグインが何千もある
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10
Play uses SBT as the build system. SBT is an interactive build system.
Play は SBT という対話的ビルドツールを利用
In SBT, build definitions are written in Scala! … But the learning curve is very steep.
object AppBuild extends Build {
lazy val root = Project(id = "root", base = file(".")).settings(
name := "test-play-app",
version := version,
libraryDependencies += Seq(
"org.scala-tools" % "scala-stm_2.11.1" % "0.3",
"org.apache.derby" % "derby" % "10.4.1.3"
)
)
def version = Option(System.getProperty("version")).getOrElse("0.0.3")
}
ビルドファイルをScalaで書く。でもビギナーには厳しい
Dependencies are managed using Ivy: familiar, but slow.
Ivyで依存性を管理する。使い慣れてるけど、実行性能は遅め
Play uses sbt-web to build static content: few plugins; depends on Rhino (slow) or node.js (!).
Playは静的コンテンツのビルドに sbt-webを利用する。プラグインが少ない
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
Many node-friendly hosting options: Heroku, Joyent, Azure, OpenShift, Nodejitsu
Node.jsをサポートするホスティング
Monitoring: New Relic, Nodetime, AppDynamics, node-monitor
Use cluster to run one node instance per CPU
if (cluster.isMaster) {
for (var i = 0; i < numCPUs; i++) {
cluster.fork(); // Fork workers
}
cluster.on('exit', function(worker, code, signal) {
console.log('worker ' + worker.process.pid + ' died');
});
} else {
http.createServer(function(req, res) {
// ...
}).listen(8000);
}
cluster を使ってコア毎にインスタンスを稼働
Use forever, monit, or Domain to handle crashes.クラッシュ対処
Configuration: node-config or nconf
var config = require('config');
var host = config.get('dbConfig.host');
{
"dbConfig": {
"host": "localhost",
"port": 5984,
"dbName": "customers"
}
}
server.js
config/default.json
Use nginx, apache, or ATS to load balance, serve static content, terminate SSL
Client
Data Center
Reverse proxy(e.g. nginx) DB
Static server(e.g. nginx)
Node instanceNode
instanceNode instanceNode
instanceNode instance
Node instanceNode
instanceNode instanceNode
instance
Node instanceNode
instanceNode instanceNode
instance
ロードバランサー、静的コンテンツ、 SSL Termination
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8
A few Play-friendly hosting options: Heroku, playframework-cloud, CloudBees
Playをサポートするホスティング
Monitoring: New Relic, metrics-play
Use the SBT Native Packager to package the app as tgz, deb, RPM, etc.
Appをネイティブパッケージとして包装できる
Configuration: Play comes with Typesafe Config
val host = Play.current.configuration.getString("dbConfig.host")
dbConfig = {
host: "localhost",
port: 5984,
dbName: "customers"
}
app/controllers/Application.scala
conf/application.conf
Use nginx, apache, or ATS to load balance, serve static content, terminate SSL
Client
Data Center
Reverse proxy(e.g. nginx)
Play app
DB
Static server(e.g. nginx)
Play app
Play app
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
Node.js: use IntelliJ or node-inspector to debugNode.js:IntelliJでデバッグできる
Use DTrace, TraceGL, node-stackviz, and node-profiler to debug perf issues
性能問題のデバッグ
Use winston, log4js, or bunyan for logging
var winston = require("winston");
winston.info("CHILL WINSTON! ... I put it in the logs.");
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
Play runs on the JVM, so you can use your favorite IDE to debug: IntelliJ, Eclipse, NetBeans
Play:好きなIDEを使える
In dev, Play shows errors right in the browser開発モードでエラーがブラウザで表示される
Use YourKit, VisualVM, BTrace, and jmap to debug perf issues
Use logback, slf4j, or log4j for logging
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10 10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10 10
Part 1: scaling for lots of traffic
大量のトラフィックのためのスケーリング
TechEmpower benchmarks. Warning: microbenchmarks are not a substitute for real world perf testing!
JSON serialization
Single query (Note: JDBC is blocking!)
Multiple queries(Note: JDBC is blocking!)
LinkedIn experience #1: Play and Node.js are very fast in a service oriented architecture with NIO.
Internet Load Balancer
Frontend Server
Frontend Server
Frontend Server
Backend Server
Backend Server
Backend Server
Backend Server
Backend Server
Data Store
Data Store
Data Store
Data Store
LinkedInの感想:Node.jsもPlayもとても速い
LinkedIn experience #2: Play is ok with blocking I/O & CPU/memory bound use cases. Node.js is not.
// BAD: write files synchronously
fs.writeFileSync('message.txt', 'Hello Node');
console.log("It's saved, but you just blocked ALL requests!");
// Good: write files asynchronously
fs.writeFile('message.txt', 'Hello Node', function (err) {
console.log("It's saved and the server remains responsive!");
});
同期I/O、高メモリ使用率と高CPU使用率の場合、Node.jsの性能が落ちる
Part 2: scaling for large teams and projects
大きいチーム・プロジェクトのためのスケーリング
Node.js: best for small projects, short projects, small teams.
Node.js:小さいプロジェクト、短いプロジェクト、小さいチームにいい
Play: best for longer projects. Slower ramp up, but scales well with team & project size.
Play:より大きいプロジェクトにスケールできる
For comparison: Spring MVC
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
Maintenance: the good partsメンテナンス:「良いパーツ」
Functional programming: first class functions, closures, underscore.js
関数型プログラミング
JavaScript is ubiquitous...JSは至る所にある
...Which means you can share developers, practices, and even code: rendr, derby, meteor
そのため、デベロッパー、情報、コードは入手しやすい
Node core is stable and mature. Bugs, regressions, and backwards incompatibility are rare.
安定したコア。バグや後方互換性の問題は比較的少ない
Maintenance: the bad parts
メンテナンス:「悪いパーツ」
'' == '0' // false
0 == '' // true
0 == '0' // true
false == 'false' // false
false == '0' // true
false == undefined // false
false == null // false
null == undefined // true
' \t\r\n ' == 0 // true
Bad Parts
// Default scope is global
var foo = "I'm a global variable!"
// Setting undeclared variables puts them in global scope too
bar = "I'm also a global variable!";
if (foo) {
// Look ma, no block scope!
var baz = "Believe it or not, I'll also be a global variable!"
}
Awful Parts
this keyword
doSomethingAsync(req1, function(err1, res1) {
doSomethingAsync(req2, function(err2, res2) {
doSomethingAsync(req3, function(err3, res3) {
doSomethingAsync(req4, function(err4, res4) {
// ...
});
});
});
});
Callback hell: control flow, error handling, and composition are all difficult
コールバック地獄
Many NPM packages are NOT stable or mature.Incompatibility + bugs + dynamic typing = pain.
NPMに不安定なパッケージが多い。非互換性 + バグ + 動的型付け = 苦痛
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
3
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
3
Maintenance: the good parts
Functional programming
def sort(a: List[Int]): List[Int] = {
if (a.length < 2) a
else {
val pivot = a(a.length / 2)
sort(a.filter(_ < pivot)) :::
a.filter(_ == pivot) :::
sort(a.filter(_ > pivot))
}
}
Powerful type system
Very expressive: case classes, pattern matching, lazy, option, implicits
val NameTagPattern = "Hello, my name is (.+) (.+)".r
val ListPattern = "Last: (.+). First: (.+).".r
// Case classes automatically generate immutable fields, equals, hashCode, constructor
case class Name(first: String, last: String)
// Use Option to avoid returning null if there is no name found
def extractName(str: String): Option[Name] = {
Option(str).collectFirst {
// Pattern matching on regular expressions
case NameTagPattern(fname, lname) => Name(fname, lname)
case ListPattern(lname, fname) => Name(fname, lname)
}
}
表現力が高い
Runs on the JVM; interop with Java.
Concurrency & streaming tools: Futures, Akka, STM, threads, Scala.rx, Async/Await, Iteratees
No callback hell!
def index = Action {
// Make 3 sequential, async calls
for {
foo <- WS.url(url1).get()
bar <- WS.url(url2).get()
baz <- WS.url(url3).get()
} yield {
// Build a result using foo, bar, and baz
}
}
Good IDE support
Maintenance: the bad parts
Slow compilerコンパイラ遅い
Fortunately, Play/SBT support incremental compilation and hot reload!
Play/sbt はインクリメンタルコンパイラと hot reloading があるから大丈夫!
Complexity
More complexity
Play is stable, but not mature: backwards incompatible API changes every release.
Play は安定だがまだ成長期。APIはよく変わる
Even worse: Scala is not binary compatible between releases!
Scala はリリース間でバイナリ互換性を持たない!
Backwards incompatibility = pain.… Static typing makes it a little more manageable.
バイナリ互換性が無いのは辛いけど、静的な型によって少しは緩和される
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
3 8
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
3 8
351Contributors544
Watchers2,376 576
Stars31,332 5,077
Github activity as of 08/10/14
Forks6,970 1,872
PR’s3,066 2,201
10,698StackOverflow Questions53,555
Google Group Members14,199 11,577
Google Group Posts/Month ~400 ~1,100
StackOverflow, mailing list activity as of 08/12/14
18langpop.com4
TIOBE10 39
CodeEval5 12
Language Popularity as of 08/12/14
IEEE Spectrum7 17
RedMonk1 13
Lang-Index12 26
人気ランキング
88,000 packages in NPM ~80 Play Modules
Joyent offers commercial support for Node.js
Typesafe offers commercial support for Play
有償サポート
Play in production
49LinkedIn1,172
Indeed3,605 179
CareerBuilder214 16
Open jobs as of 08/13/14採用募集数
9,037LinkedIn82,698
Indeed2,267 206
CareerBuilder447 30
Candidates as of 08/13/14採用候補者数
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
3 8
10 7
Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
10 10
86
10 7
8 7
10
10
10
10
3 8
10 7
Final score
85
84
Final score
85
84
山田くん、これ全員の座布団全部持って行きなさい!
Both frameworks are great. Decide based on strengths/weakness, not my arbitrary score!
結論:どちらもすごい!
1. You’re building small apps with small teams
2. You already have a bunch of JavaScript ninjas
3. Your app is mostly client-side JavaScript
4. Your app is mostly real-time
5. Your app is purely I/O bound
Use node.js if:
向き: 小チーム小アプリ、JavaScript ニンジャ
1. You don’t write lots of automated tests
2. Your code base or team is going to get huge
3. You do lots of CPU or memory intensive tasks
Don’t use node.js if:
不向き: 自動テスト書かない人、大チーム、高 CPU か 高RAM
1. You’re already using the JVM
2. You like type safety and functional programming
3. Your code base or team is going to get big
4. You want a full stack framework
5. You need flexibility: non-blocking I/O, blocking I/O, CPU intensive tasks, memory intensive tasks
Use Play if:
向き: JVM ユーザ、型安全性、関数型が好き、大チーム
1. You don’t have time to master Play, Scala, and SBT
2. You hate functional programming or static typing
Don’t use Play if:
不向き: Play/Scala/sbt を勉強してる時間が無い、型安全性、関数型が嫌い
Questions?