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

Architecture of hanami applications

Architecture of hanami applications

The general part of any web application is business logic. Unforchanotly, it's really hard to find a framework with specific rules and explanations how to work with it. In hanami, we care about long-term maintenance that's why it's really important to us how to work with business logic.

In my talk, I'll share my ideas how to store and work with business logic in hanami apps. We will talk about hanami, dry and some architecture ideas, like event sourcing. This talk will be interesting for any developers. If you work with other frameworks you can take these ideas and my it to your project.

Anton Davydov

May 31, 2018
Tweet

More Decks by Anton Davydov

Other Decks in Programming

Transcript

  1. class InstitutionsController < ApplicationController load_and_authorize_resource :only => [:destroy,:edit,:new,:create,:update] before_filter :authenticate_user!,

    :except => [:student_registration, :show, :validate_registration_pin, :result, :admission, :buy_registration_pin,:paygate_callback_failure, :paygate_cancel, :paygate_pending, :paygate_callback_success, :pi nsaction_info_print] before_filter :find_institution, :except => [:show,:index, :new, :create, :semesters_for_institute_type, :start_end_date_for_assessment_period, :courses_for_batch, :paygate_callback_failure, :paygate_cancel, :paygate_pending, :paygate_cal _success, :pin_transaction_info_print] before_filter :add_bread_crumb,:except => [:show] def paygate_callback_success @pay_gate_config = YAML::load(File.open("#{Rails.root}/config/pay_gate_config.yml"))[Rails.env] @payment = TransactionRecord.find_by_order_number(params[:OrderID]) uri = URI("https://fidelitypaygate.fidelitybankplc.com/cipg/MerchantServices/UpayTransactionStatus.ashx") parameters = {:MERCHANT_ID => "#{@pay_gate_config['merchant_id']}", :ORDER_ID => "#{@payment.order_number}"} uri.query = URI.encode_www_form(parameters) result =open(uri).read result_hash = Hash.from_xml(result) record_payment_details(result_hash) if result_hash["CIPG"]["StatusCode"] == PaymentRecord::PAYMENT_SUCCESS_CODE if @payment.transactionable_type.eql?("PaymentRecord") redirect_to institution_fees_path(@payment.transactionable_type.fee.institution), :notice => "Payment transaction has been #{result_hash['CIPG']['Status']}" elsif @payment.transactionable_type.eql?("PinBuyerInfo") unless @payment.transactionable.pin_id.present? @registration = @payment.transactionable.registration @valid_registration_pin_groups = @registration.valid_registration_pin_groups @online_valid_registration_pin_groups = @valid_registration_pin_groups.where(:pin_available_type => 'Online') @offline_valid_registration_pin_groups = @valid_registration_pin_groups.where(:pin_available_type => 'Offline') @available_pin = nil @online_valid_registration_pin_groups.each do |vpg| if vpg.available_pins.present? @available_pin = vpg.available_pins.first break else next end end if !@available_pin.present? @offline_valid_registration_pin_groups.each do |vpg| if vpg.available_pins.present? @available_pin = vpg.available_pins.first break else next end end end if @available_pin.present? @payment.transactionable.pin_id = @available_pin.id @payment.transactionable.save @available_pin.is_available = false @available_pin.save @available_pin.reload if @available_pin.pin_group.pin_available_type == 'Offline' @message = "Your Order Number is #{@payment.order_number}. Please Print Transaction report to claim you PIN from Institution." else @assigned_pin = @available_pin.number @message = "Your PIN is #{@assigned_pin}. Please keep this PIN secret." end else @message = "No pin available for #{@registration.name}. Your order number is #{@payment.order_number}.Please contact with institution to get your money back if you have paid." end else pin = Pin.find @payment.transactionable.pin_id if(pin.present? && pin.pin_group.pin_available_type == 'Offline') @message = "Your Order Number is #{@payment.order_number}. Please Print Transaction report to claim you PIN from Institution." else @available_pin = Pin.find @payment.transactionable.pin_id @assigned_pin = @available_pin.number @message = "Your PIN is #{ @assigned_pin}. Please keep this PIN secret."
  2. class InstitutionsController < ApplicationController load_and_authorize_resource :only => [:destroy,:edit,:new,:create,:update] before_filter :authenticate_user!,

    :except => [:student_registration, :show, :validate_registration_pin, :result, :admission, :buy_registration_pin,:paygate_callback_failure, :paygate_cancel, :paygate_pending, :paygate_callback_success, :pi nsaction_info_print] before_filter :find_institution, :except => [:show,:index, :new, :create, :semesters_for_institute_type, :start_end_date_for_assessment_period, :courses_for_batch, :paygate_callback_failure, :paygate_cancel, :paygate_pending, :paygate_cal _success, :pin_transaction_info_print] before_filter :add_bread_crumb,:except => [:show] def paygate_callback_success @pay_gate_config = YAML::load(File.open("#{Rails.root}/config/pay_gate_config.yml"))[Rails.env] @payment = TransactionRecord.find_by_order_number(params[:OrderID]) uri = URI("https://fidelitypaygate.fidelitybankplc.com/cipg/MerchantServices/UpayTransactionStatus.ashx") parameters = {:MERCHANT_ID => "#{@pay_gate_config['merchant_id']}", :ORDER_ID => "#{@payment.order_number}"} uri.query = URI.encode_www_form(parameters) result =open(uri).read result_hash = Hash.from_xml(result) record_payment_details(result_hash) if result_hash["CIPG"]["StatusCode"] == PaymentRecord::PAYMENT_SUCCESS_CODE if @payment.transactionable_type.eql?("PaymentRecord") redirect_to institution_fees_path(@payment.transactionable_type.fee.institution), :notice => "Payment transaction has been #{result_hash['CIPG']['Status']}" elsif @payment.transactionable_type.eql?("PinBuyerInfo") unless @payment.transactionable.pin_id.present? @registration = @payment.transactionable.registration @valid_registration_pin_groups = @registration.valid_registration_pin_groups @online_valid_registration_pin_groups = @valid_registration_pin_groups.where(:pin_available_type => 'Online') @offline_valid_registration_pin_groups = @valid_registration_pin_groups.where(:pin_available_type => 'Offline') @available_pin = nil @online_valid_registration_pin_groups.each do |vpg| if vpg.available_pins.present? @available_pin = vpg.available_pins.first break else next end end if !@available_pin.present? @offline_valid_registration_pin_groups.each do |vpg| if vpg.available_pins.present? @available_pin = vpg.available_pins.first break else next end end end if @available_pin.present? @payment.transactionable.pin_id = @available_pin.id @payment.transactionable.save @available_pin.is_available = false @available_pin.save @available_pin.reload if @available_pin.pin_group.pin_available_type == 'Offline' @message = "Your Order Number is #{@payment.order_number}. Please Print Transaction report to claim you PIN from Institution." else @assigned_pin = @available_pin.number @message = "Your PIN is #{@assigned_pin}. Please keep this PIN secret." end else @message = "No pin available for #{@registration.name}. Your order number is #{@payment.order_number}.Please contact with institution to get your money back if you have paid." end else pin = Pin.find @payment.transactionable.pin_id if(pin.present? && pin.pin_group.pin_available_type == 'Offline') @message = "Your Order Number is #{@payment.order_number}. Please Print Transaction report to claim you PIN from Institution." else @available_pin = Pin.find @payment.transactionable.pin_id @assigned_pin = @available_pin.number
  3. class Interactor def call(payload) data = yield extract!(payload) entity =

    yield validate!(data) create!(entity) end # … end
  4. def initialize(object: FuncObject.new) @object = object end def call(params) result

    = @object.call(params) if result.successful? flash[:info] = INFO_MESSAGE redirect_to routes.tasks_path else self.body = … end end
  5. def initialize(object: FuncObject.new) @object = object end def call(params) result

    = @object.call(params) if result.successful? flash[:info] = INFO_MESSAGE redirect_to routes.tasks_path else self.body = … end end Dependency Injection ❤
  6. let(:action) { Create.new(object: SuccessObject.new) } it { expect(action.call(params).to eq ...

    }
 
 let(:action) { Create.new(object: FailedObject.new) } it { expect(action.call(params).to eq ... }
  7. let(:other_object) { -> (_) { nil } }
 let(:object) {

    Create.new(object: other_object) } it { expect(action.call(params).to eq ... }
  8. include Import[‘interactors.create_user']
 
 def call(params) result = create_user.call(params) if result[:successful]

    flash[:info] = INFO_MESSAGE redirect_to routes.tasks_path else self.body = … end end
  9. include Import[‘interactors.create_user']
 
 def call(params) result = create_user.call(params) if result[:successful]

    flash[:info] = INFO_MESSAGE redirect_to routes.tasks_path else self.body = … end end Dependency Injection ❤
  10. include Import[‘interactors.create_user']
 
 def call(params) result = create_user.call(params) if result[:successful]

    flash[:info] = INFO_MESSAGE redirect_to routes.tasks_path else self.body = … end end
  11. App App App lib One server instance App App App

    lib App App App lib App App App lib App App App lib App App App lib App App App lib App App App lib
  12. DDD

  13. – Martin Fowler “You can use a different model to

    update information than the model you use to read information”
  14. Pros • Each service isolated • All processing is in

    the background • Easy to add instances for service • Can be written on any language and you can call it from any app • Persistance
  15. Cons • Not a silver bullet • Hard to understand

    the whole chain of events • Complicated • Another architecture type
 with different DB structure • Not popular in ruby