Wie gehe ich in einer ereignisgesteuerten Architektur mit dem Anfangszustand um?

33

In einer ereignisgesteuerten Architektur handelt jede Komponente nur, wenn ein Ereignis durch das System gesendet wird.

Stellen Sie sich ein hypothetisches Auto mit einem Bremspedal und einem Bremslicht vor.

  • Der Bremslicht Windungen auf , wenn er ein brake_on Ereignis und ausgeschaltet , wenn er ein brake_off Ereignis.
  • Das Bremspedal sendet ein brake_on- Ereignis, wenn es gedrückt wird, und ein brake_off- Ereignis, wenn es freigegeben wird.

Das ist alles in Ordnung und gut, bis Sie die Situation haben, in der das Auto mit bereits durchgetretenem Bremspedal eingeschaltet ist . Da die Bremsleuchte niemals ein brake_on- Ereignis empfangen hat, bleibt sie ausgeschaltet - eindeutig eine unerwünschte Situation. Wenn Sie das Bremslicht standardmäßig einschalten, wird die Situation nur umgekehrt.

Was könnte getan werden, um dieses Problem des Anfangszustands zu lösen?

EDIT: Vielen Dank für alle Antworten. Meine Frage war nicht über ein tatsächliches Auto. In Autos lösten sie dieses Problem, indem sie kontinuierlich den Status sendeten - daher gibt es in dieser Domäne kein Startproblem. In meiner Softwaredomäne würde diese Lösung viele unnötige CPU-Zyklen verwenden.

EDIT 2: Zusätzlich zur Antwort von @ gbjbaanb werde ich ein System verwenden, in dem:

  • das hypothetische Bremspedal, nach der Initialisierung sendet ein Ereignis mit seinem Zustand, und
  • Das hypothetische Bremslicht sendet nach der Initialisierung ein Ereignis, das ein Zustandsereignis vom Bremspedal anfordert.

Mit dieser Lösung gibt es keine Abhängigkeiten zwischen Komponenten, keine Rennbedingungen, keine veralteten Nachrichtenwarteschlangen und keine Hauptkomponenten.

Frank Kusters
quelle
2
Das erste, was mir in den Sinn kommt, ist, ein "synthetisches" Ereignis (Call it initialize) zu generieren, das die benötigten Sensordaten enthält.
msw
Sollte das Pedal nicht ein brake_pedal_on-Ereignis und die tatsächliche Bremse das brake_on-Ereignis senden? Ich möchte nicht, dass meine Bremsleuchte aufleuchtet, wenn die Bremse nicht funktioniert.
BDSL
3
Habe ich erwähnt, dass es ein hypothetisches Beispiel war? :-) Es ist stark vereinfacht, die Frage kurz und auf den Punkt zu bringen.
Frank Kusters

Antworten:

32

Es gibt viele Möglichkeiten, dies zu tun, aber ich bevorzuge es, ein nachrichtenbasiertes System so weit wie möglich zu entkoppeln. Dies bedeutet, dass das Gesamtsystem weder den Status einer Komponente noch den Status einer anderen Komponente lesen kann (da auf diese Weise Spaghettis von Abhängigkeiten vorliegen).

Während sich das laufende System um sich selbst kümmert, müssen wir jeder Komponente mitteilen, dass sie sich selbst starten soll, und wir haben so etwas bereits in der Komponentenregistrierung, dh das Kernsystem muss beim Start jeder Komponente mitteilen, dass es sich um eine Komponente handelt Jetzt registriert (oder fordert jede Komponente auf, ihre Details zurückzugeben, damit sie registriert werden kann). In dieser Phase kann die Komponente ihre Startaufgaben ausführen und Nachrichten wie im normalen Betrieb senden.

Das Bremspedal würde also beim Starten der Zündung eine Registrierungs- / Prüfmeldung von der Fahrzeugverwaltung erhalten und nicht nur eine Meldung "Ich bin hier und arbeite" zurücksenden, sondern auch seinen eigenen Zustand prüfen und die Meldung senden Meldungen für diesen Zustand (z. B. eine Meldung, dass das Pedal gedrückt wurde).

Das Problem wird dann zu einer Anlaufabhängigkeit, da das Bremslicht, wenn es noch nicht registriert ist, die Nachricht nicht empfängt. Dies lässt sich jedoch leicht beheben, indem alle diese Nachrichten in die Warteschlange gestellt werden, bis das Kernsystem seine Anlauf-, Registrierungs- und Prüfroutine abgeschlossen hat .

Der größte Vorteil ist, dass für die Initialisierung kein spezieller Code erforderlich ist, außer dass Sie bereits schreiben müssen (ok, wenn sich Ihre Nachricht für Bremspedalereignisse in einem Bremspedal-Handler befindet, müssen Sie dies auch in Ihrer Initialisierung aufrufen , aber das ist normalerweise kein Problem, es sei denn, Sie haben den Code stark an die Handlerlogik gebunden geschrieben und keine Interaktion zwischen Komponenten, außer denjenigen, die sie bereits wie gewohnt aneinander senden. Message-Passing-Architekturen sind deshalb sehr gut!

gbjbaanb
quelle
1
Ihre Antwort gefällt mir, da sie alle Komponenten entkoppelt - es war der wichtigste Grund, sich für diese Architektur zu entscheiden. Derzeit gibt es jedoch keine echte "Master" -Komponente, die entscheidet, dass sich das System in einem "initialisierten" Zustand befindet - alles beginnt gerade zu laufen. Mit dem Problem in meiner Frage als Ergebnis. Sobald der Master entscheidet, dass das System ausgeführt wird, kann er ein "systeminitialisiertes" Ereignis an alle Komponenten senden, wonach jede Komponente mit dem Senden ihres Status beginnt. Problem gelöst. Vielen Dank! (Jetzt habe ich nur noch das Problem, wie ich entscheiden soll, ob das System initialisiert wird ...)
Frank Kusters
Wie wäre es, wenn der Status-Update-Dispatcher das letzte von jedem Objekt empfangene Update protokolliert und dem neuen Abonnenten jedes Mal, wenn eine neue Abonnementanforderung eingeht, die letzten Updates sendet, die er von den registrierten Ereignisquellen erhalten hat?
Supercat
In diesem Fall müssen Sie auch nachverfolgen, wann Ereignisse ablaufen. Nicht alle Ereignisse können für immer für neue Komponenten gespeichert werden, die möglicherweise registriert werden.
Frank Kusters
@spaceknarf Nun, in dem Fall, dass "alles gerade erst anfängt zu laufen", können Sie keine Abhängigkeit in die Komponenten einbauen, sodass das Pedal nach dem Licht startet. Sie müssen sie nur in dieser Reihenfolge starten, obwohl ich mir vorstelle, dass etwas sie zum Laufen bringt sie in der richtigen Reihenfolge (z. B. Linux-Start-Init-Skripte vor systemd, bei denen der Dienst, der zuerst gestartet werden soll, 1.xxx und der zweite Dienst 2.xxx usw. heißt).
gbjbaanb
Skripte mit einer solchen Reihenfolge sind zerbrechlich. Es enthält viele implizite Abhängigkeiten. Stattdessen dachte ich, wenn Sie eine "Master" -Komponente haben, die eine statisch konfigurierte Liste der Komponenten hat, die ausgeführt werden sollen (wie von @Lie Ryan erwähnt), dann kann sie ein "Ready" -Ereignis senden, sobald alle diese Komponenten geladen sind. Als Reaktion darauf senden alle Komponenten ihren Ausgangszustand.
Frank Kusters
4

Sie können ein Initialisierungsereignis haben, das die Zustände beim Laden / Starten entsprechend festlegt. Dies kann für einfache Systeme oder Programme wünschenswert sein, die nicht mehrere Hardwareteile enthalten, jedoch für kompliziertere Systeme mit mehreren physischen Komponenten, da Sie das gleiche Risiko eingehen, als würden Sie überhaupt nicht initialisieren - wenn ein "Bremsen" -Ereignis in Ihrer Kommunikation übersehen wird oder verloren geht System (z. B. ein CAN-basiertes System) Sie können Ihr System versehentlich zurücksetzen, als ob Sie es mit gedrückter Bremse gestartet hätten. Je mehr Controller Sie haben, zum Beispiel mit einem Auto, desto wahrscheinlicher ist es, dass etwas übersehen wird.

Um dies zu berücksichtigen, können Sie die "Bremse ein" -Logik veranlassen, wiederholt "Bremse ein" -Ereignisse auszusenden. Vielleicht alle 1/100 Sekunde oder so. Ihr Code, der das Gehirn enthält, kann auf diese Ereignisse warten und "Bremse ein" auslösen, während er sie empfängt. Nachdem 1 / 10sec kein "brake on" Signal erhalten haben, wird ein internes "brake_off" Ereignis ausgelöst.

Unterschiedliche Ereignisse haben erheblich unterschiedliche zeitliche Anforderungen. In einem Auto muss das Bremslicht viel schneller sein als das Kontrolllicht (bei dem eine Verzögerung von mehreren Sekunden wahrscheinlich akzeptabel ist) oder andere weniger wichtige Systeme.

Die Komplexität Ihres physischen Systems bestimmt, welcher dieser Ansätze angemessener ist. Wenn es sich bei Ihrem Beispiel um ein Fahrzeug handelt, möchten Sie wahrscheinlich etwas Ähnliches.

In beiden Fällen möchten Sie sich bei einem physischen System NICHT darauf verlassen, dass ein einzelnes Ereignis korrekt empfangen / verarbeitet wird. Verbundene Mikrocontroller in einem vernetzten System haben aus diesem Grund häufig ein Timeout "Ich lebe".

Enderland
quelle
In einem physischen System würden Sie ein Kabel verlegen und eine Binärlogik verwenden: HIGH ist die gedrückte Bremse und LOW ist die nicht gedrückte Bremse
Ratschenfreak
@ratchetfreak es gibt viele möglichkeiten für so etwas. Vielleicht kann ein Schalter damit umgehen. Es gibt viele andere Systemereignisse, die nicht so einfach gehandhabt werden.
Enderland
1

In diesem Fall würde ich die Bremse nicht als einfaches Ein / Aus modellieren. Eher würde ich "Bremsdruck" Ereignisse senden. Zum Beispiel würde ein Druck von 0 aus anzeigen und ein Druck von 100 würde vollständig herabgesetzt werden. Das System (der Knoten) sendet bei Bedarf ständig (in einem bestimmten Intervall) Bremsdruckereignisse an die Steuerung (en).

Wenn das System gestartet wurde, begann es, Druckereignisse zu empfangen, bis es ausgeschaltet wurde.

Jon Raynor
quelle
1

Wenn Sie Zustandsinformationen nur über Ereignisse weitergeben können, sind Sie in Schwierigkeiten. Stattdessen müssen Sie in der Lage sein, beide:

  1. den aktuellen Zustand des Bremspedals abfragen und
  2. Registrieren Sie sich für Ereignisse "Status geändert" vom Bremspedal.

Das Bremslicht kann als Beobachter des Bremspedals gesehen werden. Mit anderen Worten, das Bremspedal weiß nichts über die Bremsleuchte und kann ohne sie arbeiten. (Dies bedeutet, dass jede Vorstellung, dass das Bremspedal proaktiv ein Ereignis "Anfangszustand" an das Bremslicht sendet, falsch ist.)

Nach der Instantiierung des Systems registriert sich die Bremsleuchte beim Bremspedal, um Bremsbenachrichtigungen zu erhalten, und liest auch den aktuellen Zustand des Bremspedals und schaltet sich selbst ein oder aus.

Dann können die Bremsbenachrichtigungen auf drei Arten implementiert werden:

  1. als parameterlose "Bremspedalzustand geändert" Ereignisse
  2. als ein Paar von Ereignissen "Bremspedal ist jetzt gedrückt" und "Bremspedal ist jetzt losgelassen"
  3. als Ereignis "neuer Bremspedalzustand" mit einem Parameter "gedrückt" oder "losgelassen".

Ich bevorzuge den ersten Ansatz, was bedeutet, dass die Bremsleuchte nach Erhalt der Benachrichtigung einfach das tut, was sie bereits zu tun weiß: den aktuellen Zustand des Bremspedals ablesen und sich selbst ein- oder ausschalten.

Mike Nakis
quelle
0

In einem ereignisgesteuerten System (das ich derzeit verwende und liebe) finde ich es wichtig, die Dinge so entkoppelt wie möglich zu halten. Lassen Sie uns mit dieser Idee gleich loslegen.

Es ist wichtig, einen Standardzustand zu haben. Ihre Bremsleuchte würde den Standardzustand "Aus" und Ihr Bremspedal den Standardzustand "Auf" annehmen. Alle Änderungen danach wären ein Ereignis.

Nun zu Ihrer Frage. Stellen Sie sich vor, Ihr Bremspedal wird initialisiert und gedrückt, das Ereignis wird ausgelöst, aber es gibt noch keine Bremslichter, um das Ereignis zu empfangen. Ich habe festgestellt, dass es am einfachsten ist, die Erstellung der Objekte (wo die Ereignis-Listener initialisiert würden) als separaten Schritt zu trennen, bevor eine Logik initialisiert wird. Das verhindert die von Ihnen beschriebenen Rennbedingungen.

Ich finde es auch umständlich, zwei verschiedene Ereignisse für das zu verwenden, was eigentlich dasselbe ist . brake_offund brake_onkönnte e_brakemit einem Parameter vereinfacht werden bool on. Sie können Ihre Veranstaltungen auf diese Weise vereinfachen, indem Sie unterstützende Daten hinzufügen.

Thebluefish
quelle
0

Was Sie brauchen, ist ein Sendeereignis und Nachrichteneingänge. Eine Sendung ist eine Nachricht, die an eine nicht festgelegte Anzahl von Zuhörern gesendet wird. Eine Komponente kann Broadcast-Ereignisse abonnieren, sodass sie nur Ereignisse empfängt, an denen sie interessiert ist. Dies bietet Entkopplung, da der Absender nicht wissen muss, wer die Empfänger sind. Die Abonnementtabelle muss während der Installation der Komponente statisch konfiguriert werden (statt beim Initialisieren von). Der Posteingang ist Teil des Nachrichtenrouters, der als Puffer für Nachrichten fungiert, wenn die Zielkomponente offline ist.

Die Verwendung von Rechnungen bringt ein Problem mit sich, nämlich die Größe des Posteingangs. Sie möchten nicht, dass das System eine wachsende Anzahl von Nachrichten für Komponenten speichern muss, die niemals mehr online sein werden. Dies ist insbesondere bei eingebetteten Systemen mit strengen Speicherbeschränkungen wichtig. Um die Größenbeschränkung des Posteingangs zu überwinden, müssen alle gesendeten Nachrichten einige Regeln befolgen. Die Regeln sind:

  1. Für jedes Sendeereignis ist ein Name erforderlich
  2. Zu jedem Zeitpunkt kann der Absender eines Sendeereignisses nur eine aktive Sendung mit einem bestimmten Namen haben
  3. Der durch das Ereignis verursachte Effekt muss idempotent sein

Der Broadcast-Name muss während der Installation der Komponente angegeben werden. Wenn eine Komponente eine zweite Sendung mit demselben Namen sendet, bevor der Empfänger die vorherige Sendung verarbeitet, überschreibt die neue Sendung die vorherige. Jetzt können Sie eine statische Größenbeschränkung für den Posteingang festlegen, die garantiert eine bestimmte Größe nicht überschreitet und anhand der Abonnementtabellen vorberechnet werden kann.

Schließlich benötigen Sie noch ein Broadcast-Archiv. Das Broadcast-Archiv ist eine Tabelle, die das letzte Ereignis aus jedem Broadcast-Namen enthält. Bei neuen Komponenten, die gerade installiert werden, wird der Posteingang mit Nachrichten aus dem Broadcast-Archiv vorab ausgefüllt. Wie der Nachrichteneingang kann auch das Broadcast-Archiv eine statische Größe haben.

Um mit Situationen fertig zu werden, in denen der Nachrichtenrouter selbst offline ist, benötigen Sie außerdem Nachrichtenausgänge. Der Nachrichtenausgang ist Teil der Komponente, in der ausgehende Nachrichten vorübergehend gespeichert sind.

Lüge Ryan
quelle