codice di lavoro grasso o slim in Rails?

1

Ho un'applicazione Rails 5 con Resque come back-end del lavoro in background. Un tipico lavoro fa un bel po 'di cose, principalmente raccoglie materiale da servizi HTTP, elabora e aggrega alcuni dei dati scaricati e li memorizza nel database locale tramite ActiveRecord.

La domanda è, dove dovrebbe essere scritto?

Se è tutto nel codice del lavoro, quel codice sarà lungo, molto procedurale, e il mio strumento di qualità del codice (Rubocop) mi dirà che sono troppe righe di codice e troppo complesse in molti modi diversi. Ma è davvero un problema in questo caso? O è una specie di caratteristica intrinseca di un lavoro? Sento che probabilmente non dovrebbe essere.

L'altra opzione sarebbe di metterne un po 'nei modelli. Alcuni dei miei modelli potevano sapere come e da dove possono essere scaricati, come devono essere elaborati i dati grezzi, ecc. E quindi possono semplicemente salvarsi in AR. In questo modo il lavoro avrebbe per lo più solo orchestrato la raccolta dei dati, la roba effettiva sarebbe nei modelli AR. Ma appartiene qui? Il modo Rails è un controller sottile, ma i job funzionano come controller in questo senso? Facendolo in questo modo renderebbe il lavoro molto più difficile da leggere, ad esempio.

    
posta Gabor Lengyel 29.03.2017 - 12:26
fonte

1 risposta

1

Poiché questi lavori in background hanno un sacco di coordinazione per fare ciò che non si adatta alle classi di modelli ActiveRecord tradizionali, puoi suddividerli in blocchi sempre più piccoli della logica correlata fino a ottenere blocchi di codice gestibili.

Un esempio di suddividere questo in basso sarebbe:

  • Job: la cosa di alto livello che stai cercando di realizzare
  • Attività: frammenti di lavoro più piccoli che richiedono ancora un coordinamento, ma lo fanno in modo più mirato
  • Servizi: classi che effettuano chiamate al servizio web, ecc.
  • Classi di entità: le tue classi ActiveRecord

Un esempio di suddivisione in giù sarebbe questo (ingenuamente) implementato ordine di elaborazione del lavoro in background per un'applicazione di e-commerce.

Il lavoro (OrderProcessorJob) ottiene gli ordini in sospeso, invia il pagamento, spedisce l'inventario e completa l'ordine, salvandolo nel database. Comprende chiamate al servizio web, chiamate al database e un'attività (ProcessOrderTask) che ha la conoscenza dell'elaborazione di un singolo ordine.

La classe OrderProcessorJob :

class OrderProcessorJob
  def perform
    pending_orders.each do |pending_order|
      order_to_process = Order.find pending_order.order_id

      begin
        if order_to_process
          process_order_task.process order_to_process pending_order
        else
          # Log that we got an order we couldn't process
        end
      rescue Exception => e1
        # Log error
      end
    end
  rescue Exception => e2
    # Log error, mark this job as "failed"
  end

private

  def order_service
    @order_service ||= OrderService.new
  end

  def process_order_task
    @process_order_task ||= ProcessOrderTask.new
  end

  def pending_orders
    order_service.pending_orders
  end

end

Il OrderProcessorJob ha le seguenti responsabilità:

  1. Ottieni ordini in sospeso da un servizio web

  2. Recupera ogni Order dal database e passa l'ordine in sospeso e l'ordine da elaborare in ProcessOrderTask

  3. Gestione degli errori di alto livello che garantisce che il lavoro in background non si arresti e può continuare a elaborare gli ordini in caso di problemi con singoli ordini

La classe ProcessOrderTask :

class ProcessOrderTask
  def process(order_to_process, pending_order)
    billing_service.confirm_payment! order_to_process
    inventory_service.ship_order! order_to_process
    order_to_process.complete!
    order_to_process.save!
  rescue BillingException => billing_error
    # Log error, mark order as failed due to billing reasons
  rescue InventoryException => inventory_error
    # Log error, mark order as failed due to inventory issues
  end

private

  def billing_service
    @billing_service ||= BillingService.new
  end

  def inventory_service
    @inventory_service ||= InventoryService.new
  end

end

Le responsabilità della classe ProcessOrderTask includono:

  1. Chiamare il servizio web di fatturazione per garantire che il pagamento sia confermato
  2. Chiamare il servizio di inventario per aggiornare l'inventario per riflettere che l'ordine è completo
  3. Contrassegna l'ordine come completato e salvalo nel database
  4. Gestione degli errori in caso di problemi di fatturazione o di inventario

Il grande vantaggio di questo approccio consiste nel mettere la maggior parte della logica in altre classi, che potrebbero essere riutilizzate al di fuori dell'ambito di un lavoro in background. Quando ogni classe ha una responsabilità più focalizzata, è più facile testare, perché c'è meno codice da testare e il numero di dipendenze per ogni classe è ridotto.

    
risposta data 29.03.2017 - 17:45
fonte