Design pattern per i flussi di lavoro di composizione

2

Per favore sopporti me perché trovo questo difficile da articolare esattamente quello che sto cercando (probabilmente non è un buon segno ...).

Ho impostato diversi servizi che possono essere combinati in un ordine particolare per creare flussi di lavoro. Ad esempio, immagina di avere i seguenti servizi:

  • 1) HTTP - > File Store (prende un file su HTTP e lo memorizza)
  • 2) FTP - > Archivio file
  • 3) File Store- > FTP (prende un file su HTTP e lo spinge su FTP)
  • 4) File Store- > Trasformazione: > Archivio file (applica una trasformazione a un file in File Store).

Potrei voler combinare servizi per creare un particolare flusso di lavoro, per esempio.

1) - > 4) - > 3)

O

2) - > 4) - > 3)

E potrebbe accadere che io esegua esattamente la stessa operazione, ad es. nei flussi di lavoro precedenti, il passaggio 4 potrebbe essere applicato sullo stesso elemento due volte, se lo stesso elemento viene inviato su HTTP e FTP. Potrei anche voler attivare i flussi di lavoro secondari, ad esempio:

4) - > 3)

Sarebbe un flusso di lavoro valido. Esiste un modello di progettazione / approccio noto per gestire questi flussi di lavoro "a cascata"? Vorrei evitare operazioni duplicate, ove possibile, ma anche progettare flussi di lavoro in modo separato e contenuto. L'unica cosa che mi viene in mente è una sorta di approccio basato sulla composizione in cui i flussi di lavoro sono composti da diversi flussi di lavoro secondari.

Spero che abbia senso!

    
posta Alex 11.12.2014 - 11:00
fonte

2 risposte

1

Non sono sicuro che ciò possa essere d'aiuto, ma in F # potresti creare un'espressione di calcolo come la seguente,

 let workFlow = 
      workflow {
         let! pageContents = ("read url", readHttp "http://www.google.com")
         do! ("write file", writeFile @"c:\temp\google.html" pageContents)
      } |> runWorkflow

Questo può essere creato dalle seguenti funzioni

 let runStage (name, f:Async<'a>) = 
     async {
         try
             do printfn "Running %s" name
             let! result = f
             return Choice1Of2(result)
          with e ->
             printfn "Stage %s failed %A" name e
             return Choice2Of2 e
     }

 type WorkflowBuilder() = 
     member x.Bind((name,curr), next:('a -> Async<'b>)) = 
             async { 
                     let! result = runStage (name, curr)
                     match result with
                     | Choice1Of2(r) -> return! next(r)
                     | Choice2Of2(e) -> return raise(e)
             }
     member x.Return(a) = async { return a }
     member x.ReturnFrom(a) = a    

 let workflow = WorkflowBuilder()

puoi quindi comporre semplici flussi di lavoro asincroni per ottenere il risultato desiderato.

 let readHttp (uri:string) = 
       async { 
         let req = HttpWebRequest.CreateHttp(uri)
         req.Method <- "GET"
         let! response = req.GetResponseAsync() |> Async.AwaitTask
         let sr = new StreamReader(response.GetResponseStream())
         return sr.ReadToEnd()
      }

 let writeFile (path:string) (contents:string) = 
      async {
          let fs = File.OpenWrite(path)
          let sw = new StreamWriter(fs)
          do sw.Write(contents)
      }

Ovviamente questa implementazione è specifica per la lingua, non c'è nulla che fermi davvero una traduzione in C # o Java. Il modello di progettazione generale utilizzato qui è una monade, in parole semplici è solo un tipo di wrapping (in questo caso Async < 'a >) plus, due funzioni (bind e return), che puoi vedere implementate nel tipo WorkflowBuilder.

Dovrei menzionare che alcuni lavori saranno necessari per produrre questo codice, ma l'idea è più importante che io provi.

    
risposta data 11.12.2014 - 12:32
fonte
1

Quello che stai cercando è il pattern Chain of Responsibility , che ti consente di comporre una pipeline di oggetti che possono agire su un messaggio.

Nella catena di responsabilità (una versione di esso), hai un'interfaccia Handler , ei tuoi comportamenti individuali implementano quell'interfaccia e hanno un riferimento al gestore successivo. Pseudo-codice, e probabilmente non abbastanza specifico per le tue esigenze, ma buono ad esempio:

interface Handler {
    protected Handler nextHandler;

    protected void pullData(string name);
    protected void pushData(string name, string data);

    public void process(string name);
}

Quindi avresti un HTTPHandler che implementa l'interfaccia:

class HTTPHandler implements Handler {
    public HTTPHandler(Handler nextHandler) {
        this.nextHandler = nextHandler;
    }

    protected void pullData(string name) {
        string data = getDataOverHTTP(name);
        if(!this.nextHandler == null) {
            this.nextHandler.pushData(name, data);
        }
    }

    protected void pushData(string name, string data) {
        postDataOverHTTP(name, data);
        if(!this.nextHandler == null) {
            this.nextHandler.pushData(name, data);
        }
    }

    public void process(string name) {
        this.pullData(name);
    }
}

Altre classi possono essere costruite di conseguenza, quindi composte insieme. Ogni implementazione di Handler sa chiamare il prossimo gestore nell'elenco. E, Handler s può essere composto insieme in modo dinamico in fase di runtime. Con una chiamata a process sul primo Handler , i dati verranno inviati fino in fondo alla pipeline. Questo articolo fornisce un esempio più completo di questo modo di catena di responsabilità.

(FYI: un modo alternativo per farlo potrebbe essere quello di creare una collezione che incapsula un elenco di Handler s e passa i dati tra di loro utilizzando un oggetto dati intermedio.)

    
risposta data 11.12.2014 - 17:38
fonte

Leggi altre domande sui tag