Upload
mark-menard
View
863
Download
0
Embed Size (px)
DESCRIPTION
Insight from using JRuby in production for six years integrating with Java services, such as Spring.
Citation preview
JRubyInsights from Six Years in Production
Mark Menard Enable Labs
Enable Labs @mark_menard!2
How do you get Ruby into a Java shop?
How do you get Ruby into a legacy Java app?
How do you move past Java to Ruby?
Enable Labs @mark_menard!3
Who are you?
Enable Labs @mark_menard!4
You love Ruby!
Enable Labs @mark_menard!5
You work in a Java shop.
Enable Labs @mark_menard!6
You have a client who uses Java.
Enable Labs @mark_menard!7
You have to work on
something that uses Java.
Enable Labs @mark_menard!8
You have a project that could benefit from true concurrency.
Enable Labs @mark_menard!9
Why?
Enable Labs @mark_menard!10
So... what is JRuby anyway?
Enable Labs @mark_menard!11
What’s different?
But I use MRI!
Enable Labs @mark_menard!12
• A JVM is required.
• Start up is a little slower.
• Prefer pure Ruby gems, or gems that have JRuby versions.
• Try to avoid gems with C-extensions.
• No continuations.
• No fork()
• Regular expressions use 1.9 semantics even in 1.8 mode.
To Summarize
Enable Labs @mark_menard!13
What can JRuby do for you?
Enable Labs @mark_menard!14
Parallelism in JRuby
Enable Labs @mark_menard!15
my_thread = Thread.new do (1..100_000).each { |i| i * 2 } end !# Do some more work. !my_thread.join # Wait for my_thread to finish.
Enable Labs @mark_menard!16
require 'peach' require 'benchmark' !overall_results = Benchmark.measure do (1..100_000).peach(8) do |i| 1000.times do |n| a, b = 0, 1 a, b = b, a+b end end end puts overall_results
Enable Labs @mark_menard!17
$ rvm use 2.0 Using /Users/mark/.rvm/gems/ruby-2.0.0-p247 $ ruby peach_example.rb 41.250000 0.060000 41.310000 ( 41.302095) $ rvm use jruby Using /Users/mark/.rvm/gems/jruby-1.7.5 $ ruby peach_example.rb 9.960000 0.170000 10.130000 ( 1.437000)
Enable Labs @mark_menard!18
Using Java Libraries
Enable Labs @mark_menard!19
require 'java' require "#{File.expand_path(File.dirname(__FILE__))}/itextpdf-5.4.4.jar" !# Make Java classes top level constants. java_import com.itextpdf.text.Document Document.__persistent__ = true java_import com.itextpdf.text.pdf.PdfWriter java_import java.io.FileOutputStream java_import com.itextpdf.text.Paragraph document = Document.new pdfWriter = PdfWriter.get_instance(document, FileOutputStream.new("document.pdf")) document.open document.add(Paragraph.new("Hello JRuby")) document.close
Enable Labs @mark_menard!20
require 'itext' Itext::Document.create("document.pdf") do p "Hello from JRuby" end
Enable Labs @mark_menard!21
require 'java' require "#{File.expand_path(File.dirname(__FILE__))}/itextpdf-5.4.4.jar" !module Itext # Namespace the Java classes for convenience. java_import com.itextpdf.text.Document Document.__persistent__ = true java_import com.itextpdf.text.pdf.PdfWriter java_import java.io.FileOutputStream java_import com.itextpdf.text.Paragraph end
Enable Labs @mark_menard!22
module Itext # Re-open the *Java* Document class. class Document def self.create (filename, &block) document = self.new pdf_writer = PdfWriter.get_instance(document, FileOutputStream.new(filename)) document.open document.instance_eval(&block) document.close end ! def paragraph (content) add(Paragraph.new(content)) end alias_method :p, :paragraph end end
Enable Labs @mark_menard!23
require 'itext' Itext::Document.create("document.pdf") do p "Hello from JRuby" end
Enable Labs @mark_menard!24
require 'java' !frame = javax.swing.JFrame.new frame.set_default_close_operation javax.swing.JFrame::EXIT_ON_CLOSE frame.set_size(300, 200) frame.get_content_pane.add javax.swing.JLabel.new('Hello world!') frame.set_visible true
Enable Labs @mark_menard!25
Case Studies
Enable Labs @mark_menard!26
Case Study 1 !
What the Client Wanted !
1Q2008
• New functionality that was predominantly orthogonal to their existing app.
• Single Signon • Faster Development Times • Some integration at the data
level.
Enable Labs @mark_menard!27
Case Study 1 !
The Technical Environment
• Struts 2 • Groovy 1.0 • Jetty Running Un-war'ed • Spring Dependency Injection -
XML Hell • Struts 2 - More XML Hell !
• Mostly Continuous Deployment
Enable Labs @mark_menard!28
Case Study 1 !
What We Used
• JRuby 1.0 • Rails 2.1 • ERB
Enable Labs @mark_menard!29
Case Study 1 !
What Made it Work
Java Integration
Enable Labs @mark_menard!30
Case Study 1 !
The Challenges
• Integrating the Signin Process • Accessing the Spring Context • Reusing Existing Permission
System • Deployment • Gem Management
Enable Labs @mark_menard!31
Case Study 1 !
Integrating the Signin Process
• Initiate all signins on the Rails side of the application.
• On success setup the HTTP session for both the Java and Rails sides of the app.
• Also handle signout in Rails.
Enable Labs @mark_menard!32
Case Study 1 !
Accessing the Spring Context
• Create a Java object that holds a static reference to the Spring context, the SpringApplicationContextFinder.
• The finder is initialized at startup with the reference to the context.
• Make a Ruby DSL to access Spring.
Enable Labs @mark_menard!33
public class SpringApplicationContextFinder implements ApplicationContextAware { ! private static ApplicationContext CONTEXT; ! /** * This method is called from within the ApplicationContext once it is * done starting up, it will stick a reference to itself into this bean. * @param context a reference to the ApplicationContext. */ public void setApplicationContext(ApplicationContext context) throws BeansException { CONTEXT = context; } ! /** * Return a reference to the Spring application context. * @return SpringApplicationContext */ public static Object getContext () { return CONTEXT; } }
Enable Labs @mark_menard!34
module SpringSupport def get_spring_context @context ||= Java::lib.SpringApplicationContextFinder.getContext() end end
Enable Labs @mark_menard!35
class SomeObject include SpringSupport ! spring_dependency :some_spring_service ! def do_something (arg) ! #@some_spring_service <---- This is a Java object. ! some_spring_service.do_something(arg) end end !result = SomeObject.new.do_something("abc")
Enable Labs @mark_menard!36
Case Study 1 !
Reusing Existing Permission System
• Permission system written in Java/Groovy.
• Still needed to be accessible from Java/Groovy.
• Did not want to maintain two versions of the permission system.
Enable Labs @mark_menard!37
Case Study 1 !
Reusing Existing Permission System !
Solution
• Use the Spring implementation of permissions.
• Check permissions in a before_filter.
• Use the SpringSupport to get access to the security manager.
Enable Labs @mark_menard!38
class CheckSecurityAccessService < Struct.new(:url, :user) include SpringSupport spring_dependency :security_manager ! def execute security_manager.check_security_access(build_vr_context) end alias_method :succeeded?, :execute ! private ! def build_vr_context VRContext.new(HashMap.new('url' => url, 'user' => user)) end ! end
Enable Labs @mark_menard!39
Case Study 1 !
Deployment and Gem Management
• App used Jetty un-war’ed. • Warbler didn’t apply. • Layout Rails app in /WEB-INF • Used GoldSpike servlet to front
Rails. (We have since updated to jruby-rack and a Servlet filter.)
• Vendor EVERYTHING and check it into git.
Enable Labs @mark_menard!40
Case Study 1 !
Success
• Completed work in about 3 months.
• Much better test coverage. • This module is still orthogonal to
the main app today. • Code has been very stable. • Code has been ported through
multiple versions of Rails. • Almost all new functionality is
done in Rails since 1Q2008.
Enable Labs @mark_menard!41
Case Study 1 !
Why did it succeed?
• Minimal integration with existing application.
• Highly compartmentalized. • Focused feature set with stable
requirements. • Java integration worked.
Enable Labs @mark_menard!42
Case Study 2
And Steve said, “let there be iPhone.”
Enable Labs @mark_menard!43
Case Study 2 !
iPhone
• Client wants about 10 screens available in the browser on the iPhone.
• He has a trip in two weeks and wants it working before he leaves.
Enable Labs @mark_menard!44
Case Study 2 !
iPhone
• Working screens inside of a week. • Ready for his trip in two weeks. • Went on to be used by the field sales staff for several
years. • Total cost far below the client’s expectation.
Rails to the Rescue !
A rip roaring success
Enable Labs @mark_menard!45
Case Study 3 !
Porting to Ruby First Attempt !
A Study in Over Enthusiasm
This JRuby is Awesome! Let’s Port the App!
Enable Labs @mark_menard!46
Case Study 3 !
Porting to Ruby First Attempt
• 388 Views • 272 Struts 2 Actions • 30 Spring Service Beans • 81 Data Access Objects • 151 Hibernate/JPA Entities
(Models) • Not Enough Tests • Primary implementation
language is Groovy
Let’s talk about the brownfield.
Enable Labs @mark_menard!47
Case Study 3
Current Architecture
Enable Labs @mark_menard!48
Case Study 3 !
Porting to Ruby First Attempt
• Ruby classes can implement Java interfaces.
Enable Labs @mark_menard!49
public interface Person { public String getName (); public void setName (String name); }
class Person include Java::Person !
attr_accessor :name end
Enable Labs @mark_menard!50
Case Study 3 !
Porting to Ruby First Attempt
• Ruby classes can implement Java interfaces.
• Plug a Ruby/Rails environment into Spring to manufacture Ruby “beans”.
Enable Labs @mark_menard!51
def getInventoryManager () { log.debug "[ RailsFactory.groovy ] : Instantiating Integration::InventoryManager" eval "Integration::InventoryManager.new\n" }
Enable Labs @mark_menard!52
Case Study 3 !
Porting to Ruby First Attempt
• Ruby classes can implement Java interfaces.
• Plug a Ruby/Rails environment into Spring to manufacture Ruby “beans”.
• On a case-by-case basis port Java/Groovy Spring service beans to JRuby.
• This was a bottom up port.
Enable Labs @mark_menard!53
Case Study 3 !
Porting to Ruby First Attempt
• No need to mess with the user experience. The views and controllers won’t change.
• Can do it incrementally. • Allows Java, Groovy and JRuby
objects to just inter-play. • Should be transparent.
Rationale
Enable Labs @mark_menard!54
Case Study 3 !Porting to Ruby First Attempt
• Technical success, business failure. • Did not take full advantage of our
Ruby tools. • There was no driving business value
in doing it.
Outcome
Enable Labs @mark_menard!55
Case Study 4 !
Porting to Rails Part 2
Ah.... sweet incremental success... ...mostly.
Enable Labs @mark_menard!56
• Do all new work in JRuby. • Find silos of existing functionality. • Wait for significant changes in
requirements for the silo. • Port one silo at a time. • Port the whole silo to JRuby. • Write lots of tests. • Find improvements to UI/UX that can be
rolled in for justification. • Use SOA for non-user facing services.
Case Study 4 !
Porting to Rails Part 2
Enable Labs @mark_menard!57
Case Study 4 !
Porting to Rails Part 2
The God Object in the Closet
Enable Labs @mark_menard!58
Case Study 4 Porting to Rails Part 2The
God
Obj
ect i
n th
e Cl
oset The Strategy
!
Make the God object a web service. Implement it in Rails.
Translate the existing Groovy code. Port test suite to RSpec.
Refactor, refactor, refactor, refactor, refactor.... Review the spec with the client extensively.
Enable Labs @mark_menard!59
JRuby works today.
Java integration lets you play where MRI just can’t go.
It’s just Ruby, with Java JVM super powers!
Enable Labs @mark_menard!60
Photo credits http://www.flickr.com/photos/usfwsnortheast/5655240564/ http://www.flickr.com/photos/falcon1961/3304306800/ !
Code Color Scheme Solarized Light !
Syntax Highlighting Tool http://www.andre-simon.de/doku/highlight/en/highlight.html