rebuilding large payments systems on rails

rebuilding large payments systems on rails

Payments applications typically require a strong audit trail, very predictable failure behavior and strong transactional integrity. In Ruby/Rails, ActiveRecord allows any part of the code to modify anything in the database, failures are often silently ignored, and database transactions are hidden for convenience by default. In this talk I'll explore how to solve those problems and use RoR to build a large scale payments system.


Michel Weksler

April 23, 2015


  1. rebuilding a large scale payments system on Rails RailsConf 2015

    Michel Weksler payments engineering, airbnb
  2. Why this talk? Audit Trails Failure Behavior Structural Coupling Testability

  3. None
  4. None
  5. None
  6. None
  7. None
  8. None
  9. Audit Trails What changed? Who made the change? When did

    they make the change? On behalf of whom? Why did the make the change?
  10. class PlainOldRubyObject; end (PlainOldRubyObject.methods - Object.methods).count # => 0 class

    Account < ActiveRecord::Base; end (Account.methods - Object.methods).count # => 396 Reduce Active Record Surface Area
  11. protected access class ProtectedAccess::Base def self.inherited(subclass) table_name = protected_access_model

    = do insert_only self.table_name = table_name end ... end end
  12. None
  13. Predictable Failure Behavior Dead programs don’t lie (Andy Hunt, Dave

    Thomas, “The Pragmatic Programmer”)
  14. Parameter Validation if options[:txn] && options[:txn].kind_of? Transaction fail Error, ":txn

    is required and should be a Transaction" end if options[:buyer] && options[:buyer].kind_of? User fail Error, ":buyer is required and should be a User" end if options[:status] unless ALL_STATUSES.include?(options[:status]) fail Error, "Status #{options[:status]} must be one of #{ALL_STATUSES}." end
  15. def validate(options) validate_arg(options) do |o| o.validate :txn, is_a: Transaction, required:

    true o.validate :buyer, is_a: User, required: true o.validate :status, is_one_of: ALL_STATUSES o.validate :confirmation_code end end Parameter Validation
  16. None
  17. “Law of Demeter” An object O, and a methods m,

    should only use: • O itself • m's parameters • Any objects created/instantiated within m • O's direct component objects • A global variable, accessible by O, in the scope of m
  18. def apply_coupon(coupon) = coupon self.paid_by = coupon.department.try(:name) end Law

    of Demeter - when things go wrong
  19. def coupon_department(coupon) if coupon && coupon.respond_to?(:department) return coupon.department end '(unknown)'

    end def apply_coupon(coupon) = coupon self.paid_by = coupon_department(coupon) end Law of Demeter - a possible solution
  20. def slug(string) string.strip.downcase.tr_s('^[a-z0-9]', '-') end (“Demeter: It’s not just a

    good idea. It’s the law.”, Avdi Grimm) Law of Demeter - chaining is fine
  21. None
  22. Testability “Can you really guarantee that this 3000 line model

    that touches our transaction table does not contain off-by-one-cent errors if it doesn’t have really good unit tests?”
  23. Service Objects class CompleteTransaction < ServiceBase def initialize(opts = {})

    validate(opts) @txn = opts[:txn] @buyer = opts[:buyer] @status = opts[:status] end def validate(opts) # use parameter validation to strongly check expected parameter values end def perform response = Net::HTTP.post_form.... # inspect response and return accordingly end end
  24. Style and Complexity Filters if params[:user].messages.last.send_by < params[:user].messages.last.send else

    job.retry_at(params[:user].messages.last.send_by) end
  25. Style and Complexity Filters email = params[:user].messages.last if email.send_by >= return job.retry_at(email.send_by) end email.send
  26. None
  27. Don’t Repeat Yourself Every piece of knowledge must have a

    single unambiguous authoritative representation within a system. (Andy Hunt, Dave Thomas, “The Pragmatic Programmer”)
  28. Q/A