Refactoring product model


Published on

A presentation given in early 2011 at Ruby on Rails Oceania about a conceptual refactoring of legacy code written to handle a reality that didn't eventuate.

  • Be the first to comment

  • Be the first to like this

No Downloads
Total views
On SlideShare
From Embeds
Number of Embeds
Embeds 0
No embeds

No notes for slide
  • Legacy code slows down feature developmentWe all write itSo there’s a lot around
  • Need to determine what the product represents before we can do anything with it.For example, we need to render it differently
  • Joining 4 tables together
  • Each thumbnail requires the same 4 tables joined
  • Each work has multiple products, and each has multiple available options
  • In 4 years, RedBubble has 11 product types.Sourcing suppliers, defining the specifications and getting samples all was time consuming.The application needed to know how to provision, configure and render the product.Genericity ended up getting in the way.
  • Selected options and defaults are on the artworkOptions are modelled in a code (DSL)Rendering a default view requires only the loading of one record
  • Every line of code had a reason for existenceChallenge existing models for every new requirement
  • Refactoring product model

    1. 1. Refactoring Legacy Code Learnings from RedBubble Paul Coia
    2. 2. What is Legacy Code? Untested code Dead code Large classes/methods Spaghetti architecture Models too rigid to meet new requirements
    3. 3. Product model
    4. 4. Available Option Value Product Type Enumeration table Product Artwork OptionOption Value Print Size, Frame Colour, Matte Colour, Frame Style Small, Medium, Large, Red, etc. FramedPrint
    5. 5. Enumeration table Easy to add new products or options  No new columns, just insert rows No code changes Can query against it  “Find all artworks available on red t-shirts” Populate web form elements easily
    6. 6. Domain model is in the DB Product class is a generic type What about domain logic? „Instances‟ need a separate set of tables
    7. 7. Buying a product OptionOption Value Available Option Value Product Artwork Product Type Selected Product Selected Option Value
    8. 8. Preparing the product form framed_print = ProductType.find_by_name(“FramedPrint”) # Get the Framed Print product fp_product = artwork.products.detect do |p| p.product_type == framed_print end aovs = fp_product.available_option_values # Group these into the options options_hash = {} aovs.each do |aov| options_hash[aov.option_value.option] << aov.option_value end # Order the option values, so we have S, M, L, etc. # Populate form elements
    9. 9. SQL view 1. Load products by artwork_id 2. Load available_product_options by product_id 3. Load option_values by option_value_id 4. Load options by option_id
    10. 10. Troublesome requirements Some products have default options T-Shirt default colour and style Options that are not constrained Calendar start month Inter-option constraints Longsleeve Tee colours
    11. 11. Default options Flag on available_product_option Need to enforce one default per option type Render artwork with default options
    12. 12. Rendering default options
    13. 13. Rendering the defaults # Get the default product tee_product = artwork.get_default_product aovs = tee_product.available_option_values defaults = {|aov| aov.default? } # Group these into the options (assuming one per option) options_hash = {} defaults.each do |aov| options_hash[aov.option_value.option] << aov.option_value end # Need to validate that we have all required defaults # and only one per option, etc.
    14. 14. SQL view 1. Load products by product_id 2. Load available_product_options by product_id 3. Load option_values by option_value_id 4. Load options by option_id  Same joins as the first example
    15. 15. Rendering lots of products!
    16. 16. Data volumes 0 50 100 150 200 250 300 350 Works Products Available Option Values Millions
    17. 17. Inter option constraints Roundneck T-shirts can have all colours V-Neck can only have a few How do you model that in the DB? Do you want to?
    18. 18. Original driver Want to add new products weekly Really? Real-world constraints Product preparation takes months Cannot cover all eventualities New products always required new code anyway
    19. 19. Alternative approach Model product types as classes Define options and constraints (DSL) Available products is a list of keys on Artwork Available product options is a hash on the Artwork So is the default options
    20. 20. Product class class TShirtProduct option Style, "mens", "T-Shirt" option Style, "vneck", "V-Neck T-Shirt" option Style, "longsleeve", "Long Sleeve T-Shirt" option Style, "womens", "Girly Fitted T-Shirt" option Style, "mhoodie", "Hoodie" option Size, "xs", "XS" option Size, "small", "Small" option Size, "medium", "Medium” ... AVAILABLE_COLORS = { "mens" => all_colors, "vneck" => [”black”, ”grey”, "navy", "white”] } end
    21. 21. Database model Artwork
    22. 22. What have we gained? An expressive, flexible product model Simpler code, overall Actually easier to add products now And easier to modify existing ones Nearly 25% average speed increase
    23. 23. What have we lost? “Find all red t-shirts” Utilise a search engine “How many large framed prints have we sold?” Reporting via Data Warehouse Online product addition 100s of millions of rows from the DB!