Ridurre l'accoppiamento in una serie di attività

0

Sto lavorando su qualche codice in questo momento che riguarda l'elaborazione delle richieste degli utenti. Ogni richiesta richiede un'approvazione. Quando viene effettuata una richiesta, vengono creati uno o più record sul database che registra ciò che la richiesta comporta. Quindi viene creato un ticket che viene inserito nella coda di lavoro dell'approvatore.

Per una richiesta tipica, l'approvazione comporta i seguenti passaggi:

  1. La richiesta viene elaborata (di solito con il risultato che una tabella viene aggiornata)
  2. Le modifiche vengono registrate in una tabella della cronologia (controllo della registrazione).
  3. La richiesta è contrassegnata come approvata dal responsabile dell'approvazione (controllo di approvazione).
  4. Il ticket viene rimosso dalla coda di lavoro.
  5. Al richiedente originale viene inviata un'email per comunicare che la richiesta è stata approvata.

Abbiamo una singola classe che avvia in sequenza questi passaggi all'interno di una transazione ( link ). Per la maggior parte, la sequenza non ha molta importanza e l'email non è essenziale.

Il problema è che ognuna di queste classi ha almeno 5+ dipendenze (in realtà, è più vicina a 10+). Sono stordito su come ridurre il numero di dipendenze quando si tratta di un flusso di lavoro come questo.

Ho pensato di raggruppare alcuni di questi elementi insieme, ma non c'è alcun raggruppamento logico. Comunque, non vedo un granché nel creare un livello non necessario del genere.

Ho anche pensato di fare una sorta di modello di osservatore (eventi .NET). Una classe più in alto potrebbe ascoltare l'evento, istanziare le classi necessarie e dargli il via. Tuttavia, quella "classe superiore" dipenderebbe nuovamente da tutte le classi, quindi tornerei subito nella stessa situazione.

Quindi, l'unico modo in cui sono stato in grado di pensare di ridurre il numero di dipendenze è quello di creare un raggruppamento artificiale di classi o semplicemente rendere il problema di livello superiore. Non sembrano ideali e mi chiedo se mi manca qualcosa. Se aiuta, sto usando Ninject con ASP.NET MVC. Ho accesso a un contenitore IOC ( DependencyResolver ). Mi chiedo se la soluzione sia una sorta di combinazione tra l'uso di listener / eventi e un contenitore IOC.

    
posta Travis Parks 28.01.2014 - 04:11
fonte

1 risposta

3

Se chiami questi passaggi , allora chiaramente l'ordine è pertinente in una certa misura. L'ordine esatto potrebbe non avere importanza, ma ci sarà una certa quantità di "X e Y accadono prima di Z". Ad esempio, probabilmente non vuoi inviare l'email dicendo che la richiesta è stata approvata, prima che sia stata effettivamente approvata. Allo stesso modo, probabilmente la richiesta non può essere approvata prima che venga effettivamente elaborata / aggiunta alla coda / etc.

Gli script di transazione (o "gestori del flusso di lavoro") sono fondamentalmente codice procedurale. Il numero di dipendenze non è in realtà il problema the , è solo un proxy per il problema. Il problema effettivo è il numero di responsabilità che lo script ha. E nel tuo caso, il numero di responsabilità aumenta con ogni nuovo passaggio che aggiungi. E questo, ovviamente, va contro il principio di singola responsabilità .

L'antitesi più comune al codice procedurale è il codice basato sugli eventi. Dici questo:

A class higher up could listen for the event, instantiate the needed classes and kick them off. However, that "higher-up class" would then be dependent on all of the classes again, so I'd be right back in the same situation.

Ma manca il punto. Stai ancora provando ad avere una classe una responsabile per il passaggio ogni . Invece, si desidera avere una classe per il passaggio ogni - ascolta per qualsiasi evento precursore che si suppone inneschi quel passaggio, quindi avvia quel passo e quel passo da solo. Una responsabilità e pochissime dipendenze, dal momento che gli eventi stessi sono solo piccole bustine di "argomenti", o in alcuni casi solo stringhe o interfacce marker.

Esistono molti framework per coordinare i gestori di eventi, che vanno da bus di servizio transazionali altamente affidabili come NServiceBus o Mass Transit , che sono pesanti sull'infrastruttura e progettati per sistemi distribuiti, fino a Appcelerate (precedentemente bbvCommom / bbvEventBroker, che ha un'estensione Ninject) o semplice Eventi di dominio , progettati per essere eseguiti interamente in-process.

L'idea di base è sempre la stessa, però:

  1. Un client invia un messaggio per avviare un processo.
  2. Un gestore (o più) risponde a quel messaggio.
  3. Ogni gestore pubblica un evento ogni volta che finisce.
  4. I gestori successivi possono rispondere a tali eventi e continuare il processo.

Questo va avanti fino a quando deve, fino a quando non c'è più nulla di "ascolto".

Quindi, nel tuo esempio, potresti avere queste classi (nota: questo non è scritto per essere specifico per ogni particolare framework, è più o meno pseudocodice):

// Messages
public class SubmitOrderCommand { ... }
public class ApproveOrderCommand { ... }
public class OrderProcessedEvent { ... }
public class OrderApprovedEvent { ... }

// Handlers
public class OrderProcessor : IConsume<SubmitOrderCommand>
{
    public void Handle(SubmitOrderCommand message)
    {
        // Process the order
        Events.Publish<OrderProcessedEvent>();
    }
}

public class OrderAuditor : IConsume<OrderCommand>, IConsume<OrderProcessedEvent>
{
    public void Handle(OrderCommand message) { // Write to audit log }
    public void Handle(OrderProcessedEvent message) { // Mark order as processed }
}

public class OrderApprover : IConsume<OrderProcessedEvent>, IConsume<ApproveOrderCommand>
{
    public void Handle(OrderProcessedEvent message)
    {
        // Notify somebody that an order needs to be approved
    }

    public void Handle(ApproveOrderCommand message)
    {
        // Remove ticket from work queue
        Events.Publish<OrderApprovedEvent>();
    }
}

public class OrderNotifier : IConsume<OrderApprovedEvent>
{
    public void Handle(OrderApprovedEvent message) { // Send an email }
}

Suppongo che tu abbia avuto l'idea. In questa architettura, il numero di dipendenze per ogni gestore è interamente limitato alle azioni a cui è responsabile direttamente e a uno o due eventi che esso consuma o pubblica.

Ovviamente, questo significa rinunciare alla coperta di sicurezza di avere l'intero flusso di lavoro descritto in un unico posto. I manager e gli analisti di business amano davvero i loro diagrammi di flusso, e persino i programmatori tendono ad affezionarsi alla capacità di vedere, a colpo d'occhio, ogni fase coinvolta in un particolare processo. Il vantaggio pratico del modello event-driven è un accoppiamento molto più agevole, la testabilità e la possibilità di espandere il flusso di lavoro "orizzontalmente" senza modificare alcun gestore esistente, o in realtà che hanno bisogno di capire o preoccuparsi dell'ordine esatto di eventi.

P.S. Tutti i framework che ho citato eseguono un'iniezione di dipendenza sui gestori. Tu non istanzia i gestori, il framework fa. Questo è ciò che rende questo così diverso da uno script di transazione.

    
risposta data 28.01.2014 - 16:02
fonte