Pracuję nad faktoringiem niektórych aspektów istniejącej usługi internetowej. Interfejsy API usług są implementowane poprzez rodzaj „potoku przetwarzania”, w którym są zadania wykonywane sekwencyjnie. Nic dziwnego, że późniejsze zadania mogą wymagać informacji obliczonych na podstawie wcześniejszych zadań, a obecnie sposób ten jest wykonywany przez dodanie pól do klasy „stan potoku”.
Myślałem (i mam nadzieję?), Że istnieje lepszy sposób na dzielenie się informacjami między krokami potoku niż posiadanie obiektu danych z polami zillionów, z których niektóre mają sens dla niektórych kroków przetwarzania, a nie dla innych. Sprawienie, by klasa ta była bezpieczna dla wątków, byłoby bardzo uciążliwe (nie wiem, czy byłoby to w ogóle możliwe), nie ma sposobu na uzasadnienie jej niezmienników (i prawdopodobnie nie ma żadnych).
Szukałem inspiracji w książce wzorów Gang of Four, ale nie czułem, żeby było tam jakieś rozwiązanie (Memento było w tym samym duchu, ale nie do końca). Szukałem również w trybie online, ale za drugim razem, gdy wyszukujesz „potok” lub „przepływ pracy”, zalewa Cię informacja o potoku Unix lub zastrzeżone silniki i struktury przepływu pracy.
Moje pytanie brzmi - jak podejmiesz kwestię rejestrowania stanu wykonania potoku przetwarzania oprogramowania, aby późniejsze zadania mogły wykorzystywać informacje obliczone przez wcześniejsze? Wydaje mi się, że główną różnicą w potokach uniksowych jest to, że nie zależy ci tylko na wynikach bezpośrednio poprzedzającego zadania.
Zgodnie z żądaniem, niektóre pseudokody ilustrujące mój przypadek użycia:
Obiekt „kontekstu potoku” ma kilka pól, które różne pola potoku mogą wypełnić / odczytać:
public class PipelineCtx {
... // fields
public Foo getFoo() { return this.foo; }
public void setFoo(Foo aFoo) { this.foo = aFoo; }
public Bar getBar() { return this.bar; }
public void setBar(Bar aBar) { this.bar = aBar; }
... // more methods
}
Każdy etap potoku jest również obiektem:
public abstract class PipelineStep {
public abstract PipelineCtx doWork(PipelineCtx ctx);
}
public class BarStep extends PipelineStep {
@Override
public PipelineCtx doWork(PipelieCtx ctx) {
// do work based on the stuff in ctx
Bar theBar = ...; // compute it
ctx.setBar(theBar);
return ctx;
}
}
Podobnie w przypadku hipotetycznego FooStep
, który może wymagać paska obliczonego przez BarStep przed nim, wraz z innymi danymi. A potem mamy prawdziwe wywołanie API:
public class BlahOperation extends ProprietaryWebServiceApiBase {
public BlahResponse handle(BlahRequest request) {
PipelineCtx ctx = PipelineCtx.from(request);
// some steps happen here
// ...
BarStep barStep = new BarStep();
barStep.doWork(crx);
// some more steps maybe
// ...
FooStep fooStep = new FooStep();
fooStep.doWork(ctx);
// final steps ...
return BlahResponse.from(ctx);
}
}