refactor your monolithic rails app to a soa
DESCRIPTION
TRANSCRIPT
Refactoring Your Monolithic Rails App
To A SOA:
Risks and Rewards
Saturday, March 19, 2011
The Birth Of A Monolithic Rails App:
A Simple Survey Engine
Saturday, March 19, 2011
Saturday, March 19, 2011
Saturday, March 19, 2011
Saturday, March 19, 2011
Saturday, March 19, 2011
Saturday, March 19, 2011
Saturday, March 19, 2011
Pretty Simple App, huh?
Saturday, March 19, 2011
“But we need to remarket all those emails.”
Saturday, March 19, 2011
Saturday, March 19, 2011
“We need more complex surveys.”
Saturday, March 19, 2011
Saturday, March 19, 2011
“Now we need an admin. And where’s our reporting?”
Saturday, March 19, 2011
Saturday, March 19, 2011
“Some clients need SOAP deliveries.”
Saturday, March 19, 2011
Saturday, March 19, 2011
And so on for 4 years.
Until...
Saturday, March 19, 2011
“What’s wrong with the site guys?”
Saturday, March 19, 2011
1. Slow
2. Buggy
3. New Features Take Forever To Implement
4. Deploys Are A Nightmare
5. Tons Of Dependencies
Saturday, March 19, 2011
We Needed To Go From This...
Saturday, March 19, 2011
DeliveryClient
BudgetCaps
BudgetSchool
Search
Lead
Referral
LeadProcessController
SearchResult
Campus
LeadStatus
CampusLocation
BudgetCaps
Contact
ContactDemographic
Program
DegreeLevel
StudyArea Company
Invitation
Enrollment
ReportsController
SchoolsController
BudgetsController
CampusLocationsController
ProgramsController
LeadsController
CompanyController
Monolithic RailsApp
Saturday, March 19, 2011
To This...
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
RabbitMQ
LeadConversion
Reporting
BudgetService
AdminSurveyEngine
OperationalData Stores
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
RabbitMQ
LeadConversion
Reporting
BudgetService
AdminSurveyEngine
OperationalData Stores
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
RabbitMQ
LeadConversion
BudgetService
AdminSurveyEngine
OperationalData Stores
Reporting
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
RabbitMQ
LeadConversion
Reporting
BudgetService
Admin
OperationalData Stores
SurveyEngine
Saturday, March 19, 2011
But How?
Saturday, March 19, 2011
Good Code
Bad Code
The Strangler Approach
Saturday, March 19, 2011
3 Drivers To Our ‘Strangler’ Approach:
1. Separate Responsibilities
2. Operate Asynchronously
3. Make Incremental Changes
Saturday, March 19, 2011
1. Separate Responsibilities
Saturday, March 19, 2011
Consolidate Like Functionality
Saturday, March 19, 2011
DeliveryClient
BudgetCaps
BudgetSchool
Search
Lead
Referral
LeadProcessController
SearchResult
Campus
LeadStatus
CampusLocation
BudgetCaps
Contact
ContactDemographic
Program
DegreeLevel
StudyArea Company
Invitation
Enrollment
ReportsController
SchoolsController
BudgetsController
CampusLocationsController
ProgramsController
LeadsController
CompanyController
Qualification Logic
Monolithic RailsApp
Saturday, March 19, 2011
DeliveryClient
BudgetCaps
BudgetSchool
Search
Lead
Referral
LeadProcessController
SearchResult
Campus
LeadStatus
CampusLocation
BudgetCaps
Contact
ContactDemographic
Program
DegreeLevel
StudyArea Company
Invitation
Enrollment
ReportsController
SchoolsController
BudgetsController
CampusLocationsController
ProgramsController
LeadsController
CompanyController
Monolithic RailsApp
Qualification Logic
Saturday, March 19, 2011
DeliveryClient
BudgetCaps
BudgetSchool
Search
Lead
Referral
LeadProcessController
SearchResult
Campus
LeadStatus
CampusLocation
BudgetCaps
Contact
ContactDemographic
Program
DegreeLevel
StudyArea Company
Invitation
Enrollment
ReportsController
SchoolsController
BudgetsController
CampusLocationsController
ProgramsController
LeadsController
CompanyController
Monolithic RailsApp
Qualification Logic
Saturday, March 19, 2011
DeliveryClient
BudgetCaps
BudgetSchool
Search
Lead
Referral
LeadProcessController
SearchResult
Campus
LeadStatus
CampusLocation
BudgetCaps
Contact
ContactDemographic
Program
DegreeLevel
StudyArea Company
Invitation
Enrollment
ReportsController
SchoolsController
BudgetsController
CampusLocationsController
ProgramsController
LeadsController
CompanyController
Qualification Logic
Monolithic RailsApp
Saturday, March 19, 2011
DeliveryClient
BudgetCaps
BudgetSchool
Search
Lead
Referral
LeadProcessController
SearchResult
Campus
LeadStatus
CampusLocation
BudgetCaps
Contact
ContactDemographic
Program
DegreeLevel
StudyArea Company
Invitation
Enrollment
ReportsController
SchoolsController
BudgetsController
CampusLocationsController
ProgramsController
LeadsController
CompanyController
Delivery LogicMonolithic Rails
App
Saturday, March 19, 2011
DeliveryClient
BudgetCaps
BudgetSchool
Search
Lead
Referral
LeadProcessController
SearchResult
Campus
LeadStatus
CampusLocation
BudgetCaps
Contact
ContactDemographic
Program
DegreeLevel
StudyArea Company
Invitation
Enrollment
ReportsController
SchoolsController
BudgetsController
CampusLocationsController
ProgramsController
LeadsController
CompanyController
Delivery LogicMonolithic Rails
App
Saturday, March 19, 2011
Legacy Tests As Behavior Scaffolding
Saturday, March 19, 2011
describe Contact do describe "#validate" do it "requires 10 digit phone numbers" do ... end
it "rejects blank phone numbers" do ... end
it "rejects areacodes that start with 0 or 1" do ... end
it "requires 5 digit zip" do ... end
it "rejects invalidly formatted emails" do ... end
it "rejects blank emails" do ... end
it "rejects emails with bad words" do ... end ...end
Saturday, March 19, 2011
describe Contact do describe "#validate" do it "requires 10 digit phone numbers" do ... end
it "rejects blank phone numbers" do ... end
it "rejects areacodes that start with 0 or 1" do ... end
it "requires 5 digit zip" do ... end
it "rejects invalidly formatted emails" do ... end
it "rejects blank emails" do ... end
it "rejects emails with bad words" do ... end ...end
Phone
Saturday, March 19, 2011
describe Contact do describe "#validate" do it "requires 10 digit phone numbers" do ... end
it "rejects blank phone numbers" do ... end
it "rejects areacodes that start with 0 or 1" do ... end
it "requires 5 digit zip" do ... end
it "rejects invalidly formatted emails" do ... end
it "rejects blank emails" do ... end
it "rejects emails with bad words" do ... end ...end
Zip
Saturday, March 19, 2011
describe Contact do describe "#validate" do it "requires 10 digit phone numbers" do ... end
it "rejects blank phone numbers" do ... end
it "rejects areacodes that start with 0 or 1" do ... end
it "requires 5 digit zip" do ... end
it "rejects invalidly formatted emails" do ... end
it "rejects blank emails" do ... end
it "rejects emails with bad words" do ... end ...end
Saturday, March 19, 2011
describe Phone do describe "#is_satisfied_by?" do
it "returns false if the number is not 10 digits long" do ... end
it "returns false if number matches invalid number list" do ... end
it "returns true if area code is valid" do ... end ... endend
describe Zip do describe "#is_satisfied_by" do
it "returns false if zip is not 5 digits" do ... end
it "returns false if zip does not exist in postal_codes database table" do ... end
... end end
Saturday, March 19, 2011
describe Contact do describe "#validate" do it "requires 10 digit phone numbers" do ... end
it "rejects blank phone numbers" do ... end
it "rejects areacodes that start with 0 or 1" do ... end
it "requires 5 digit zip" do ... end
it "rejects invalidly formatted emails" do ... end
it "rejects blank emails" do ... end
it "rejects emails with bad words" do ... end ...end
Saturday, March 19, 2011
scrubber = Scrubber.configure do |s| s.scrub(:first_name).as(:invalid).if(:blank) s.scrub(:last_name).as(:invalid).if(:blank) s.scrub(:email).as(:invalid).if(:blank) s.scrub(:phone).as(:invalid).if(:blank)
s.scrub(:first_name).as(:invalid).unless(:kid_friendly) s.scrub(:last_name).as(:invalid).unless(:kid_friendly) s.scrub(:email).as(:invalid).unless(:kid_friendly)
s.scrub(:country).as(:filtered).if {|lead| lead.contact.country == 'Other'}.and_warn("Only requests from the United States can be accepted at this time")
s.scrub_with(:email).as(:filtered) s.scrub_with(:education_level).as(:filtered) s.scrub_with(:duplicate).as(:filtered)end
scrubber.process({ :first_name => "Chris", :last_name => "Wyckoff", :email => "[email protected]", :phone => "8011231234", :country => "US" })
Saturday, March 19, 2011
scrubber = Scrubber.configure do |s| s.scrub(:first_name).as(:invalid).if(:blank) s.scrub(:last_name).as(:invalid).if(:blank) s.scrub(:email).as(:invalid).if(:blank) s.scrub(:phone).as(:invalid).if(:blank)
s.scrub(:first_name).as(:invalid).unless(:kid_friendly) s.scrub(:last_name).as(:invalid).unless(:kid_friendly) s.scrub(:email).as(:invalid).unless(:kid_friendly)
s.scrub(:country).as(:filtered).if {|lead| lead.contact.country == 'Other'}.and_warn("Only requests from the United States can be accepted at this time")
s.scrub_with(:email).as(:filtered) s.scrub_with(:education_level).as(:filtered) s.scrub_with(:duplicate).as(:filtered)end
scrubber.process({ :first_name => "Chris", :last_name => "Wyckoff", :email => "[email protected]", :phone => "8011231234", :country => "US" })
Saturday, March 19, 2011
scrubber = Scrubber.configure do |s| s.scrub(:first_name).as(:invalid).if(:blank) s.scrub(:last_name).as(:invalid).if(:blank) s.scrub(:email).as(:invalid).if(:blank) s.scrub(:phone).as(:invalid).if(:blank)
s.scrub(:first_name).as(:invalid).unless(:kid_friendly) s.scrub(:last_name).as(:invalid).unless(:kid_friendly) s.scrub(:email).as(:invalid).unless(:kid_friendly)
s.scrub(:country).as(:filtered).if {|lead| lead.contact.country == 'Other'}.and_warn("Only requests from the United States can be accepted at this time")
s.scrub_with(:email).as(:filtered) s.scrub_with(:education_level).as(:filtered) s.scrub_with(:duplicate).as(:filtered)end
scrubber.process({ :first_name => "Chris", :last_name => "Wyckoff", :email => "[email protected]", :phone => "8011231234", :country => "US" })
Saturday, March 19, 2011
scrubber = Scrubber.configure do |s| s.scrub(:first_name).as(:invalid).if(:blank) s.scrub(:last_name).as(:invalid).if(:blank) s.scrub(:email).as(:invalid).if(:blank) s.scrub(:phone).as(:invalid).if(:blank)
s.scrub(:first_name).as(:invalid).unless(:kid_friendly) s.scrub(:last_name).as(:invalid).unless(:kid_friendly) s.scrub(:email).as(:invalid).unless(:kid_friendly)
s.scrub(:country).as(:filtered).if {|lead| lead.contact.country == 'Other'}.and_warn("Only requests from the United States can be accepted at this time")
s.scrub_with(:email).as(:filtered) s.scrub_with(:education_level).as(:filtered) s.scrub_with(:duplicate).as(:filtered)end
scrubber.process({ :first_name => "Chris", :last_name => "Wyckoff", :email => "[email protected]", :phone => "8011231234", :country => "US" })
Saturday, March 19, 2011
Lather, Rinse, Repeat
Saturday, March 19, 2011
LeadQualification
Monolithic RailsApp
Saturday, March 19, 2011
LeadDelivery
LeadQualification
Monolithic RailsApp
Saturday, March 19, 2011
Cohesion
Saturday, March 19, 2011
Scrubbing Engine
Zip Phone EmailParameterized
Rule
Scrubbing DSLScrubber
Conditions Operands
Factory
Qualification Service
Saturday, March 19, 2011
Delivery Service
Lead FormatterField Mapper
Get/Post Email FTP/SFTP CustomPhone Date Case Truncate
Lead Deliverer
Get/Post Email FTP/SFTP Custom
Response Handler
Saturday, March 19, 2011
Adhesion
Saturday, March 19, 2011
DeliveryClient
BudgetCaps
BudgetSchool
Search
Lead
Referral
LeadProcessController
SearchResult
Campus
LeadStatus
CampusLocation
BudgetCaps
Contact
ContactDemographic
Program
DegreeLevel
StudyArea Company
Invitation
Enrollment
ReportsController
SchoolsController
BudgetsController
CampusLocationsController
ProgramsController
LeadsController
CompanyController
Monolithic RailsApp
Saturday, March 19, 2011
2. Operate Asynchronously
Saturday, March 19, 2011
Client 1Monolithic Rails
App
Saturday, March 19, 2011
Client 1
Client 2
Monolithic RailsApp
Saturday, March 19, 2011
Client 1
Client 2
Client 3
Monolithic RailsApp
Saturday, March 19, 2011
LeadDelivery
Client 1
Client 2
Client 3
Monolithic RailsApp
Saturday, March 19, 2011
Client 1
Client 2
Client 3
LeadDelivery
LeadDelivery
LeadDelivery
Monolithic RailsApp
Saturday, March 19, 2011
Client 1
Client 2
Client 3
200 ms
LeadDelivery
LeadDelivery
LeadDelivery
Monolithic RailsApp
Saturday, March 19, 2011
Client 1
Client 2
Client 3
200 ms
LeadDelivery
LeadDelivery
LeadDelivery
4000 ms
Monolithic RailsApp
Saturday, March 19, 2011
Client 1
Client 2
Client 3
200 ms
LeadDelivery
LeadDelivery
LeadDelivery
4000 ms
10000 ms
Monolithic RailsApp
Saturday, March 19, 2011
3. Make Incremental Changes
Saturday, March 19, 2011
Lead Delivery
Client 1
Client 2
Client 3
Monolithic RailsApp
Saturday, March 19, 2011
Router
LeadDelivery
Client 1
Client 1
Client 2
Client 3
Monolithic RailsApp
Saturday, March 19, 2011
Monolithic RailsApp
LeadDelivery
Client 1
Client 1
Client 2
Client 3
Router
Saturday, March 19, 2011
class DeliveryRouter
def self.route(lead) if(publishable_to_new_delivery?(self)) DeliveryRouter.publish(lead) else lead.submit end end
def self.publish(lead) Bunny.publish(:lead_delivery, DeliveryMapper.map(lead)) end def self.publishable_to_new_delivery?(lead) lead.school.active_for_new_delivery? end end
Saturday, March 19, 2011
class DeliveryRouter
def self.route(lead) if(publishable_to_new_delivery?(self)) DeliveryRouter.publish(lead) else lead.submit end end
def self.publish(lead) Bunny.publish(:lead_delivery, DeliveryMapper.map(lead)) end def self.publishable_to_new_delivery?(lead) lead.school.active_for_new_delivery? end end
Saturday, March 19, 2011
class DeliveryRouter
def self.route(lead) if(publishable_to_new_delivery?(self)) DeliveryRouter.publish(lead) else lead.submit end end
def self.publish(lead) Bunny.publish(:lead_delivery, DeliveryMapper.map(lead)) end def self.publishable_to_new_delivery?(lead) lead.school.active_for_new_delivery? end end
Saturday, March 19, 2011
class DeliveryMapper
def self.map(lead) { :first_name => lead.contact.first_name, :last_name => lead.contact.last_name, :address => lead.contact.address, :city => lead.contact.city, :state => lead.contact.state, :zip => lead.contact.zip, :phone => lead.contact.phone, :email => lead.contact.email, :client_id => lead.client.id, :lead_id => lead.id, ... } end
end
Saturday, March 19, 2011
class DeliveryRouter
def self.route(lead) if(publishable_to_new_delivery?(self)) DeliveryRouter.publish(lead) else lead.submit end end
def self.publish(lead) Bunny.publish(:lead_delivery, DeliveryMapper.map(lead)) end def self.publishable_to_new_delivery?(lead) lead.school.active_for_new_delivery? end end
Saturday, March 19, 2011
Router
LeadDelivery
Client 1
Client 1
Client 2
Client 3
Monolithic RailsApp
Saturday, March 19, 2011
LeadDelivery
Client 1
Client 2
Client 3
Legacy Delivery Code
Monolithic RailsApp
Saturday, March 19, 2011
What Did We Gain?
Saturday, March 19, 2011
Speed
Saturday, March 19, 2011
Maintainability
Saturday, March 19, 2011
Testability
Saturday, March 19, 2011
Composability
Saturday, March 19, 2011
SourceLead
QualificationLead
Delivery
Saturday, March 19, 2011
SourceLead
DeliveryLead
QualificationLead
Assignment
Saturday, March 19, 2011
SourceLead
QualificationLead
DeliveryCall
Center
Call Center
LeadDelivery Client
Saturday, March 19, 2011
LeadQualification
LeadDelivery
LeadDelivery
LeadAssignment
LeadDelivery
Call Center
LeadDelivery Client
Saturday, March 19, 2011
Education
LeadDelivery
LeadQualification
Employment
Loan Financing
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
RabbitMQ
LeadConversion
Reporting
BudgetServiceAdmin
Saturday, March 19, 2011
{'event':'delivery','status':'accepted','lead_id':'1234','timestamp':'2011-02-23 08:09:01','lead_details':{
...}
}
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
RabbitMQ
LeadConversion
Reporting
BudgetServiceAdmin
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
RabbitMQ
LeadConversion
Reporting
BudgetServiceAdmin
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
RabbitMQ
LeadConversion
Reporting
BudgetServiceAdmin
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
RabbitMQ
LeadConversion
Reporting
BudgetServiceAdmin
Saturday, March 19, 2011
What Did We Risk?
Saturday, March 19, 2011
Maintainability
Saturday, March 19, 2011
Testability
Saturday, March 19, 2011
Combat over-decomposition by consolidating code w/o necessarily isolating it as a service
1) gems2) local CouchDB stores that sync w/ a master store
Overly Decomposed Services
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
FieldFormatting
Rules Engine
SurveyEngine
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
FieldFormatting
Rules Engine
SurveyEngine
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
FieldFormatting
Rules Engine
SurveyEngine
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
FieldFormatting
Rules Engine
SurveyEngine
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
FieldFormatting
Rules Engine
SurveyEngine
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
SurveyEngine
Saturday, March 19, 2011
LeadDelivery
Saturday, March 19, 2011
LeadQualification
LeadDelivery
EmailRemarketing
SurveyEngine
Saturday, March 19, 2011
Saturday, March 19, 2011
Map Your Architecture
Saturday, March 19, 2011
1) http://www.vanderburg.org/Blog/Software/Development/cohesion.rdoc
2) http://www.martinfowler.com/blikiStranglerApplication.html
3) http://www.eaipatterns.com/docs/EDA.pdf
Saturday, March 19, 2011
Saturday, March 19, 2011
Thank You.
Questions?
Saturday, March 19, 2011