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à:
-
Ottieni ordini in sospeso da un servizio web
-
Recupera ogni Order
dal database e passa l'ordine in sospeso e l'ordine da elaborare in ProcessOrderTask
-
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:
- Chiamare il servizio web di fatturazione per garantire che il pagamento sia confermato
- Chiamare il servizio di inventario per aggiornare l'inventario per riflettere che l'ordine è completo
- Contrassegna l'ordine come completato e salvalo nel database
- 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.