76
From 1 To 30 How To Disassemble One Monster App Into An Ecosystem Of 30 Jonathan Palley, CTO/COO Guo Lei, Chief Architect © 2010 Idapted, Ltd.

RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

  • Upload
    jpalley

  • View
    3.846

  • Download
    2

Embed Size (px)

DESCRIPTION

As your business grows bigger, you just can’t stop adding new models/controllers to your original rails application – resulting in a messy, unmaintainable and difficult to deploy monolithic application. Its time to refactor. This talk will share our experience, results and best practices in splitting a single rails “application-system” into 30 independently maintainable yet interconnected applications. After two and a half years of development (starting in pre-Rails 1.0 days!), our live-trainer English learning system now supported multiple roles (learner/trainer/trainer supervisor/sales/materials creation/support/etc) and an exhaustive list of features to support our complex business processes. We set ourselves a year-long goal of splitting this monolithic system into small cooperating applications that could be developed independently by individual developers. At the same time, we could not lose the usability cohesiveness and data-interdependence that defined the power of our system. Through numerous iterations, many mistakes and a bit of pure-luck we developed an optimized process for the refactor and best practices for making 30 independent rails apps behave as one. The results: lower development time, greater stability and scalability and much higher developer happiness. We’ll talk about specific code, measurements, pitfalls, plugins, process and best practices to answer questions such as: How to know where to split single applications into many. How to measure the result. How the applications should interact with each other. How to reduce administration and DRY configuration applications. How to share data among applications. How to DRY for common logic. How to make a consistent user experience. How to interact with non-Ruby technology; in our case Erlang, FreeSWITCH (VoIP) and Flex

Citation preview

Page 1: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

From 1 To 30How To Disassemble

One Monster App Into An Ecosystem Of 30

Jonathan Palley, CTO/COO

Guo Lei, Chief Architect

© 2010 Idapted, Ltd.

Page 2: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

An

Experience

Page 3: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

A Tale of Two Buildings

Page 4: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

2009 ShanghaiLotus Riverside Community

1909 BeijingForbidden City

Page 5: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

1

30

Page 6: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

What is one?

Page 7: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

The entire web application/system/platform

runs as one single rails application

(We are talking about really large systems. Multiple different types of clients/functions)

Page 8: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Problems

Hard to test/extend/scale

Confused new staff

Page 9: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

What is 30?

Page 10: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

A ecosystem of applications

Page 11: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Independent

Page 12: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Linked and Seamless

Page 13: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Basic features of each app• Separate database• Runs independently (complete story) • Lightweight (single developer)• Tight internal cohesion and loose external coupling

Advantages• Independent Development Cycle• Developer autonomy• Technology (im)maturity safety

APPEAL TO DEVELOPER LAZINESS

Page 14: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

What’s the mystery of the forbidden city?

Page 15: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Consistent UI

• Shared CSS/JS/Styleguide• Common Helpers in Shared Gem• Safely try new things

Page 16: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

All applications use the same base CSS/JS

Keep all the application the same style

<%= idp_include_js_css %># =><script src ="/assets/javascripts/frame.js" type="text/javascript"></script><link href="/assets/stylesheets/frame.css" media="screen" rel="stylesheet"

type="text/css" />

interface

Page 17: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

CSS Frameworkinterface

Page 18: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Abstract Common Helpers to Gem

Search function for models

interface

Page 19: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Common Helpers: Combo search (cont)

View:<%= search_form_for(HistoryRecord, :interaction_id, :released,[:rating, {:collection=>assess_ratings}],[:mark_spot_num,{:range=>true}], [:created_at, {:ampm=>true}]) %>

Controller:@history_records = HistoryRecord.combo_search(params)

interface

Page 20: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Common Helpers: List table

well formattedwith pagination

sortablecustomizable

interface

Page 21: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Common Helpers: List table (cont)<%= idp_table_for(@history_records,:sortable=>true,:customize => "history_records") do |item, col| col.add :id, link_to(item.id, admin_history_record_path(item)),:order=>:id col.build :duration, :waiting_time, :review_time col.add :scenario, item.scenario_title, :order => :scenario_title col.add :mark_spot_num end%>

interface

Page 22: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Development Lifecycle

interface

1. Implement new View code/plugin in a second application

2. Abstract into plugin using existing “idp” helpers

3. Put it into main view gem

Page 23: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

interface data

Page 24: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

How do applications share data?(remember: each app has its own data)

data

-“Read Only” Database Connections- Services- AJAX Loaded View Segments

Page 25: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Business example

user

course

purchase

learning process

data

Page 26: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Purchase App

Requirement: List course packages for user to select to purchase

The course package data is stored in the “course” application

but

data

Page 27: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Solution

readonly db connection

data

course

Page 28: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Code

Model:class CoursePackage < ActiveRecord::Base acts_as_readonly :courseend

View:<ul><% CoursePackage.all.each do |package| %> <li><%= package.title %> <%= package.price %></li><% end %></ul>

data

Page 29: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Why doesn’t this break the rule of loose coupling?

data

Page 30: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

acts_as_readonly in Depth

def acts_as_readonly(name, options = {})config = CoreService.app(name).databaseestablish_connection config[Rails.env]set_table_name(self.connection.current_database + (options[:table_name]||table_name).to_s)

end

data

Page 31: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

acts_as_readonly in Depth

def acts_as_readonly(name, options = {})

config = CoreService.app(name).databaseestablish_connection config[Rails.env]set_table_name(self.connection.current_database + (options[:table_name]||table_name).to_s)

end

data

Page 32: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Core service

class CoreService < ActiveResource::Base self.site = :userdef self.app(app_name)

CoreService.find(app_name)end

end

data

Page 33: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Centralized configurationdata

How does Core know all the configurations?

Page 34: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Each app posts its configuration to core when it is started

data

Page 35: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

data

config/site_config.ymlapp: courseapi: course_list: package/courses

config/initializers/idp_initializer.rb

CoreService.reset_config

Page 36: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

data

core_service.rb in idp_lib

APP_CONFIG = YAML.load(Rails.root.join(“config/site_config.yml”))

def reset_config self.post(:reset_config, :app => { :name => APP_CONFIG["app"], :settings => APP_CONFIG, :database => YAML.load_file(

Rails.root.join("config/database.yml"))})end

Page 37: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

data

Page 38: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Again, implemented in gem

data

config/environment.rb

config.gem ‘idp_helpers’config.gem ‘idp_lib’

Page 39: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

data

gems

Page 40: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

• Web services for “write” interactions

class CoursePackageService < ActiveSupport::Base self.site = :courseend

data

Page 41: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

example

Roadmap needs to be generated after learner pays.

data

Page 42: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

codesCourse: app/controllers/roadmap_services_controller.rb

def createRoadmap.generate(params[:user_id], params[:course_id])

end

Purchase: app/models/roadmap_service.rb

class RoadmapService < ActiveSupport::Base self.site = :courseendPurchase: app/models/order.rb

def activate_roadmap RoadmapService.create(self.user_id, self.course_id)end

data

Page 43: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

AJAX Loaded Composite Viewdata

<div><%= ajax_load(url_of(:course, :course_list)) %></div>

Fetched from different

applications

Ecosystem url_for

Page 44: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

interface data user

Page 45: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Features of User Service

• Registration/login• Profile management• Role Based Access Control

user

Page 46: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Access Control

Each Controller is one Node

user

* Posted to user service when app starts

Page 47: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Access Control

before_filter :check_access_right

def check_access_right unless xml_request? or inner_request? access_denied unless has_page_right?(params[:controller]) endend

user

* Design your apps so access control can be by controller!

Page 48: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

How to share?

user

Page 49: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Step 1: User Authuser

Page 50: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Step 1: User Auth

config/initializers/idp_initializer.rb

ActionController::Base.session_store = :active_record_storeActiveRecord::SessionStore::Session.acts_as_remote :user,

:readonly => false

user

Page 51: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Step 2: Access ControlTell core its controllers structure

CoreService. reset_rights

def self.reset_rights data = load_controller_structure

self.post(:reset_rights, :data => data)end

user

Page 52: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Step 2: Access Control

before_filter :check_access_right

def check_access_right unless xml_request? or inner_request? access_denied unless has_page_right?(params[:controller]) endend

user

Page 53: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Step 2: Access Control

has_page_right?

Readonly db conn again

user

Page 54: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Step 2: Access Control

def has_page_right?(page) roles = current_user.roles

roles_of_page = IdpRoleRight.all(:conditions => ["path = ?", page]).map(&:role_id)

(roles - (roles - roles_of_page)).size > 0end

class IdpRoleRight < ActiveRecord::Base acts_as_readonly :user, :table_name => "role_rights"end

user

Page 55: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Again, gems!user

config/environment.rb

config.gem ‘idp_helpers’config.gem ‘idp_lib’config.gem ‘idp_core’

Page 56: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

interface servicedata user

Page 57: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Support applications

• File• Mail• Comet service

service

Page 58: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Fileclass Article < ActiveRecord::Base has_filesend

@article.files.first.url

Upload File in Background to

FileService

Store with app_name,

model_name, model_id

Use readonly magic to easily

display

Idp_file_form

Specify Class that Has Files

Page 59: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Cometservice

class ChatRoom < ActiveRecord::Base acts_as_realtime end

<%= realtime_for(@chat_room, current_user.login) %>

<%= realtime_data(dom_id, :add, :top) %>

@chat_room.realtime_channel.broadcast(“hi world", current_user.login)

Page 60: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

mail

Mail services

MailService.send(“[email protected]”, :welcome, :user => “test”)

service

Page 61: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Host all in one domain

Load each rails app into a subdir, we use Unicorn

unicorn_rails --path /user unicorn_rails --path /studycenter unicorn_rails --path /scenario

Page 62: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Host all in one domain

use Nginx as a reverse proxy

location /user { proxy_pass http://rails_app_user; }

location /studycenter { proxy_pass http://rails_app_studycenter; }

Page 63: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Host all in one domain

All you see is a uniform URL

www.eqenglish.com/user www.eqenglish.com/studycenter www.eqenglish.com/scenario

Page 64: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem
Page 65: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Pair-deploy

Page 66: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

How to split one into many?

Page 67: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

By Story

Each App is one group of similar features.

By DataEach App writes to the same data

Page 68: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Example

• User Management• Course package• Purchase• Learning process• …

Page 69: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Iteration

Page 70: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Be adventurous at the beginning.Split one into as many as you think

is sensitive

Page 71: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Then you may find

• Some applications interact with each other frequently.

• Lots of messy and low efficiency code to deal with interacting.

Page 72: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Merge them into one.

Page 73: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Measurement

• Critical and core task of single app should not call services of others.

• One doesn’t need to know much about others’ business to do one task (or develop).

• Independent Stories

Page 74: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Pitfalls

• Applications need to be on the same intranet.

• No “right place” for certain cases.

Page 75: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

Results: Higher Productivity

-Faster build to deploy-More developer autonomy-Safer- Scalable-Easier to “jump in”- Greater Happiness

Page 76: RailsConf 2010: From 1 to 30 - How to refactor one monolithic application into an application ecosystem

© 2010 Idapted, Ltd.

Thank you!

Q&Ahttp://www.idapted.comhttp://developer.idapted.comhttp://t.sina.com.cn/[email protected] (@jpalley)[email protected] (@fiyuer)