Video: https://www.youtube.com/watch?v=b6yLwvNSDck
Here's the showdown you've been waiting for: Node.js vs Play Framework. Both are popular open source web frameworks that are built for developer productivity, asynchronous I/O, and the real time web. But which one is easier to learn, test, deploy, debug, and scale? Should you pick Javascript or Scala? The Google v8 engine or the JVM? NPM or Ivy? Grunt or SBT? Two frameworks enter, one framework leaves.
This is the English version of the presentation. For the version with Japanese subtitles, see http://www.slideshare.net/brikis98/nodejs-vs-play-framework-with-japanese-subtitles
10. var http = require('http');
http.createServer(function (req, res) {
res.writeHead(200, {'Content-Type': 'text/plain'});
res.end('Hello Worldn');
}).listen(1337, '127.0.0.1');
console.log('Server running at http://127.0.0.1:1337/');
server.js
The “Hello World” Node app: 1 file, 6 lines of code.
11. 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
The “Hello World” Express app: 1 file, 8 lines of code.
38. var regForm = forms.create({
name: fields.string({required: true}),
age: fields.number({min: 18})
Forms, node-formidable, validator.js. Play form binding and validation API.
Form binding and validation
});
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!")
)
39. // 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.");
});
POST /clients Clients.create
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")
}
bodyParser, xml2js, node-formidable. Play JSON, XML, File Upload APIs.
JSON, XML, File Upload
41. socket.io: server & client APIs;
WebSockets, Flash Sockets, polling, etc.
Play WebSockets, Comet, and
EventSource APIs. Server-side only.
Real-time web
// 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) { … });
42. ● Express.js is a minimal framework ● Play is a full stack 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
43. Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
44. Learn
Develop
Test
Secure
Build
The framework scorecard
Deploy
Debug
Scale
Maintain
Share
10 7
8 10
52. "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")
}
Functional testing: use Play’s built-in
functional test helpers.
53. 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" }
}
}
}
UI testing: use Play’s built-in integration test
helpers and built-in Selenium support.
65. Play uses SBT as the build system. SBT is an
interactive build system.
66. 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")
}
In SBT, build definitions are written in Scala!
… But the learning curve is very steep.
81. Use the SBT Native Packager to package the
app as tgz, deb, RPM, etc.
82. dbConfig = {
host: "localhost",
port: 5984,
dbName: "customers"
}
conf/application.conf
val host = Play.current.configuration.getString("dbConfig.host")
app/controllers/Application.scala
Configuration: Play comes with Typesafe Config
83. 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
102. 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 experience #1: Play and Node.js are very
fast in a service oriented architecture with NIO.
103. // 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!");
});
LinkedIn experience #2: Play is ok with blocking I/O
& CPU/memory bound use cases. Node.js is not.
118. // 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
128. 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)
}
}
Very expressive: case classes, pattern
matching, lazy, option, implicits
131. 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
}
}
No callback hell!
144. StackOverflow 10,698
53,555 Questions
Google Group
14,199 Members 11,577
Google Group
~400 Posts/Month ~1,100
StackOverflow, mailing list activity as of 08/12/14
145. 4 langpop.com 18
10 TIOBE 39
5 CodeEval 12
7 IEEE Spectrum 17
1 RedMonk 13
12 Lang-Index 26
Language Popularity as of 08/12/14
158. Both frameworks are great. Decide based on
strengths/weakness, not my arbitrary score!
159. Use node.js if:
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
160. Don’t use node.js if:
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
161. Use Play if:
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
162. Don’t use Play if:
1. You don’t have time to master Play, Scala, and SBT
2. You hate functional programming or static typing