Upgrade to Pro — share decks privately, control downloads, hide ads and more …

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
Tweet

Other Decks in Technology

Transcript

  1. Audit Trails What changed? Who made the change? When did

    they make the change? On behalf of whom? Why did the make the change?
  2. 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
  3. protected access class ProtectedAccess::Base def self.inherited(subclass) table_name = subclass.name.tableize protected_access_model

    = Class.new(ActiveRecord::Base) do insert_only self.table_name = table_name end ... end end
  4. 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
  5. 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
  6. “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
  7. def coupon_department(coupon) if coupon && coupon.respond_to?(:department) return coupon.department end '(unknown)'

    end def apply_coupon(coupon) self.coupon = coupon self.paid_by = coupon_department(coupon) end Law of Demeter - a possible solution
  8. 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
  9. 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?”
  10. 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
  11. Style and Complexity Filters email = params[:user].messages.last if email.send_by >=

    Time.now return job.retry_at(email.send_by) end email.send
  12. 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”)