Slide 1

Slide 1 text

I don’t like Rails anymore an exploration of web application architecture design with Ruby Damir Zekić @sidonath

Slide 2

Slide 2 text

Ruby on Rails "Ruby on Rails is an open-source web framework that's optimized for programmer happiness and sustainable productivity."

Slide 3

Slide 3 text

Honeymoon Can last up to 3-6 months Features added with ease Tests run very fast

Slide 4

Slide 4 text

Speed Time When is honeymoon over? Adding a feature
 takes more than a day A feature change
 causes a headache You wait more than
 60s for tests to run or…

Slide 5

Slide 5 text

class CheckoutsController < ApplicationController def create ActiveRecord::Base.transaction do ! cancel_abandoned_checkouts return unless new_checkout_from_params @checkout.save! ! if params[:email_address_set_membership] && params[:email_address_set_membership][:email_address_set_id].present? @checkout.meta( :email_address_set_join_intention, params[:email_address_set_membership][:email_address_set_id] ) end ! # Track checkout creation mixpanel.track_event('Payment', { source: params[:checkout][:source], user: current_user.id, method: params[:checkout][:service] }) ! mixpanel.track_event('Retailer Link', { source: params[:checkout][:source], providerId: @checkout.ownerships.reduce([]){|pids, o| pids << o.product.provider.id }.uniq, itemId: @checkout.item_ids, embedded: params[:checkout][:embedded], url: request.referer ? request.referer.encode('UTF-8', invalid: :replace, undef: :replace) : nil }) ! payment_service = params[:checkout][:service] current_user.meta(:most_recent_payment_service, payment_service) key = (payment_service + '_payment_service_usage_count').to_sym if current_user.meta(key).nil? current_user.meta(key, 1) else current_user.meta(key, current_user.meta(key) + 1 ) end ! if payment_service == 'PayLater' && @checkout.pay_later_allowed? if @checkout.gift && [email protected]_address respond_to do |format| msg = "Provide the full billing address." format.html do flash[:error] = Notification.new(msg, "You tried to purchase a gift using 'pay later' option, but no billing address was specified." + "
Contact us for more info: [email protected]") redirect_to checkout_path and return end format.json { render json: {message: msg}, status: :unprocessable_entity and return} end end ! @checkout.complete_without_payment! if current_user.existant_ea PurchaseMailer.delay.purchase_confirmation(current_user.existant_ea, @checkout) end ! respond_to_successfully_completed_checkout(@checkout, payment_service) and return end ! collect_mailing = case params[:checkout][:collect_mailing] when 'true' then true when 'false' then false else false end ! allow_guest_checkout = case params[:checkout][:pp_mode] when 'cc' then true when 'account' then false else true end ! invoice_items = nil ! invoice_items = @checkout.ownerships.inject([]) do |a, e| i = e.product_item if i.custom_invoice_items a += i.custom_invoice_items else a << { name: i.product.description, amount: i.price * 100, description: i.description } end a end if invoice_items.map{|x| x[:amount] }.sum != @checkout.amount * 100 invoice_items = nil end ! begin payment_creation_options = { user_id: current_user.id, checkout_id: @checkout.id, amount: @checkout.amount, service: payment_service, collect_mailing: collect_mailing, allow_guest_checkout: allow_guest_checkout } payment_creation_options[:invoice_items] = invoice_items if invoice_items payment_creation_options[:pp_page_style] = params[:checkout][:pp_page_style] if params[:checkout][:pp_page_style] ! payment_creation_options[:amazon_cobranding_url] = params[:checkout][:amazon_cobranding_url] if params[:checkout][:amazon_cobranding_url] ! @payment = Payment.create( payment_creation_options ) rescue ActiveResource::ResourceInvalid, StandardError notify_airbrake($!) flash[:error] = Notification.new(UX::PAYMENTS[:creation_failed], UX::PAYMENTS[:help], false) respond_to do |format| format.html { redirect_to checkout_path and return } format.json { render json: {message: UX::PAYMENTS[:creation_failed]}, status: 422 and return } end else @checkout.update_attributes({ payment_id: @payment.id }, without_protection: true) ! respond_to do |format| format.html { redirect_to( @payment.get(:authorization_url)['value'] ) and return } format.json { render json: { payment_id: @payment.id } and return } end end ! end # ActiveRecord::Base.transaction end end Your code looks like this (this is a single
 controller action)

Slide 6

Slide 6 text

The Issues Technical Social

Slide 7

Slide 7 text

The Technical Issues It's hard to write isolated tests Refactoring controllers Concerns are not the answer

Slide 8

Slide 8 text

SOLID

Slide 9

Slide 9 text

SOLID Single Responsibility Principle Open/Closed Principle Liskov Substitution Principle Interface Segregation Principle Dependancy Inversion Principle

Slide 10

Slide 10 text

Extra Law of Demeter Ask, don't tell

Slide 11

Slide 11 text

No content

Slide 12

Slide 12 text

The Technical Issues an ActiveRecord model many responsibilities no interface segregation an ActionController controller many responsibilities can't be extended for new features

Slide 13

Slide 13 text

Speed Time When is honeymoon over?

Slide 14

Slide 14 text

Rich Hickey: Simple Made Easy http://www.infoq.com/presentations/Simple-Made-Easy

Slide 15

Slide 15 text

Ruby on Rails "Ruby on Rails is an open-source web framework that's optimized for programmer happiness and sustainable productivity."

Slide 16

Slide 16 text

The Social Issues Stigma against "enterprisy" and dogma No competition

Slide 17

Slide 17 text

How to Manage? Read about design patterns Refactor using design patterns Design domain model thinking
 about interactions first

Slide 18

Slide 18 text

Alternatives to Rails For the brave: go without a framework
 and focus on designing your application first Adam Hawkins" Joy of Design Application First, Frameworks Second

Slide 19

Slide 19 text

Alternatives to Rails Lotus http://lotusrb.org/

Slide 20

Slide 20 text

Lotus Not yet released Promising competing full-stack framework Different approach

Slide 21

Slide 21 text

Lotus Components Lotus::Router Lotus::Controller Lotus::View Lotus::Model

Slide 22

Slide 22 text

Does Lotus Deliver?

Slide 23

Slide 23 text

➜ lotus-controller git:(master) bundle exec rake Run options: --seed 24213 ! # Running: ! ................................................................. ................................................................. .............. ! Finished in 0.063052s, 2283.8292 runs/s, 4187.0202 assertions/s. ! 144 runs, 264 assertions, 0 failures, 0 errors, 0

Slide 24

Slide 24 text

➜ lotus-model git:(master) bundle exec rake Run options: --seed 33698 ! # Running: ! ................................................................. ................................................................. ................................................................. ................................................................. ............ ! Finished in 0.633141s, 429.6041 runs/s, 546.4817 assertions/s. ! 272 runs, 346 assertions, 0 failures, 0 errors, 0 skips ➜ lotus-model git:(master)

Slide 25

Slide 25 text

class RoomsController action 'Create' do expose :form ! def initialize(repository: RoomRepository, router: Application.router) @repository = repository @router = router end ! def call(params) @form = FormFactory.new_room room = @form.populate(params.fetch(:room), self) @repository.persist(room) redirect_to @router.path(:rooms) end ! def form_invalid throw 422 end end end Room Reservation (a Lotus greenfield app) https://github.com/sidonath/room-reservation

Slide 26

Slide 26 text

Recommended Materials Matt Wynne
 Hexagonal Rails (a talk)" Erich Gamma, Richard Helm, Ralph Johnson, John Vlissides
 Design Patterns: Elements of Reusable Object-Oriented Software" Elisabeth Freeman, Eric Freeman, Bert Bates, Kathy Sierra, Elisabeth Robson
 Head First Design Patterns Martin Fowler
 Patterns of Enterprise Application Architecture" Avdi Grimm
 Objects on Rails (free ebook) Sandi Metz
 Practical Object-Oriented Design in Ruby

Slide 27

Slide 27 text

Questions? Damir Zekić @sidonath [email protected]