gibt es ein etabliertes Entwurfsmuster für folgende Situation?
Ich habe viele Entitäten des gleichen Typs, die sich zu jedem Zeitpunkt in einem definierten Zustand befinden. Die Zustände und deren Übergänge sind über einen Zustandsautomaten wohldefiniert. Jetzt gibt es einige Objekte, die an gewissen Zustandsübergängen interessiert sind, aber nicht alle Instanzen kennen.
Mein Ansatz wäre ein “erweitertes Observerpattern”. Dabei soll ein MyEntityStateBroadcaster als Verteilungspunkt dienen, der für die Benachrichtigung der Statusänderungen einer Gruppe von Entitäten zuständig ist. Mal so dahingeschrieben könnte das dann so aussehen:
public interface MyEntityStateBroadcaster {
attach(Observer observer);
detach(Observer observer);
notify(MyEntity subject, State previousState);
}
public class MyEntity {
private final MyEntityStateBroadcaster broadcaster;
private State state = State.A;
public MyEntity(MyEntityStateBroadcaster broadcaster) {
this.broadcaster = broadcaster;
}
public State updateState(State newState) {
State oldState = state;
state = newState;
broadcaster.notify(this, oldState);
return oldState;
}
}```
Ist dieser Entwurf sinnvoll? Gibt es andere, bessere Möglichkeiten um das Problem zu lösen?
Das Problem scheint mir so allgemeingültig, dass sich da doch sicherlich schon andere Leute Gedanken zu gemacht haben sollten...
Ich verstehe noch nicht, wo Dein Ansatz über die existieren Implementierung von [JAPI]Observer[/JAPI]/[JAPI]Observable [/JAPI]hinaus geht.
Sinnvonn wäre das für mich nur, wenn du das Observer-Interface erweiterst:```public interface StateObserver {
public enum State {
A, B, C
}
void notify(Object souce);
boolean wantsStateChange(State from, State to);
}public interface StateObserver {
public enum State {
A, B, C
}
void notify(Object souce);
boolean wantsStateChange(State from, State to);
}
//usage:
class MyObserver implements StateObserver{ @Override
public boolean wantsStateChange(State from, State to) {
return State.A.equals(from)&&State.C.equals(to);
} @Override
public void notify(Object souce) {
// what ever yo need to do
// when State changes from A to C
}
}
class MyObservable{
List myObservers= new ArrayList<>();
private void notify(Object source, State from, State to){
for(myObserver:myObservers){
if(myObserver.wantsStateChange(from,to))
myObserver.notify(souce);
}
}
Die Interfaces sind natürlich geeignet, um das von mir beschriebene Szenario abzudecken. Allerdings wäre die Klasse, die [japi]Observable[/japi] implementiert nicht diejenige, die beobachtet werden soll. Diejenigen, die sich als Subject beim “Broadcaster” registriert haben sollen beobachtet werden.
Ein Bild macht meinen Gedanken vielleicht deutlicher:
Auch ein interessanter Gedanke. In meinem Beispiel würde der Broadcaster injiziert, man könnte natürlich auch eine Liste von Observern injizieren.
Man müsste sich da noch Gedanken über möglicherweise erst später auftauchende Observer machen:
Wenn man eine Referenz auf die Liste übergibt und im Subject keine Kopie erstellt, braucht man wieder einen zentralen Ort, an dem die Liste der Observer verwaltet wird. Das dafür zuständige Objekt könnte dann auch das Verteilen der Nachrichten übernehmen, womit man wieder bei dem aufgezeigten Ansatz wäre.
Wenn man die Observer mittels DI-Framework injiziert, dann wird die Observer-Liste nach der Erzeugung des Subjects nicht mehr aktualisiert.
Das spielt in meinem konkreten Fall aber gar keine Rolle, weil die Subjects kurzlebig sind und die Observer die gesamte Laufzeit der Anwendung über existieren.
Insofern stellt das eine schöne Vereinfachung dar.
Der Statuswechsel wird durch verschiedene Controller, die jeweils langlebig und auch vom Container gemanaged sind, ausgelöst. Das führt dann zu Redundanz bei der Ermittlung der Observer bzw. zu Boilerplate-Code bei der Nutzung der Klasse.
Wahrscheinlich ist das aber die einzige Möglichkeit, ohne Bytecodemanipulation zu verwenden.
Publish-Subscribe ist der allgemeine Name für derartige Konstrukte, das Observer Pattern ist eine Inkarnation davon. Vor einigen Jahren fand ich die Unterteilung in diesem Artikel [1] sehr gut. Das, wie ich finde sehr gute Buch [2] zu dem Thema, behandelt Umsetzungen auf C#-Basis aus denen man Inspiration ziehen kann. Und auf Systemintegrationsebene, wobei sich die Muster auch im Kleinen ähneln, kann man vll. noch mal [3] überfliegen.
Vll. reicht dir schon die einfache Implementierung von Guavas EventBus [4].
@ThreadPool : danke für die Buchempfehlungen. Enterprise Integration Patterns hört sich interessant an.
Eine Anforderung ist auch noch, dass das ganze an eine Transaktion gekoppelt werden soll. Erst nachdem die (JPA-)Transaktion abgeschlossen ist, soll das Event publiziert werden.
Um das ordentlich zu verzahnen, muss ich wahrscheinlich erst noch ein wenig in die Implementierungsphase gehen. Angedacht hatte ich, die Nachrichten in eine Warteschlange einzureihen und dann je nach Ausgang der Transaktion ein Commit oder Rollback auszulösen. Grund dafür ist, dass ansonsten der alte Zustand kompliziert zwischengespeichert werden müsste und ggf. Nachrichten fälschlicherweise verschickt werden (falls später ein Rollback auftritt). Außerdem sollen alle Änderungen bereits in der Datenbank sichtbar sein, da ggf. auch andere Anwendungsmodule (die über andere Anbindungen, bspw. JMS, eingebunden sind) auf die Änderungen reagieren müssen.
Um adäquat über Events im Lifecycle einer Entity (in diesem Fall wahrscheinlich PostPersist und PostUpdate) informiert zu werden, bieten sich die JPA lifecycle events mit den entsprechenden Annotationen an. Hier mal ein Artikel zum Einstieg in das Thema: JPA Lifecycle Events
Die Lifecycle Listener verwende ich an einigen Stellen bereits. Allerdings bin ich mir noch nicht so ganz sicher, ob das wirklich der beste Weg ist.
Externe Listener sind quasi unbrauchbar, weil sich keine Abhängigkeiten injizieren lassen. Und bei Listenern innerhalb der Entitäten bekomme ich dann einen Haufen von Ereignissen die ich dann noch manuell synchronisieren muss.
Ein Listener, der auf ein “AfterCommit”-Event reagiert, wäre deutlich besser. Das sollte meinen Dokumentationsumwälzungen zufolge auch funktionieren. (Edit: ich sollte vllt. noch erwähnen, dass ich dabei Spring verwende.)
Zum Thema Synchronisierung mit der Transaktion habe ich einen guten Blogbeitrag gefunden: Thinking about IT: Transaction synchronization callbacks in Spring Framework
Dort ist eigentlich genau das umgesetzt, was ich brauche: man registriert eine Aktion, die nach erfolgreichem Commit ausgeführt wird. Wenn die Transaktion zurückgerollt wird, dann wird die Aktion auch nicht ausgelöst.