The Enterprise Strikes Back

1,672 views

Published on

Explains how to make use of ruby in java-based work environments. There are some hints at .NET equivalents along the way.

This is part 3 of a trilogy of Star Wars-themed ruby talks given at Protegra's SDEC 2011 in Winnipeg, Canada.

Published in: Technology, Art & Photos
0 Comments
0 Likes
Statistics
Notes
  • Be the first to comment

  • Be the first to like this

No Downloads
Views
Total views
1,672
On SlideShare
0
From Embeds
0
Number of Embeds
226
Actions
Shares
0
Downloads
10
Comments
0
Likes
0
Embeds 0
No embeds

No notes for slide

The Enterprise Strikes Back

  1. 1. The Enterprise Strikes BackThe Ruby Trilogy: Part III
  2. 2. The Enterprise Strikes BackThe Ruby Trilogy: Part III
  3. 3. Burke Libbey Stefan Penner@burkelibbey @stefanpenner
  4. 4. Burke Libbey Stefan Penner@burkelibbey @stefanpenner
  5. 5. Burke Libbey Stefan Penner@burkelibbey @stefanpenner
  6. 6. Overview• Ruby on the JVM (20m)• Ruby as Glue (20m)• Testing Java with RSpec (10m)• Cloud City (15m)
  7. 7. Ruby Java+ Productive + Not Scary- Scary - Less productive
  8. 8. Ruby in Java + Not Scary + Productive
  9. 9. http://jruby.org
  10. 10. • Full ruby implementation on the JVM• Access to both ruby and Java libraries• Can be deployed to existing Java infrastructure
  11. 11. The many flavours of ruby • MRI (and YARV) • JRuby • IronRuby • MacRuby • Rubinius • ...and several more...
  12. 12. java.lang.System.out.println("Hello World")
  13. 13. The Magic Sauce require java (contains up to 30% midichlorians)
  14. 14. Calling Java from rubyf = javax.swing.JFrame.newf.getContentPane.add(javax.swing.JLabel.new("Hello World"))close_operation = javax.swing.JFrame::EXIT_ON_CLOSEf.setDefaultCloseOperation(close_operation)f.packf.setVisible(true)
  15. 15. Impedance Mismatchf = javax.swing.JFrame.newf.getContentPane.add(javax.swing.JLabel.new("Hello World"))close_operation = javax.swing.JFrame::EXIT_ON_CLOSEf.setDefaultCloseOperation(close_operation)f.packf.setVisible(true) Let’s break this down.
  16. 16. Impedance Mismatch “Getters” and “Setters” are non-idiomatic in ruby.f = javax.swing.JFrame.newf.getContentPane.add(javax.swing.JLabel.new("Hello World")) getContentPaneclose_operation = javax.swing.JFrame::EXIT_ON_CLOSEf.setDefaultCloseOperation(close_operation)f.packf.setVisible(true) snake_case is generally preferred to CamelCase
  17. 17. Impedance Mismatch JavaBean properties can be accessed like this:f = javax.swing.JFrame.newf.content_pane.add(javax.swing.JLabel.new("Hello World"))close_operation = javax.swing.JFrame::EXIT_ON_CLOSEf.setDefaultCloseOperation(close_operation)f.packf.setVisible(true) “get” disappears, CamelCase changes to snake_case
  18. 18. Impedance Mismatch “set” is replaced by “=”f = javax.swing.JFrame.newf.content_pane.add(javax.swing.JLabel.new("Hello World"))close_operation = javax.swing.JFrame::EXIT_ON_CLOSEf.default_close_operation = close_operationf.packf.visible = true “setDefaultCloseOperation(x)” becomes “default_close_operation = x”
  19. 19. Ugliness Aboundsf = javax.swing. javax.swing.JFrame.newf.content_pane.add(javax.swing.JLabel.new("Hello World")) javax.swing.close_operation = javax.swing. javax.swing.JFrame::EXIT_ON_CLOSEf.default_close_operation = close_operationf.packf.visible = true Namespaces everywhere!
  20. 20. Ugliness Aboundsjava_import javax.swing.JFramejava_import javax.swing.JLabelf = JFrame.newf.content_pane.add(JLabel.new("Hello World")) JLabel.newclose_operation = JFrame::EXIT_ON_CLOSEf.default_close_operation = close_operationf.packf.visible = true java_import adds classes to a ruby context
  21. 21. 100% Rubified™java_import javax.swing.JFramejava_import javax.swing.JLabelJFrame.new.tap do |f| f.content_pane.add JLabel.new("Hello World") f.default_close_operation = JFrame::EXIT_ON_CLOSE f.pack f.visible = trueend
  22. 22. Method Rubification™• In general, CamelCase Java methods can optionally be transliterated to snake case.
  23. 23. Uniform Access Principle All services offered by a module should be available through a uniform notation, which does not betray whether they are implemented through storage or through computation. - Bertrand Meyer
  24. 24. Two Rules• Everythings an Object• Objects expose Methods and Only Methods
  25. 25. Method Rubification™ Java System.currentTimeMillis() Ruby System.current_time_millis
  26. 26. Seriously though, not actually trademarked.Method Rubification™ Java person.getName() Ruby person.name
  27. 27. Someone should get on that.Method Rubification™ Java person.setAge(42) Ruby person.age = 42
  28. 28. Method Rubification™FALSE! TRUE! Java person.isJedi() Ruby person.jedi?
  29. 29. Is ruby an acceptable Java? (http://bit.ly/pfNluA)• Most Java code can literally be written as ruby with few high-level changes.• A more relaxed type system and syntax often has productivity benefits.
  30. 30. Exhibit A // interfaces = HashMap{ label => NetworkInterface }Java Collection c = interfaces.values(); Iterator itr = c.iterator(); while(itr.hasNext()) { NetworkInterface interface = itr.next(); if (interface.isLoopback()) { return interface.getDisplayName(); } } VS. # interfaces = {label => NetworkInterface}Ruby interfaces.values.find(&:loopback?).display_name
  31. 31. Exhibit A // interfaces = HashMap{ label => NetworkInterface }Java Collection c = interfaces.values(); Iterator itr = c.iterator(); while(itr.hasNext()) { NetworkInterface interface = itr.next(); if (interface.isLoopback()) { return interface.getDisplayName(); } } VS. # interfaces = {label => NetworkInterface}Ruby interfaces.values.find(&:loopback?).display_name 1 2 345 67 Only 7 characters of syntactic support!
  32. 32. “Are you suggesting I write all my Java in ruby?!” • Not really... • JRuby is much slower than Java (doesn’t matter as often as you’d think) • Ruby’s added expressiveness makes it easier to shoot yourself in the foot. (or, in fact, to lazily clone an infinite number of your feet every planck length between your gun and your target)
  33. 33. “Are you suggesting I write all my Java in ruby?!” • ...but maybe sometimes. • One possibility: • Encode high-level logic in expressive and concise ruby code • Supporting code in fast, safe Java • Mix and match as appropriate
  34. 34. Ruby as Glue• Ruby is great for: • wiring existing codebases together • other miscellaneous tasks
  35. 35. Perl The original “Swiss Army Knife”
  36. 36. Perl Pretty much mean the same thing. • “glue” • “duct tape” • “swiss army knife”
  37. 37. Perl Ruby
  38. 38. Subcategories of “Glue” • Wiring stuff together • Sysadmin tasks
  39. 39. Wiring stuff together• There’s a lot we could cover here, but:
  40. 40. Nokogiri• An extremely user-friendly XML library• fast! (wraps libxml2)
  41. 41. Nokogirirequire open-urirequire nokogirihtml = open("http://slashdot.org").readdoc = Nokogiri::XML(html)(doc/".story a").each do |link| puts "#{link.text} (#{link.attr(href)})"end# Britains Broadband Censors: a Bunch of Students (//yro.slashdot...# ...
  42. 42. Don’t do this.(Nokogiri::XML(open("http://slashdot.org").read)/".story a").each{|a|puts"#{a.text} (#{a.attr("href")})"}
  43. 43. Sysadmin with Ruby• Nice system APIs (quite similar to perl’s)• System provisioning libraries/DSLs
  44. 44. Provisioning systems • Puppet • Chef • Vagrant
  45. 45. Puppet• Describe system, and puppet sets it up• Nontrivial, but the general idea is: • I want mysql • I want nginx < 0.8 • I want this cron job: “....” • Go. http://puppetlabs.com/
  46. 46. Puppetclass postgres-server { package { postgresql-server: ensure => latest } group { postgres: gid => 26 } user { postgres: comment => "PostgreSQL Server", uid => 26, gid => 26, home => "/var/lib/pgsql", shell => "/bin/bash" } service { postgresql: running => true, pattern => "/usr/bin/postmaster", require => package["postgresql-server"] }}
  47. 47. Puppet• Fantastic for defining a reproducible production environment
  48. 48. Chef• Same idea as puppet• Somewhat less powerful• Slightly more approachable to most ruby developers http://www.opscode.com/chef/
  49. 49. Chefpackage "sudo" do action :upgradeendtemplate "/etc/sudoers" do source "sudoers.erb" mode 0440 owner "root" group "root" variables( :sudoers_groups => node[authorization][sudo][groups], :sudoers_users => node[authorization][sudo][users], :passwordless => node[authorization][sudo][passwordless] )end
  50. 50. Puppet ChefConfiguration Language custom ruby Targeted at production production “Powerfulness” Lots Mostly lots Verdict Good Different Written in ruby ruby
  51. 51. Vagrant• Uses puppet and/or chef• End product is a virtual machine for development use• Consistent system for all developers, per project
  52. 52. Vagrant• If you use puppet or chef, vagrant lets you test production locally
  53. 53. Suggestions• As a non-ruby-dev: • Use puppet • Consider vagrant
  54. 54. Suggestions• As a ruby developer: • Consider chef and puppet, use whichever suits your taste and needs • Consider vagrant
  55. 55. Databases
  56. 56. In the Real World,
  57. 57. In the Real World, We have Data
  58. 58. In the Real World, We have DataWhich lives in Databases
  59. 59. Databases Are Always simple
  60. 60. All our data is ALWAYSin the same DBMS
  61. 61. Reality Check
  62. 62. Luckily We haveODBC + JDBC
  63. 63. Luckily We haveODBC + JDBC + Ruby
  64. 64. Ruby Gives you Options• Active Record• Sequel• DataMapper• more
  65. 65. Active Record Design pattern coined by Martin Fowler in “Patterns of enterprise application architecture”. Also, Ruby on Rails’s default ORM.• Lots of Power• Lots of Opinion• Might fight with you (for non-standard uses)https://github.com/rails/rails/tree/master/activerecord
  66. 66. Active Record Syntax (Raw)require active_recordActiveRecord::Base.establish_connection({ :adapter => mysql, :database => test_database, :username => tester, :password => test22})connection = ActiveRecord::Base.connectionconnection.tables> [users, products, ducks, oranges]users = []connection.execute(SELECT * FROM users).each_hash do |user| users << userendusers> .... array of users, each user as a hash.users.first> { :id => 1, :username => stefan, :password => is_super_secure }
  67. 67. Active Record Syntax (ORM)require active_recordActiveRecord::Base.establish_connection({ :adapter => mysql, :database => test_database, :username => tester, :password => test22})# class <camel_case_singular_table_name> < ActiveRecord::Baseclass User < ActiveRecord::Base # if the tables name does not fit convention, it can be manually overridden. # table_name :users_tableendUser.first> #<User id: 2, :name => stefan, :password => is_super_secure >User.find(2)> #<User id: 2, :name => stefan, :password => is_super_secure >User.where(:name => stefan)> #<User id: 2, :name => stefan, :password => is_super_secure >
  68. 68. Active Record Syntax (AREL)User.first> #<User id: 2, :name => stefan, :password => is_super_secure >User.find(2)> #<User id: 2, :name => stefan, :password => is_super_secure >User.where(:name => stefan)> #<User id: 2, :name => stefan, :password => is_super_secure >User.where(:name => stefan).order(id ASC)> #<User id: 2, :name => stefan, :password => is_super_secure >
  69. 69. SequelElegant Full featured database toolkit for ruby.• Supports a ton of DBMS’s• DSL• ORM http://sequel.rubyforge.org/ Supports - ADO, Amalgalite, DataObjects, DB2, DBI, DO, Firebird, ibmdb, Informix, JDBC, MySQL, Mysql2, ODBC, OpenBase, Oracle, PostgreSQL, SQLite3, Swift, and tinytds
  70. 70. require "sequel" Sequel Example# connect to an in-memory databaseDB = Sequel.sqlite# create an items tableDB.create_table :items do primary_key :id String :name Float :priceend# create a dataset from the items tableitems = DB[:items]# populate the tableitems.insert(:name => abc, :price => rand * 100)items.insert(:name => def, :price => rand * 100)items.insert(:name => ghi, :price => rand * 100)# print out the number of recordsputs "Item count: #{items.count}"# print out the average priceputs "The average price is: #{items.avg(:price)}"
  71. 71. Sequel ORMrequire "sequel"# connect to an in-memory databaseDB = Sequel.sqlite# create an items tableDB.create_table :items do primary_key :id String :name Float :priceend# create a dataset from the items tableclass Item < Sequel::Model(:items) # associations # validationsend# populate the tableItem.create(:name => abc, :price => rand * 100)Item.create(:name => def, :price => rand * 100)Item.create(:name => ghi, :price => rand * 100)# print out the number of recordsputs "Item count: #{Item.count}"# print out the average priceputs "The average price is: #{Item.avg(:price)}"
  72. 72. Sequel ORM + jRubyready out of the box
  73. 73. Sequel ORM +IRONRuby apparently works?
  74. 74. Case Study (Access)• Access (top 2 solutions) • Solution 1 (Cross Platform) • jRuby • JDBC • HXTT’s driver (www.hxtt.com) • Sequel • Solution 2 (Windows Only) • Ruby • ADO via WIN32OLE • Sequel
  75. 75. Case Study (Access)
  76. 76. Case Study (Access) 1. Download jRuby http://jruby.org/
  77. 77. Case Study (Access) 1. Download jRuby http://jruby.org/ 2. Install Sequel gem install Sequel
  78. 78. Case Study (Access) 1. Download jRuby http://jruby.org/ 2. Install Sequel gem install Sequel 3. Download JDBC Access Driver http://www.hxtt.com/access.html
  79. 79. Case Study (Access)require rubygemsrequire sequelrequire sequel/adapters/jdbcrequire sequel/jdbc_hxtt_adapterrequire ../Support/Access_JDBC40.jarroot = File.expand_path "../../", __FILE__path_to_db = root + "/Support/AccessThemeDemo.mdb"DB = Sequel.connect("jdbc:access:////#{path_to_db}")puts DB.tablesclass Notes < Sequel::Model(:Notes)endclass ThemeImages < Sequel::Model(:tbl_ThemeImages)endclass Users < Sequel::Model(:tbl_Users)end
  80. 80. ba-da-bing
  81. 81. But our new appand our old database have differentsystem requirements.
  82. 82. We Want Isolation
  83. 83. _ -NeedWe Want Isolation
  84. 84. Power by.. Sinatra Web DSL get /pictures do [picture1,picture2,picture2] end post /pictures {} put /pictures/:id {} delete /pictures/:id {} used at linkedInhttp://engineering.linkedin.com/44/linkedin-app-end-end-jruby-frontier-and-voldemort
  85. 85. sooo... big deal?
  86. 86. RestServer• any jdbc database into a restful json service• https://github.com/stefanpenner/restserver
  87. 87. Portability/Bundling• Warblerhttp://github.com/nicksieger/warbler• single jar/war file• compiled (if needed)
  88. 88. it’s an app!
  89. 89. Putting it all together (p1) ScreenShotr download:https://github.com/stefanpenner/screenshotr/zipball/master git@github.com:stefanpenner/screenshotr
  90. 90. Sorry, contrived example!
  91. 91. anyways...
  92. 92. Java Gives Us • PortabilityRuby Gives Us • Happiness
  93. 93. ScreenShotrNormally Annoying re: Portability• screen capture• GUI but thx jRuby
  94. 94. Lets Shoot some Screensrequire javajava_import java.awt.Robotjava_import java.awt.Toolkitjava_import java.awt.Rectanglejava_import java.awt.Imagejava_import java.awt.image.BufferedImagejava_import javax.imageio.ImageIOscreenRect = Rectangle.new(Toolkit.default_toolkit.getScreenSize())capture = Robot.new.createScreenCapture(screenRect)ImageIO.write(capture,jpg,java.io.File.new(some_tmp_file.jpg))
  95. 95. Hold UP
  96. 96. Hold UPjava_import ?
  97. 97. Hold UP java_import ?why not require?
  98. 98. Hold UP java_import ?why not require? requiring classes just makes them discoverable later.
  99. 99. Hold UP java_import ?why not require? requiring classes just makes them discoverable later.why not import?
  100. 100. Hold UP java_import ?why not require? requiring classes just makes them discoverable later.why not import? Rake defines import
  101. 101. Hold UP java_import ?why not require? requiring classes just makes them discoverable later.why not import? Rake defines import weird...
  102. 102. And fill some clipboardsrequire javajava_import java.awt.Toolkitjava_import javax.imageio.ImageIOss = StringSelection.new(public_url) Toolkit.getDefaultToolkit().getSystemClipboard().setContents(ss, nil)
  103. 103. upload to servergem install rest-clientrequire rubygemsrequire rest-clientRestClient.post(http://../resource, :file => File.new(path/to/file))
  104. 104. upload to servergem install rest-clientrequire rubygemsrequire rest-clientRestClient.post(http://../resource, :file => File.new(path/to/file))
  105. 105. <Insert Segue Here>
  106. 106. ...and Vice Versa• We’ve done a lot of calling Java from ruby.• The reverse is possible as well.
  107. 107. ...and Vice Versaimport org.jruby.embed.InvokeFailedException;import org.jruby.embed.ScriptingContainer;// ...ScriptingContainer container = new ScriptingContainer();container.runScriptlet("puts "Thats no Moon""); http://en.wikipedia.org/wiki/Jruby
  108. 108. Testing Java with Ruby“i find your lack of tests disturbing.”
  109. 109. Testing Java with Ruby• JtestR is wonderful • Includes most of ruby’s leading testing libraries • supports ant and maven • easy to add to a project • takes advantage of jruby/java bridge
  110. 110. JtestR Installation• Download jarfile from jtestr.codehaus.org• Add to classpath• Add a task to maven/ant• Create tests in ./test or ./spec• Run task
  111. 111. Example!import java.util.HashMapdescribe "An empty", HashMap do before :each do @hash_map = HashMap.new end it "accepts new entries" do @hash_map.put "foo", "bar" @hash_map.get("foo").should == "bar" end it "returns a keyset iterator that throws an exception on next" do proc do @hash_map.key_set.iterator.next end.should raise_error(java.util.NoSuchElementException) endend
  112. 112. “i saw a cityin the clouds”
  113. 113. MYTH: Ruby can’t scale.
  114. 114. FACT: Ruby scales like a BOSS
  115. 115. FACT: Ruby scales like a BOSS (because it has to)
  116. 116. Fog• “The Ruby Cloud Services Library”• Lets you upload to S3, provision instances in EC2, set DNS records in DNSimple...• ...and much more.
  117. 117. Fog“• Chewbacca-approved. whargarblbgarblgr- ” garblgragh
  118. 118. Fog“• Chewbacca-approved. (chewbaccaproved?) whargarblbgarblgr- garblgragh ”
  119. 119. 2.5 imperial tons of providers
  120. 120. ( )eh.2.5 imperial tons of H providers
  121. 121. Sinatra Example#config.rurequire ./lib/screen_shotr/serverrun ScreenShotr::Server.new
  122. 122. require sinatrarequire fogrequire digest/sha1
  123. 123. module ScreenShotr class Server < Sinatra::Base def storage #@storage ||= Fog::Storage.new({ # :provider => AWS, # :aws_access_key_id => ACCESS_KEY_ID, # :aws_secret_access_key => SECRET_ACCESS_KEY}) @storage ||= Fog::Storage.new({ :local_root => ~/fog, :provider => Local }) def directory storage.directories.find("data").first or storage.directories.create(:key => data ) end# snip ....
  124. 124. get / do "hello, world!"end
  125. 125. post /picture/create do file = params[:file] data = file[:tempfile] #super secure filename filename = file[:filename] key = Digest::SHA1.hexdigest("super random seed"+Time.now.to_s) key << .jpg file = directory.files.create( :body => data.read, :key => key ) file.public_url or "http://0.0.0.0:9292/picture/#{key}"end
  126. 126. get /picture/:key do file = directory.files.get(params[:key]) send_file file.send(:path)end
  127. 127. rackup
  128. 128. and...
  129. 129. Fog Storage:Kind of cool.
  130. 130. Fog Compute: Wicked cool.
  131. 131. HerokuClassic Stack (run rack/rails)Cedar Stack (run “anything”)
  132. 132. HerokuClassic Stack (run rack/rails)$ heroku createCreated sushi.herokuapp.com | git@heroku.com:sushi.git$ git push heroku master-----> Heroku receiving push-----> Rails app detected-----> Compiled slug size is 8.0MB-----> Launching... done, v1http://sushi.herokuapp.com deployed to Heroku
  133. 133. HerokuCedar Stack (run “anything”)$ cat Procfileweb: bundle exec rails server -p $PORTworker: bundle exec rake resque:work QUEUE=*urgentworker: bundle exec rake resque:work QUEUE=urgentclock: bundle exec clockwork clock.rb$ heroku scale web=4 worker=2 urgentworker=1 clock=1Scaling processes... done“Officially Everything”: Ruby, Node.js,Clojure, Java, Python, and Scala
  134. 134. “help me ruby...you’re my only hope!”
  135. 135. “good... the force is strong with you. Thanks!a powerful rubyist you will become.”

×