How to disassemble one monster app into an ecosystem of 30
Upcoming SlideShare
Loading in...5
×
 

Like this? Share it with your network

Share

How to disassemble one monster app into an ecosystem of 30

on

  • 1,899 views

 

Statistics

Views

Total Views
1,899
Views on SlideShare
1,899
Embed Views
0

Actions

Likes
2
Downloads
31
Comments
0

0 Embeds 0

No embeds

Accessibility

Categories

Upload Details

Uploaded via as Adobe PDF

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment

How to disassemble one monster app into an ecosystem of 30 Presentation Transcript

  • 1. From 1 To 30 How To Disassemble One Monster App Into An Ecosystem Of 30 Beta 技术沙龙 http://club.blogbeta.com 官方twitter : @betasalon Groups : http:// groups.google.com/group/betasalon Jonathan Palley, CTO/COO Guo Lei, Chief Architect © 2010 Idapted, Ltd.
  • 2. An Experience
  • 3. A Tale of Two Buildings
  • 4. 2009 Shanghai Lotus Riverside Community 1909 Beijing Forbidden City
  • 5. 1 30
  • 6. What is one?
  • 7. 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)
  • 8. Problems Confused new staff Hard to test/extend/scale
  • 9. What is 30?
  • 10. A ecosystem of applications
  • 11. Independent
  • 12. Linked and Seamless
  • 13. 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
  • 14. What’s the mystery of the forbidden city?
  • 15. Consistent UI • Shared CSS/JS/Styleguide • Common Helpers in Shared Gem • Safely try new things
  • 16. interface 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" />
  • 17. interface CSS Framework
  • 18. interface Abstract Common Helpers to Gem Search function for models
  • 19. interface 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)
  • 20. interface Common Helpers: List table well formatted sortable with pagination customizable
  • 21. interface 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 %>
  • 22. interface Development Lifecycle 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
  • 23. interface data
  • 24. data How do applications share data? (remember: each app has its own data) -“Read Only” Database Connections - Services - AJAX Loaded View Segments
  • 25. data Business example user purchase course learning process
  • 26. data Purchase App Requirement: List course packages for user to select to purchase but The course package data is stored in the “course” application
  • 27. data Solution readonly db connection course
  • 28. data Code Model: class CoursePackage < ActiveRecord::Base acts_as_readonly :course end View: <ul> <% CoursePackage.all.each do |package| %> <li><%= package.title %> <%= package.price %></li> <% end %> </ul>
  • 29. data Why doesn’t this break the rule of loose coupling? Model: class CoursePackage < ActiveRecord::Base acts_as_readonly :course end View: <ul> <% CoursePackage.all.each do |package| %> <li><%= package.title %> <%= package.price %></li> <% end %> </ul>
  • 30. data acts_as_readonly in Depth def acts_as_readonly(name, options = {}) config = CoreService.app(name).database establish_connection config[Rails.env] set_table_name(self.connection.current_database + (options[:table_name]||table_name).to_s) end
  • 31. data acts_as_readonly in Depth def acts_as_readonly(name, options = {}) config = CoreService.app(name).database establish_connection config[Rails.env] set_table_name(self.connection.current_database + (options[:table_name]||table_name).to_s) end
  • 32. data Core service class CoreService < ActiveResource::Base self.site = :user def self.app(app_name) CoreService.find(app_name) end end
  • 33. data Centralized configuration How does Core know all the configurations?
  • 34. data Each app posts its configuration to core when it is started
  • 35. data config/site_config.yml app: course api: course_list: package/courses config/initializers/idp_initializer.rb CoreService.reset_config
  • 36. 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
  • 37. data Model in Purchase: class CoursePackage < ActiveRecord::Base acts_as_readonly :course end
  • 38. data Again, implemented in gem config/environment.rb config.gem ‘idp_helpers’ config.gem ‘idp_lib’
  • 39. data gems
  • 40. data • Web services for “write” interactions class CoursePackageService < ActiveSupport::Base self.site = :course end
  • 41. data example Roadmap needs to be generated after learner pays.
  • 42. data codes Course: app/controllers/roadmap_services_controller.rb def create Roadmap.generate(params[:user_id], params[:course_id]) end Purchase: app/models/roadmap_service.rb class RoadmapService < ActiveSupport::Base self.site = :course end Purchase: app/models/order.rb def activate_roadmap RoadmapService.create(self.user_id, self.course_id) end
  • 43. data AJAX Loaded Composite View <div> <%= ajax_load(url_of(:course, :course_list)) %> </div> Ecosystem url_for Fetched from different applications
  • 44. data Open Source http://github.com/idapted/eco_apps
  • 45. interface data user
  • 46. user Features of User Service • Registration/login • Profile management • Role Based Access Control
  • 47. user Access Control Each Controller is one Node * Posted to user service when app starts
  • 48. user 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]) end end * Design your apps so access control can be by controller!
  • 49. user How to share?
  • 50. user Step 1: User Auth SSO Shared Session Same Session Store
  • 51. user Step 1: User Auth config/initializers/idp_initializer.rb ActionController::Base.session_store = :active_record_store ActiveRecord::SessionStore::Session.acts_as_remote :user, :readonly => false
  • 52. user Step 2: Access Control Tell core its controllers structure CoreService. reset_rights def self.reset_rights data = load_controller_structure self.post(:reset_rights, :data => data) end
  • 53. user 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]) end end
  • 54. user Step 2: Access Control has_page_right? Readonly db conn again
  • 55. user Step 2: Access Control class IdpRoleRight < ActiveRecord::Base acts_as_readonly :user, :table_name => "role_rights" end 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 > 0 end
  • 56. user Again, gems! config/environment.rb config.gem ‘idp_helpers’ config.gem ‘idp_lib’ config.gem ‘idp_core’
  • 57. interface data user service
  • 58. service Support applications • File • Mail • Comet service
  • 59. File Specify Class that class Article < ActiveRecord::Base Has Files has_files end Upload File in Background to Idp_file_form FileService Store with app_name, model_name, model_id Use readonly magic to easily @article.files.first.url display
  • 60. service Comet 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)
  • 61. service mail Mail services MailService.send(“test@idapted.com”, :welcome, :user => “test”)
  • 62. 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
  • 63. 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; }
  • 64. Host all in one domain All you see is a uniform URL www.eqenglish.com/user www.eqenglish.com/studycenter www.eqenglish.com/scenario
  • 65. Pair-deploy
  • 66. How to split one into many?
  • 67. By Story Each App is one group of similar features. By Data Each App writes to the same data
  • 68. Example • User Management • Course package • Purchase • Learning process • …
  • 69. Iteration
  • 70. Be adventurous at the beginning. Split one into as many as you think is sensitive
  • 71. Then you may find • Some applications interact with each other frequently. • Lots of messy and low efficiency code to deal with interacting.
  • 72. Merge them into one.
  • 73. 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
  • 74. Pitfalls • Applications need to be on the same intranet. • No “right place” for certain cases.
  • 75. Results: Higher Productivity -Faster build to deploy -More developer autonomy -Safer - Scalable -Easier to “jump in” - Greater Happiness
  • 76. Support Tech • FreeSWITCH • VoIP • http://www.freeswitch.org.cn • Flex • Erlang • concurrent tasks • Titanium • mobile
  • 77. Full Stack of Us CTO Rails/Flex VoIP UI UE System
  • 78. Full Stack of Us Develop environment: • 4 mbp + 4 black apples + 1 linux • textmate & netbeans Servers: • test: 2 physical pc with Xen serving 10 virtual servers • production: 2 (China) + 5(US)
  • 79. Full Stack of Us Web Server: • nginx + unicorn Rails: • Rails version 2.3.4 • Ruby version: 1.8.7
  • 80. Full Stack of Us Plugins: • exception_notification • will_paginate • in_place_editor_plus • acts_as_list • open_flash_chart • Spawn + workling
  • 81. Full Stack of Us Gems: • rspec • factory_girl • thinking-sphinx DB: • mysql Cache: • memcache
  • 82. Full Stack of Us Test: • rspec + factory_girl • Autospec Deploy: • webistrano
  • 83. Full Stack of Us Team Build: • board • code review • tea break • retreat
  • 84. Join Us If you are: • smart • creative • willing to do great things with great people • happen to know JS/Flex/Rails Send whatever can prove your ability to: tech-team-jobs@idapted.com
  • 85. Thank you! Q&A http://developer.idapted.com jpalley@idapted.com (@jpalley) guolei@idapted.com (@fiyuer) © 2010 Idapted, Ltd.