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

Taming The Rails Monolith Mammoth

Yatish Mehta
September 06, 2020
28

Taming The Rails Monolith Mammoth

Yatish Mehta

September 06, 2020
Tweet

Transcript

  1. if ? puts "ERB is Awesome!!" else puts "HAML is

    Awesome!!" end # => ERB is Awesome!! # HAML is Awesome!!
  2. if puts "ERB is Awesome!!" puts "ERB is Awesome!!" else

    puts "HAML is Awesome!!" end # => ERB is Awesome!! # HAML is Awesome!!
  3. def create @issue = Issue.new(issue_params) respond_to do |format| if @issue.save

    format.html { redirect_to @issue, notice: ‘Success.’ } format.json { render action: 'show', status: :created, location: @issue } else format.html { render action: 'new' } format.json { render json: @issue.errors, status: :unprocessable_entity } end end end Expectation
  4. def bulk_update @issues.sort! @copy = params[:copy].present? attributes = parse_params_for_bulk_issue_attributes(params) unsaved_issues

    = [] saved_issues = [] if @copy && params[:copy_subtasks].present? # Descendant issues will be copied with the parent task # Don't copy them twice @issues.reject! {|issue| @issues.detect {|other| issue.is_descendant_of?(other)}} end @issues.each do |orig_issue| orig_issue.reload if @copy issue = orig_issue.copy({}, :attachments => params[:copy_attachments].present?, :subtasks => params[:copy_subtasks].present? ) else issue = orig_issue end journal = issue.init_journal(User.current, params[:notes]) issue.safe_attributes = attributes call_hook(:controller_issues_bulk_edit_before_save, { :params => params, :issue => issue }) if issue.save saved_issues << issue else unsaved_issues << orig_issue end end # ... render :action => 'bulk_edit' end end Reality
  5. aasm_state :approved, :enter => Proc.new { |a| begin a.update_approved_by(User.current_user) a.approvable.generate_approval_list(:on

    => 'approve') unless a.skip_generate_approval_list || a.is_ultimate? rescue Exception => err a.update_attribute(:approved_by, nil) raise err end }, :after_enter => Proc.new { |a| a.update_approval_date a.update_act_as a.create_delegate_for_approval_group a.approvable.after_approval_approved(a) a.approve_approvable if a.last? }
  6. class ExpenseReport::Approve def initialize(document:, context:) @document = document @context =

    parse_context(context) end def call #.. end end Plain Old Ruby Object(PORO)
  7. Defines an application’s boundary with a layer of services that

    establishes a set of available operations and coordinates the application’s response in each operation — Martin Fowler
  8. In DDD Eric Evans uses the term Service Object to

    refer to objects that represent processes (as opposed to Entities and Values). DDD Service Objects are often useful to factor out behavior that would otherwise bloat Entities, it's also a useful step to patterns like Strategy and Command. — Martin Fowler
  9. module Service class ApprovalChain::Create include Virtus.model include Service::Base attribute :document,

    ::Approvable attribute :template, String attribute :with_proxy, Boolean, :default => false attribute :rules, ::RuleCollection # Custom Array class CustomFieldRecordNotFound < StandardError; end def call ensure_data_source_provided! approval_chain = create_from_document(document) apply_custom_filters(approval_chain) #... end private def ensure_data_source_provided! # Add logic here end # .. other methods end end
  10. module Service class ApprovalChain::Create include Virtus.model include Service::Base attribute :document,

    ::Approvable attribute :template, String attribute :with_proxy, Boolean, :default => false attribute :rules, ::RuleCollection # Custom Array class CustomFieldRecordNotFound < StandardError; end def call ensure_data_source_provided! approval_chain = create_from_document(document) apply_custom_filters(approval_chain) #... end private def ensure_data_source_provided! # Add logic here end # .. other methods end end
  11. module Service class ApprovalChain::Create include Virtus.model include Service::Base attribute :document,

    ::Approvable attribute :template, String attribute :with_proxy, Boolean, :default => false attribute :rules, ::RuleCollection # Custom Array class CustomFieldRecordNotFound < StandardError; end def call ensure_data_source_provided! approval_chain = create_from_document(document) apply_custom_filters(approval_chain) #... end private def ensure_data_source_provided! # Add logic here end # .. other methods end end
  12. module Service class ApprovalChain::Create include Virtus.model include Service::Base attribute :document,

    ::Approvable attribute :template, String attribute :with_proxy, Boolean, :default => false attribute :rules, ::RuleCollection # Custom Array class CustomFieldRecordNotFound < StandardError; end def call ensure_data_source_provided! approval_chain = create_from_document(document) apply_custom_filters(approval_chain) #... end private def ensure_data_source_provided! # Add logic here end # .. other methods end end
  13. module Service class ApprovalChain::Create include Virtus.model include Service::Base attribute :document,

    ::Approvable attribute :template, String attribute :with_proxy, Boolean, :default => false attribute :rules, ::RuleCollection # Custom Array class CustomFieldRecordNotFound < StandardError; end def call ensure_data_source_provided! approval_chain = create_from_document(document) apply_custom_filters(approval_chain) #... end private def ensure_data_source_provided! # Add logic here end # .. other methods end end
  14. # /app/services/service/base.rb module Service::Base def self.call(*args) wrap_in_transaction_if_requested do new(*args).call end

    end #... end Service::ApprovalChain::Create.call({ document: document, with_proxy: true, ... })
  15. app !"" services #"" service $ #"" approval_list $ #""

    supplier $ #"" call_out $ #"" contract $ #"" expense_report $ #"" invoices $ #"" pay $ #"" sign_provider $ !"" base.rb !"" service.rb File Structure
  16. Some actions in a system warrant a Service Object to

    encapsulate their operation. I reach for Service Objects when an action meets one or more of these criteria: • The action is complex (e.g. closing the books at the end of an accounting period) • The action reaches across multiple models (e.g. an e-commerce purchase using Order, CreditCard and Customer objects) • The action interacts with an external service (e.g. posting to social networks) • The action is not a core concern of the underlying model (e.g. sweeping up outdated data after a certain time period). • There are multiple ways of performing the action (e.g. authenticating with an access token or password). This is the Gang of Four Strategy pattern. — Bryan Helmkamp