-
1.
Crank Up Your Apps With
Jim Crossley
MagicRuby
Creative Commons BY-SA 3.0
Feb 2011
1
-
2.
Jim who?
• “recovering java architect”
• java since ’96, ruby since ’06
• emacs and guitars since forever
• Red Hat Senior Engineer
• proud member of
2
-
3.
3
-
4.
Bob McWhirter
4
-
5.
Agenda
• High-level BS and Development
Process
• Web, Messaging, Scheduling,
Services, Clustering
5
-
6.
TorqueBox
the power of JBoss with the expressiveness of Ruby
6
-
7.
TorqueBox: what?
• A “real” app server for Ruby
• Founded in 2008
• Bob’s “labor of love”
• 100% open-source, LGPL license
• Reflects a strong commitment to
Ruby from Red Hat
7
-
8.
TorqueBox: why?
• “Native” support for Rack apps
• Built-in background processing
• Built-in scheduling
• Built-in clustering
• Easily scalable
• Optionally enterprisey
8
-
9.
JRuby
a good idea done well
9
-
10.
JRuby: why?
• Very fast runtime
• Real threads
• Java libraries
• Java tools
• Healthy community
10
-
11.
JRuby: why not?
• Slower start up
• Native gems
• FFI
• C extension support
• Some Ruby libs not thread-safe
11
-
12.
JBoss AS
but before we get to that...
12
-
13.
Enterprise Java
misconceptions
13
-
14.
Is it
Scary?
14
-
15.
Acronymious?
WAR JAXBEAR JSTL SLSB
JSF JNLPJTA JPA JNI JCK
JAX-RPC JDOM JAXP JAX-WS
JSE JEE JMM JTS JWS
NIO JDO AWT JMX
JLS JAR JRE JCP
JSR JNDI
JSP SAF EJB JME POJO
JMSJDBC JDK JVM
JAX-RS JPQL
NPE
15
-
16.
Bloated?
16
-
17.
Bewildering?
17
-
18.
Enterprise Java is all that
and more! but...
18
-
19.
I promise...
• No XML
• No Java *
• No war files *
• Only Ruby, YAML and...
* Unless you really want to
19
-
20.
JBoss AS
the enterprisey good parts
20
-
21.
JBoss AS6
• Tomcat for web
• Infinispan for caching
• HornetQ for messaging
• Quartz for scheduling
• mod_cluster for clustering
21
-
22.
AS = Application Server
• Not just “web server + interpreter”
• More like initd than httpd
• Can host multiple, disparate apps
simultaneously
• Provides basic services to all the
apps it hosts
22
-
23.
Hot Deployment
$JBOSS_HOME/server/default/deploy/
• anything added to deploy/
will get deployed
• anything removed from
deploy/ will get
undeployed
• anything updated in
deploy/ will get redeployed
• TorqueBox deployers
make JBoss grok YAML
and ruby archives
23
-
24.
Hot Deployment
$JBOSS_HOME/server/default/deploy/
• deployment
anything added to deploy/
will get deployed
descriptors
• anything removed from
deploy/ will get
undeployed
• anything updated in
deploy/ will get redeployed
• TorqueBox deployers
make JBoss grok YAML
and ruby archives
24
-
25.
Hot Deployment
$JBOSS_HOME/server/default/deploy/
• anything added to deploy/
will get deployed
• anything removed from
deploy/ will get
undeployed
• anything updated in
zip files
deploy/ will get redeployed
• (archives)
TorqueBox deployers
make JBoss grok YAML
and ruby archives
25
-
26.
Development Process
how do they work?
26
-
27.
easy install
$ wget http://torquebox.org/torquebox-dev.zip
$ unzip torquebox-dev.zip
$ export TORQUEBOX_HOME=$PWD/torquebox-1*
$ export JBOSS_HOME=$TORQUEBOX_HOME/jboss
$ export JRUBY_HOME=$TORQUEBOX_HOME/jruby
$ export PATH=$JRUBY_HOME/bin:$PATH
27
-
28.
easy install
$ wget http://torquebox.org/torquebox-dev.zip
$ unzip torquebox-dev.zip
$ export TORQUEBOX_HOME=$PWD/torquebox-1*
$ export JBOSS_HOME=$TORQUEBOX_HOME/jboss
$ export JRUBY_HOME=$TORQUEBOX_HOME/jruby
$ export PATH=$JRUBY_HOME/bin:$PATH
28
-
29.
easy install
$ wget http://torquebox.org/torquebox-dev.zip
$ unzip torquebox-dev.zip
$ export TORQUEBOX_HOME=$PWD/torquebox-1*
$ export JBOSS_HOME=$TORQUEBOX_HOME/jboss
$ export JRUBY_HOME=$TORQUEBOX_HOME/jruby
$ export PATH=$JRUBY_HOME/bin:$PATH
29
-
30.
easy install
$ wget http://torquebox.org/torquebox-dev.zip
$ unzip torquebox-dev.zip Make sure the jruby
found in your path is in
$JRUBY_HOME/bin.
$ export TORQUEBOX_HOME=$PWD/torquebox-1*
$ export JBOSS_HOME=$TORQUEBOX_HOME/jboss
$ export JRUBY_HOME=$TORQUEBOX_HOME/jruby
$ export PATH=$JRUBY_HOME/bin:$PATH
30
-
31.
easy install
$ jruby -S gem install bundler
$ jruby -S gem install rails
$ jruby -S gem install sinatra
31
-
32.
rake tasks
Rakefile
require "org.torquebox.rake-support"
32
-
33.
rake tasks
rake torquebox:run
Run TorqueBox server
rake torquebox:deploy[context_path]
Deploy the app in the current directory
rake torquebox:undeploy
Undeploy the app in the current directory
33
-
34.
rake tasks
Start torquebox:run in its own
shell and leave it running. Instead
of script/server or shotgun
or thin or whatever else,
use torquebox:deploy.
34
-
35.
deployment descriptors
torquebox:deploy creates
a deployment descriptor in the
JBoss deploy/ directory
35
-
36.
deployment descriptors
deploy/myapp-{rails,rack}.yml
application:
root: /path/to/myapp
env: development
web:
context: myapp
host: www.yourhost.com
static: public
rackup: config.ru
environment:
MAIL_HOST: mail.yourhost.com
REPLY_TO: you@yourhost.com
36
-
37.
deployment descriptors
deploy/myapp-{rails,rack}.yml
application:
root: /path/to/myapp
env: development
web:
context: myapp The fully-qualified
host: www.yourhost.com path to the app.
static: public This will be the value
rackup: config.ru
environment: of either
RAILS_ROOT or
MAIL_HOST: mail.yourhost.com
REPLY_TO: you@yourhost.com RACK_ROOT
37
-
38.
deployment descriptors
deploy/myapp-{rails,rack}.yml
application:
root: /path/to/myapp
env: development
web:
context: myapp
host: www.yourhost.comThe runtime mode of
static: public the app. This will be
rackup: config.ru
environment:
either RAILS_ENV
MAIL_HOST: mail.yourhost.comor RACK_ENV
REPLY_TO: you@yourhost.com
38
-
39.
deployment descriptors
The app’s context path
deploy/myapp-{rails,rack}.yml
(or “sub URI”):
http://localhost:8080/myapp
application: Can be set via rake:
root: rake torquebox:deploy[myapp]
/path/to/myapp
env: development The default is root:
web: http://localhost:8080/
context: myapp
host: www.yourhost.com
static: public
rackup: config.ru
environment:
MAIL_HOST: mail.yourhost.com
REPLY_TO: you@yourhost.com
39
-
40.
deployment descriptors
deploy/myapp-{rails,rack}.yml
application:
root: /path/to/myapp A list of virtual
env: development hostnames to which
web: to bind the app.
context: myapp
host: www.yourhost.com
static: public
rackup: config.ru
environment:
MAIL_HOST: mail.yourhost.com
REPLY_TO: you@yourhost.com
40
-
41.
deployment descriptors
deploy/myapp-{rails,rack}.yml
application:
root: /path/to/myapp
env: development
web:
The location of the
context: myapp app’s static
host: www.yourhost.com content, either
static: public absolute or relative
rackup: config.ru to the app’s root.
environment:
MAIL_HOST: mail.yourhost.com
REPLY_TO: you@yourhost.com
41
-
42.
deployment descriptors
deploy/myapp-{rails,rack}.yml
application:
root: /path/to/myapp
env: development
web:
The name of the
context: myapp rackup script used to
host: www.yourhost.com boot your app
static: public
rackup: config.ru
environment:
MAIL_HOST: mail.yourhost.com
REPLY_TO: you@yourhost.com
42
-
43.
deployment descriptors
deploy/myapp-{rails,rack}.yml
application:
root: /path/to/myapp
env: development Any environment
web: variables required
context: myapp by the app.
host: www.yourhost.com
static: public
rackup: config.ru
environment:
MAIL_HOST: mail.yourhost.com
REPLY_TO: you@yourhost.com
43
-
44.
deployment descriptors
• config/torquebox.yml
• internal descriptors have the
same structure as the
external ones in deploy/
• may be used to provide your
own reasonable defaults
44
-
45.
Web
make rack, not war
45
-
46.
jruby-rack
• All rack-based frameworks
supported: rails, sinatra, etc
• No packaging required: apps deploy
from where they sit on disk
• No redeploy necessary to see
changes when using rack reloading
or rails development mode
46
-
47.
database connectivity
Gemfile
gem "activerecord-jdbc-adapter"
gem "jdbc-postgres"
# gem "jdbc-sqlite3"
# gem "jdbc-mysql"
47
-
48.
rails template
• Adds TorqueBox rake tasks
• Adds the JDBC sqlite3 gems
• Adds TorqueBox session_store
• Adds Backgroundable module
48
-
49.
my first rails app
$ jruby -S rails new myapp -m
$TORQUEBOX_HOME/share/rails/template.rb
$ cd myapp
$ jruby -S bundle install
$ jruby -S rake torquebox:deploy
49
-
50.
Messaging
asynchronicity
50
-
51.
lots of options
RabbitMQ Resque cron BackgroundDRb
workling CronEdit
BackgroundJob
Kestral rufus-scheduler JobFu
Ap4r
Amazon SQS async_observer
Taskr Sparrow Background-Fu
beanstalkd DelayedJob RailsCron
Open4
Conveyor
WorkerQueue daemons rake
ActiveMQ
starling Spawn ActiveMessaging
51
-
52.
TorqueBox::Messaging
• JMS (Java Message Service)
is an API for messaging
• implementation
HornetQ is the JBoss JMS
52
-
53.
TorqueBox::Messaging
• Task
• Backgroundable
• MessageProcessor
• Queue
• Topic
53
-
54.
TorqueBox::Messaging
• No extra tables in your database
• No external system to manage
• Little to no config required at all
• No redeploy necessary in dev mode
• Efficient loading of rails environment
• Automatic load balancing and retries
• Works on Windows, if you care
54
-
55.
Tasks
app/tasks/email_task.rb
class EmailTask < TorqueBox::Messaging::Task
def welcome(payload)
person = payload[:person]
person ||= Person.find_by_id(payload[:id])
if person
# send the email
person.welcomed = true
person.save!
end
end
end
55
-
56.
Tasks
app/tasks/email_task.rb
class EmailTask < TorqueBox::Messaging::Task
def welcome(payload)
person = payload[:person]
person ||= Person.find_by_id(payload[:id])
if person
# send the email
person.welcomed = true
person.save!
end
end
end
56
-
57.
Tasks
app/tasks/email_task.rb
class EmailTask < TorqueBox::Messaging::Task
def welcome(payload)
person = payload[:person]
person ||= Person.find_by_id(payload[:id])
if person
# send the email
person.welcomed = true
person.save!
end
end
end
57
-
58.
Tasks
app/tasks/email_task.rb
class EmailTask < TorqueBox::Messaging::Task
def welcome(payload)
person = payload[:person]
person ||= Person.find_by_id(payload[:id])
if person
# send the email
person.welcomed = true
person.save!
end
end
end
58
-
59.
Tasks
app/tasks/email_task.rb
class EmailTask < TorqueBox::Messaging::Task
def welcome(payload)
person = payload[:person]
person ||= Person.find_by_id(payload[:id])
if person
# send the email
person.welcomed = true
person.save!
end
end
end
59
-
60.
Tasks
app/controllers/people_controller.rb
class PeopleController < ApplicationController
def create
@person = Person.new(params[:person])
respond_to do |format|
if @person.save
EmailTask.async(:welcome, :id => person.id)
# respond appropriately
end
end
end
end
60
-
61.
Tasks
app/controllers/people_controller.rb
class PeopleController < ApplicationController
def create
@person = Person.new(params[:person])
respond_to do |format|
if @person.save
EmailTask.async(:welcome, :id => person.id)
# respond appropriately
end
end
end
end
61
-
62.
Tasks
Call them from your
controllers, models, and
observers, or even other
tasks. Even in non-Rails
apps!
62
-
63.
Backgroundable
Inspired by DelayedJob’s
handle_asynchronously,
it’s trivial to create implicit
background Tasks.
63
-
64.
Backgroundable
lib/something.rb
include TorqueBox::Messaging
class Something
include Backgroundable
always_background :foo
def foo;; end
def bar;; end
end
...
@something.foo
@something.background.bar
64
-
65.
Backgroundable
lib/something.rb
include TorqueBox::Messaging
class Something
include Backgroundable
always_background :foo
def foo;; end
def bar;; end
end
...
@something.foo
@something.background.bar
65
-
66.
Backgroundable
lib/something.rb
include TorqueBox::Messaging
class Something
include Backgroundable
always_background :foo
def foo;; end
def bar;; end
end
...
@something.foo
@something.background.bar
66
-
67.
Backgroundable
lib/something.rb
include TorqueBox::Messaging
class Something
include Backgroundable
always_background :foo
def foo;; end
def bar;; end
end
...
@something.foo
@something.background.bar
67
-
68.
Backgroundable
app/models/slow_poke.rb
class SlowPoke < ActiveRecord::Base
def takes_forever;; end
def might_take_awhile;; end
end
SlowPoke.always_background :takes_forever
@slowpoke.takes_forever
@slowpoke.background.might_take_awhile
68
-
69.
Queues
Tasks are built on top of
Queues. Of course, you may
build your own “MOM” apps
by defining your own Queues,
Topics, and their message
Processors yourself.
69
-
70.
Queues
config/queues.yml
/queues/questions:
/queues/answers:
durable: false
70
-
71.
Processors
You can create a processor
class to receive messages
from a Topic or Queue
71
-
72.
Processors
config/messaging.yml
/topics/print: PrintHandler
/queues/popular:
- PopularHandler
- AdultObserver:
filter: "age >= 18"
concurrency: 5
/queues/students:
PrintHandler:
config:
color: true
72
-
73.
Processors
config/messaging.yml
/topics/print: PrintHandler
/queues/popular:
- PopularHandler
- AdultObserver:
filter: "age >= 18"
concurrency: 5
/queues/students:
PrintHandler:
config:
color: true
73
-
74.
Processors
config/messaging.yml
/topics/print: PrintHandler
/queues/popular:
- PopularHandler
- AdultObserver:
filter: "age >= 18"
concurrency: 5
/queues/students:
PrintHandler:
config:
color: true
74
-
75.
Processors
config/messaging.yml
/topics/print: PrintHandler
/queues/popular:
- PopularHandler
- AdultObserver:
filter: "age >= 18"
concurrency: 5
/queues/students:
PrintHandler:
config:
color: true
75
-
76.
Processors
app/models/print_handler.rb
include TorqueBox::Messaging
class PrintHandler < MessageProcessor
def on_message(body)
puts "Processing #{body} of #{message}"
end
def configure(opts)
@color = opts['color']
end
end
76
-
77.
Queues (again)
But how do you send a
message?
77
-
78.
Queues
example
include TorqueBox
req = Messaging::Queue.new '/queues/questions'
res = Messaging::Queue.new '/queues/answers'
Thread.new do
req.publish "What time is it?"
puts res.receive( :timeout => 1000 )
end
puts req.receive
res.publish Time.now
78
-
79.
Queues
example
include TorqueBox
req = Messaging::Queue.new '/queues/questions'
res = Messaging::Queue.new '/queues/answers'
Thread.new do
req.publish "What time is it?"
puts res.receive( :timeout => 1000 )
end
puts req.receive
res.publish Time.now
79
-
80.
Queues
example
include TorqueBox
req = Messaging::Queue.new '/queues/questions'
res = Messaging::Queue.new '/queues/answers'
Thread.new do
req.publish "What time is it?"
puts res.receive( :timeout => 1000 )
end
puts req.receive
res.publish Time.now
80
-
81.
Queues
example
include TorqueBox
req = Messaging::Queue.new '/queues/questions'
res = Messaging::Queue.new '/queues/answers'
Thread.new do
req.publish "What time is it?"
puts res.receive( :timeout => 1000 )
end
puts req.receive
res.publish Time.now
81
-
82.
Queues
“on the fly”
include TorqueBox
queue = Messaging::Queue.new '/queues/foo'
queue.create
...
queue.destroy
82
-
83.
Topics
• behavior is different, but interface is
the same.
• all subscribers of a topic see each
message, but only one subscriber
will see any message from a queue
• use topics.yml to define topics
• use TorqueBox::Messaging::Topic
83
-
84.
Scheduling
get regular later
84
-
85.
Jobs
app/jobs/newsletter_sender.rb
class NewsletterSender
def run()
subscriptions = Subscription.find(:all)
subscriptions.each do |e|
send_newsletter( e )
end
end
end
85
-
86.
Jobs
config/jobs.yml
monthly_newsletter:
description: first of month
job: NewsletterSender
cron: ‘0 0 0 1 * ?’
sandbox:
job: Sandbox
cron: ‘*/5 * * * * ?’
86
-
87.
Jobs
“Fire every half hour from 10am until 1pm
on the third Friday of each month”
0 */30 10-13 ? * FRI#3
Seconds Minutes Hours DOM Month DOW Year
1-7
1-12 1970-
1-31 SUN-
0-59 0-59 0-23 JAN- 2099
?LW SAT
DEC empty
?L#
87
-
88.
Jobs
• More portable. What is the first day
of the week on BSD again? What’s
cron on Windows?
• Self contained within the app. No
external systems to manage and
keep in sync.
• Full rails environment loaded and
available.
88
-
89.
Services
run along, lil’ daemon
89
-
90.
Services
Long-running, non-web
“daemons” that share the
runtime environment and
deployment lifecycle of
your app.
90
-
91.
Services
• Represented as a class with
optional initialize(Hash),
start() and stop() methods,
which should each return quickly.
• Typically will start a long-running
loop in a thread and respond to
external events.
• Configured via services.yml
91
-
92.
Services
config/services.yml
IrcBot:
server: freenode.net
channel: #torquebox
publish: /topics/irc
MyMudServer:
SomeOtherService:
92
-
93.
Services
app/{models,etc}/my_service.rb
class MyService
def initialize opts={}
name = opts[:publish]
@queue = Messaging::Queue.new(name)
end
def start
Thread.new { run }
end
def stop
@done = true
end
end
93
-
94.
Services
app/{models,etc}/my_service.rb
class MyService
def initialize opts={}
name = opts[:publish]
@queue = Messaging::Queue.new(name)
end
def start
Thread.new { run }
end
def stop
@done = true
end
end
94
-
95.
Services
app/{models,etc}/my_service.rb
class MyService
def initialize opts={}
name = opts[:publish]
@queue = Messaging::Queue.new(name)
end
def start
Thread.new { run }
end
def stop
@done = true
end
end
95
-
96.
Services
app/{models,etc}/my_service.rb
class MyService
def initialize opts={}
name = opts[:publish]
@queue = Messaging::Queue.new(name)
end
def start
Thread.new { run }
end
def stop
@done = true
end
end
96
-
97.
Services
app/{models,etc}/my_service.rb
class MyService
def run
until @done
@queue.publish(Time.now)
sleep(1)
end
end
end
97
-
98.
Services
app/{models,etc}/my_service.rb
class MyService
def run
until @done
@queue.publish(Time.now)
sleep(1)
end
end
end
98
-
99.
Services
app/services/evented_service.rb
class EventedService
def initialize opts={}
@opts = opts
end
def start
Thread.new { EventMachine.run {...} }
end
def stop
EventMachine.stop
end
end
99
-
100.
Clustering
less failure faster
100
-
101.
out-of-the-box
• JBoss provides
• session replication
• load-balanced messaging
• mod_cluster provides
• session affinity
• intelligent load-balanced web
• failover
101
-
102.
JBOSS_CONF
TorqueBox ships with two
JBoss server configurations:
default (not clustered) and all
(clustered). To enable
clustering...
$ export JBOSS_CONF=all
102
-
103.
deploy and run
$ export JBOSS_CONF=all
$ jruby -S rake torquebox:deploy
$ jruby -S rake torquebox:run
103
-
104.
mod_cluster
A reverse proxy implemented
as an Apache module with
JBoss awareness. Constantly
gathers load statistics and
deployment availability for
intelligent request distribution.
104
-
105.
mod_cluster
105
-
106.
mod_cluster
• Dynamic configuration
• Server-side load factor calculation
• Fine-grained web app lifecycle
• AJP (Apache JServ Protocol) is
optional. HTTP[S] is also supported.
106
-
107.
Future
coming soon
107
-
108.
Future
• Performance benchmarking and
optimization
• Infinispan for ActiveSupport::Cache
and NoSQL persistence
• JBoss SSO, HASingleton, logging
• CDI injection of Java components into
Ruby
• More JavaEE bridged to Ruby where
it makes sense, e.g. BPM, Drools,
Transactions, etc.
108
-
109.
Resources
• http://torquebox.org
• #torquebox on freenode
• https://github.com/torquebox
• http://twitter.com/torquebox
• http://projectodd.org
109
-
110.
Thanks!
questions?
110
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
easy JEE integration\nreplacement for JSF\n
\n
Can also apply ruby tools, e.g. Cucumber and RSpec, to java.\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
Hot deployment, clustering, caching, messaging, scheduling, transactions, lifecycle management, etc.\n
\n
\n
\n
\n
Ensure you have Java 6 installed\n
Ensure you have Java 6 installed\n
Ensure you have Java 6 installed\n
Ensure you have Java 6 installed\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
Not imposing JEE arbitrary packaging conventions on ruby just to enjoy the JEE services.\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
Or integrate with a legacy Java app through its queues.\n
\n
\n
destinations on the left, processors on the right!\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
Ruby messages being Base64-encoded over JMS String-type messages\n
\n
\n
\n
\n
Should reside beneath app/jobs\nOnly a run() method is required\n
\n
\n
The last day of the month\nThe last weekday of the month\nThe weekday closest to the 15th\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
Recent articles posted on torquebox.org detailing clustering config\n
reverse proxy / load balancer\n
\n
\n
\n
\n
As server nodes join and leave the cluster, the balancer (mod_cluster) is aware of these changes.\n
\n
\n
\n
\n
\n
\n
\n
\n
\n
*.bat equivalents\nwindows service config\n
\n
\n