jruby 6 years in production

Post on 19-Jan-2015

863 Views

Category:

Technology

0 Downloads

Preview:

Click to see full reader

DESCRIPTION

Insight from using JRuby in production for six years integrating with Java services, such as Spring.

TRANSCRIPT

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

http://www.enablelabs.com/

info@enablelabs.com

866-895-8189

Enable Labs@mark_menard

top related