The document discusses refactoring a monolithic Rails application into a service-oriented architecture (SOA). It describes separating the application into multiple services based on functionality (e.g. qualification logic, delivery logic). This is done incrementally using a "strangler fig" approach of creating new services alongside the existing monolithic code. Tests are used to define the behavior of the new services. Data validation rules are also separated out into a scrubbing service. The approach aims to separate responsibilities, operate services asynchronously, and make incremental changes to transition from a monolithic to microservices architecture.
21. 1. Slow
2. Buggy
3. New Features Take Forever To Implement
4. Deploys Are A Nightmare
5. Tons Of Dependencies
Saturday, March 19, 2011
22. We Needed To Go From This...
Saturday, March 19, 2011
23. Monolithic Rails
App Company
Controller
Contact
Lead
Invitation Contact Demographic
Status
Enrollment
Degree
LeadProcess Level
Controller Lead
Study
Program Company
Area
Referral
Leads
School Delivery
Controller Budget
Client
Search
Programs
Campus Budget Budget Controller
Reports Caps Caps
Search
Controller
Result Campus
Location Campus
Schools Budgets Locations
Controller Controller Controller
Saturday, March 19, 2011
25. Lead Lead Email Lead
Qualification Delivery Remarketing Conversion
RabbitMQ
Operational Reporting
Data Stores
Survey Budget
Admin
Engine Service
Saturday, March 19, 2011
26. Lead Lead Email Lead
Qualification Delivery Remarketing Conversion
RabbitMQ
Operational Reporting
Data Stores
Survey Budget
Admin
Engine Service
Saturday, March 19, 2011
27. Lead Lead Email Lead
Qualification Delivery Remarketing Conversion
RabbitMQ
Operational Reporting
Data Stores
Survey Budget
Admin
Engine Service
Saturday, March 19, 2011
28. Lead Lead Email Lead
Qualification Delivery Remarketing Conversion
RabbitMQ
Operational Reporting
Data Stores
Survey Budget
Admin
Engine Service
Saturday, March 19, 2011
34. Qualification Monolithic Rails
Logic App Company
Controller
Contact
Lead
Invitation Contact Demographic
Status
Enrollment
Degree
LeadProcess Level
Controller Lead
Study
Program Company
Area
Referral
Leads
School Delivery
Controller Budget
Client
Search
Programs
Campus Budget Budget Controller
Reports Caps Caps
Search
Controller
Result Campus
Location Campus
Schools Budgets Locations
Controller Controller Controller
Saturday, March 19, 2011
35. Qualification
Monolithic Rails
Logic App Company
Controller
Contact
Lead
Invitation Contact Demographic
Status
Enrollment
Degree
LeadProcess Level
Controller Lead
Study
Program Company
Area
Referral
Leads
School Delivery
Controller Budget
Client
Search
Programs
Campus Budget Budget Controller
Reports Caps Caps
Search
Controller
Result Campus
Location Campus
Schools Budgets Locations
Controller Controller Controller
Saturday, March 19, 2011
36. Qualification
Monolithic Rails
App Logic Company
Controller
Contact
Lead
Invitation Contact Demographic
Status
Enrollment
Degree
LeadProcess Level
Controller Lead
Study
Program Company
Area
Referral
Leads
School Delivery
Controller Budget
Client
Search
Programs
Campus Budget Budget Controller
Reports Caps Caps
Search
Controller
Result Campus
Location Campus
Schools Budgets Locations
Controller Controller Controller
Saturday, March 19, 2011
37. Monolithic Rails
Qualification
App Company
Logic Controller
Contact
Lead
Invitation Contact Demographic
Status
Enrollment
Degree
LeadProcess Level
Controller Lead
Study
Program Company
Area
Referral
Leads
School Delivery
Controller Budget
Client
Search
Programs
Campus Budget Budget Controller
Reports Caps Caps
Search
Controller
Result Campus
Location Campus
Schools Budgets Locations
Controller Controller Controller
Saturday, March 19, 2011
38. Monolithic Rails
App Delivery Logic
Company
Controller
Contact
Lead
Invitation Contact Demographic
Status
Enrollment
Degree
LeadProcess Level
Controller Lead
Study
Program Company
Area
Referral
Leads
School Delivery
Controller Budget
Client
Search
Programs
Campus Budget Budget Controller
Reports Caps Caps
Search
Controller
Result Campus
Location Campus
Schools Budgets Locations
Controller Controller Controller
Saturday, March 19, 2011
39. Monolithic Rails
Delivery Logic App Company
Controller
Contact
Lead
Invitation Contact Demographic
Status
Enrollment
Degree
LeadProcess Level
Controller Lead
Study
Program Company
Area
Referral
Leads
School Delivery
Controller Budget
Client
Search
Programs
Campus Budget Budget Controller
Reports Caps Caps
Search
Controller
Result Campus
Location Campus
Schools Budgets Locations
Controller Controller Controller
Saturday, March 19, 2011
40. Legacy Tests As Behavior Scaffolding
Saturday, March 19, 2011
41. 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
42. describe Contact do
describe "#validate" do
it "requires 10 digit phone numbers" do
Phone
...
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
43. 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
...
Zip
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
44. 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
...
Email
end
it "rejects blank emails" do
...
end
it "rejects emails with bad words" do
...
end
...
end
Saturday, March 19, 2011
45. 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
...
end
end
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
46. 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
47. 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 => "foo@yahoo.com",
:phone => "8011231234",
:country => "US"
})
Saturday, March 19, 2011
48. 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 => "foo@yahoo.com",
:phone => "8011231234",
:country => "US"
})
Saturday, March 19, 2011
49. 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 => "foo@yahoo.com",
:phone => "8011231234",
:country => "US"
})
Saturday, March 19, 2011
50. 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 => "foo@yahoo.com",
:phone => "8011231234",
:country => "US"
})
Saturday, March 19, 2011
55. Qualification Service
Factory
Scrubber Scrubbing DSL
Conditions Operands
Scrubbing Engine
Parameterized
Zip Phone Email
Rule
Saturday, March 19, 2011
56. Delivery Service
Field Mapper Lead Formatter
Phone Date Case Truncate Get/Post Email FTP/SFTP Custom
Lead Deliverer Response Handler
Get/Post Email FTP/SFTP Custom
Saturday, March 19, 2011
58. Monolithic Rails
App Company
Controller
Contact
Lead
Invitation Contact Demographic
Status
Enrollment
Degree
LeadProcess Level
Controller Lead
Study
Program Company
Area
Referral
Leads
School Delivery
Controller Budget
Client
Search
Programs
Campus Budget Budget Controller
Reports Caps Caps
Search
Controller
Result Campus
Location Campus
Schools Budgets Locations
Controller Controller Controller
Saturday, March 19, 2011
69. Client 1
Monolithic Rails
App
Lead Delivery Client 2
Client 3
Saturday, March 19, 2011
70. Monolithic Rails
Client 1
App
Client 1
Router
Lead Client 2
Delivery
Client 3
Saturday, March 19, 2011
71. Monolithic Rails
Client 1
App
Client 1
Router
Lead Client 2
Delivery
Client 3
Saturday, March 19, 2011
72. 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
73. 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
74. 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
76. 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
77. Monolithic Rails
Client 1
App
Client 1
Router
Lead Client 2
Delivery
Client 3
Saturday, March 19, 2011
78. Client 1
Monolithic Rails
App
Lead
Legacy Delivery Code Delivery Client 2
Client 3
Saturday, March 19, 2011
98. Overly Decomposed Services
Combat over-decomposition by
consolidating code w/o necessarily
isolating it as a ser vice
1) gems
2) local CouchDB stores that sync w/ a
master store
Saturday, March 19, 2011
99. Lead Lead Email
Qualification Delivery Remarketing
Survey
Engine
Field Rules
Formatting Engine
Saturday, March 19, 2011
100. Lead Lead Email
Qualification Delivery Remarketing
Survey
Engine
Field Rules
Formatting Engine
Saturday, March 19, 2011
101. Lead Lead Email
Qualification Delivery Remarketing
Survey
Engine
Field Rules
Formatting Engine
Saturday, March 19, 2011
102. Lead Lead Email
Qualification Delivery Remarketing
Survey
Engine
Field Rules
Formatting Engine
Saturday, March 19, 2011
103. Lead Lead Email
Qualification Delivery Remarketing
Survey
Engine
Field Rules
Formatting Engine
Saturday, March 19, 2011
104. Lead Lead Email
Qualification Delivery Remarketing
Survey
Engine
Saturday, March 19, 2011