Un'interessante scelta / discussione di design è comparsa nella revisione del codice e vorrei capire di più sulle soluzioni proposte. La recensione originale include il ri-factoring di un pezzo di codice disordinato che non posso condividere, ma penso che concentrarsi sui problemi con il codice originale stia distorcendo le opinioni su come dovrebbe essere una buona soluzione.
Abbiamo un'applicazione web Ruby / Sinatra con molte classi ORM Sequel, oltre a moduli separati che eseguono compiti. Alcune delle attività sono lente e possono essere eseguite in modo asincrono, quindi le scarichiamo dal server Web tramite un meccanismo di lavoro. Questo utilizza una coda beanstalkd per passare i parametri dell'attività dal processo Web al processo di lavoro.
È la progettazione API del programma di pianificazione del lavoro che è in discussione.
Ad esempio, potremmo avere metodi come segue:
module Foo
def self.slow_generic_task params
end
end
class Bar < Sequel::Model
def slow_instance_method params
end
end
I due approcci per un job scheduler che stiamo considerando sono:
1. L'interfaccia di lavoro richiama solo i metodi di classe o modulo
Questo semplifica la firma del metodo e la serializzazione dei lavori nella coda. L'interfaccia di lavoro ha questo aspetto:
Job.schedule( Foo, :slow_generic_task, params )
Job.schedule( Bar, :slow_instance_method, id, params )
e la serializzazione dei lavori in coda è semplice. Tuttavia, per ogni destinazione è necessario un metodo di istanza che aggiungiamo un metodo adattatore:
class Bar < Sequel::Model
def self.slow_instance_method id, params
self[id].slow_instance_method params
end
end
I dubbi su questo approccio sono:
-
È necessario aggiungere questo metodo di classe "shim" come parte implicita dell'API (quindi non ha semplificato l'API tanto quanto spostato parte della complessità dalla sintassi e dalla progettazione).
-
Non sembra esserci una buona ragione per avere un metodo "istanziato da id e fare qualcosa con l'istanza" su una classe diversa da quella di soddisfare il contratto implicito dalla progettazione del Job Scheduler.
2. L'interfaccia di lavoro viene estesa per disambiguare le chiamate singleton dai metodi di istanza ORM
Ciò implica avere un'interfaccia per il sistema di lavoro che consente la disambiguazione. Per es.
Job.schedule_singleton( Foo, :slow_generic_task, params )
Job.schedule_instance( Bar, :slow_instance_method, id, params )
oppure potremmo accettare un'istanza di Bar e disambiguare all'interno della funzione di pianificazione:
Job.schedule( Foo, :slow_generic_task, params )
Job.schedule( bar, :slow_instance_method, params )
(è così che il codice originale è stato rielaborato) In entrambi i casi, la serializzazione dei lavori in coda e la loro gestione nel processo di lavoro che legge la coda è diventata più complessa.
La cosa positiva di questo approccio è che non sono richieste modifiche a Bar
. Qualsiasi metodo di istanza di oggetto basato su ORM che vogliamo rinviare per separare il processo di lavoro può essere gestito da questa interfaccia così com'è.
I dubbi su questo approccio sono:
-
Richiede logica aggiuntiva o metodi aggiuntivi per disambiguare i tipi di oggetto passati allo scheduler.
-
Richiede una serializzazione più complessa nella coda dei lavori.
-
Richiede logica aggiuntiva all'altra estremità all'interno del codice worker per decidere se costruire l'oggetto di destinazione come ORM o chiamare direttamente come metodo singleton.
Naturalmente ho un'opinione, ma ho cercato di presentarlo in modo neutro.
La mia domanda / e: Qualche preoccupazione o vantaggio degli approcci di cui sopra - o qualsiasi altra analisi - abbastanza seri da prendere una decisione chiara per un design "migliore"? Ci sono alternative migliori a cui siamo stati accecati concentrandoci sulle differenze tra questi due approcci?