Integration mit "Gosuslugi". Anwenden des Workflow-Kerns (Teil II)

Das letzte Mal haben wir uns den Ort SMEV im Problem der Integration mit dem Portal "öffentliche Dienste" angesehen. Durch die Bereitstellung eines einheitlichen Kommunikationsprotokolls zwischen den Teilnehmern erleichtert SMEV die Interaktion zwischen vielen verschiedenen Abteilungen und Organisationen, die ihre Dienste über das Portal bereitstellen möchten, erheblich.



Der Dienst kann als ein über die Zeit verteilter Prozess betrachtet werden, der mehrere Punkte aufweist, über die das Ergebnis beeinflusst werden kann (über das Portal stornieren, in der Abteilung ablehnen, Informationen über die Änderung des Status des Dienstes an das Portal senden und auch das Ergebnis seiner Bereitstellung senden). In dieser Hinsicht durchläuft jeder Dienst seinen eigenen Lebenszyklus durch diesen Prozess und sammelt Daten über die Anforderung des Benutzers, empfangene Fehler, die Ergebnisse des Dienstes usw. Auf diese Weise können Sie jederzeit weitere Maßnahmen zur Verarbeitung des Dienstes steuern und entscheiden.



Wir werden weiter darüber sprechen, wie und mit welcher Hilfe Sie eine solche Verarbeitung organisieren können.



Auswahl einer Business Process Automation Engine



Zur Organisation der Datenverarbeitung, gibt es Bibliotheken und Systeme für die Automatisierung von Geschäftsprozessen, weitauf dem Markt: von eingebetteten Lösungen bis hin zu Systemen mit vollem Funktionsumfang, die einen Rahmen für die Prozesssteuerung bieten. Wir haben Workflow Core als Tool zur Automatisierung von Geschäftsprozessen ausgewählt. Diese Wahl wurde aus mehreren Gründen getroffen: Erstens ist die Engine in C # für die .NET Core-Plattform geschrieben (dies ist unsere Hauptentwicklungsplattform), sodass es im Gegensatz zu beispielsweise Camunda BPM einfacher ist, sie in die gesamte Produktübersicht aufzunehmen. Darüber hinaus handelt es sich um eine eingebettete Engine, die zahlreiche Möglichkeiten zum Verwalten von Instanzen von Geschäftsprozessen bietet. Zweitens wird unter den vielen unterstützten Speicheroptionen auch PostgreSQL in unseren Lösungen verwendet. Drittens bietet die Engine eine einfache Syntax zur Beschreibung des Prozesses in Form einer fließenden API (es gibt jedoch auch eine Variante zur Beschreibung des Prozesses in einer JSON-Datei)Die Verwendung schien weniger bequem zu sein, da es schwierig wird, einen Fehler in der Beschreibung des Prozesses bis zum Zeitpunkt seiner tatsächlichen Ausführung zu erkennen.



Geschäftsabläufe



Unter den allgemein anerkannten Werkzeugen zur Beschreibung von Geschäftsprozessen ist die BPMN- Notation zu beachten . Die Lösung für das FizzBuzz- Problem in der BPMN-Notation könnte beispielsweise folgendermaßen aussehen:





Die Workflow Core-Engine enthält die meisten Bausteine ​​und Anweisungen in der Notation und ermöglicht es Ihnen, wie oben erwähnt, die fließenden API- oder JSON-Daten zur Beschreibung bestimmter Prozesse zu verwenden. Die Implementierung dieses Prozesses mithilfe der Workflow Core Engine kann folgende Form annehmen :



  //    .
  public class FizzBuzzWfData
  {
    public int Counter { get; set; } = 1;
    public StringBuilder Output { get; set; } = new StringBuilder();
  }

  //  .
  public class FizzBuzzWorkflow : IWorkflow<FizzBuzzWfData>
  {
    public string Id => "FizzBuzz";
    public int Version => 1;

    public void Build(IWorkflowBuilder<FizzBuzzWfData> builder)
    {
      builder
        .StartWith(context => ExecutionResult.Next())
        .While(data => data.Counter <= 100)
          .Do(a => a
            .StartWith(context => ExecutionResult.Next())
              .Output((step, data) => data.Output.Append(data.Counter))
            .If(data => data.Counter % 3 == 0 || data.Counter % 5 == 0)
              .Do(b => b
                .StartWith(context => ExecutionResult.Next())
                  .Output((step, data) => data.Output.Clear())
                .If(data => data.Counter % 3 == 0)
                  .Do(c => c
                    .StartWith(context => ExecutionResult.Next())
                      .Output((step, data) =>
                        data.Output.Append("Fizz")))
                .If(data => data.Counter % 5 == 0)
                  .Do(c => c
                    .StartWith(context => ExecutionResult.Next())
                      .Output((step, data) =>
                        data.Output.Append("Buzz"))))
            .Then(context => ExecutionResult.Next())
              .Output((step, data) =>
              {
                Console.WriteLine(data.Output.ToString());
                data.Output.Clear();
                data.Counter++;
              }));
    }
  }
}


Natürlich kann der Prozess einfacher beschrieben werden, indem die Ausgabe der gewünschten Werte direkt in den Schritten nach den Kardinalitätsprüfungen hinzugefügt wird. Mit der aktuellen Implementierung können Sie jedoch feststellen, dass jeder Schritt einige Änderungen am allgemeinen „Sparschwein“ der Prozessdaten vornehmen und auch die Ergebnisse der vorherigen Schritte nutzen kann. In diesem Fall werden die Prozessdaten in einer Instanz gespeichert FizzBuzzWfData, auf die zum Zeitpunkt ihrer Ausführung jeder Schritt Zugriff hat.



MethodeBuildNimmt ein Process Builder-Objekt als Argument, das als Ausgangspunkt für den Aufruf einer Kette von Erweiterungsmethoden dient, die die Schritte eines Geschäftsprozesses nacheinander beschreiben. Erweiterungsmethoden können wiederum eine Beschreibung von Aktionen direkt im aktuellen Code in Form von Lambda-Ausdrücken enthalten, die als Argumente übergeben werden, oder sie können parametrisiert werden. Im ersten Fall, der in der Auflistung dargestellt ist, wird ein einfacher Algorithmus in einen ziemlich komplexen Befehlssatz übersetzt. Im zweiten Fall ist die Logik der Schritte in separaten Klassen verborgen, die vom Typ erben Step(oder AsyncStepfür asynchrone Varianten), sodass Sie komplexe Prozesse in eine präzisere Beschreibung einpassen können. In der Praxis scheint der zweite Ansatz besser geeignet zu sein, während der erste für einfache Beispiele oder extrem einfache Geschäftsprozesse ausreicht.



Die eigentliche Prozessbeschreibungsklasse implementiert die parametrisierte Schnittstelle IWorkflowund enthält bei der Ausführung des Vertrags die Prozesskennung und die Versionsnummer. Dank dieser Informationen kann die Engine Prozessinstanzen im Speicher erzeugen, sie mit Daten füllen und ihren Status im Speicher festlegen. Mithilfe der Versionsunterstützung können Sie neue Prozessvarianten erstellen, ohne dass das Risiko besteht, dass vorhandene Instanzen im Repository beeinträchtigt werden. Um eine neue Version zu erstellen, reicht es aus, eine Kopie der vorhandenen Beschreibung zu erstellen, der Eigenschaft eine nächste Nummer zuzuweisen Versionund das Verhalten dieses Prozesses nach Bedarf zu ändern (der Bezeichner sollte unverändert bleiben).



Beispiele für Geschäftsprozesse im Rahmen unserer Aufgabe sind:



  • – .
  • – , , .
  • – .
  • – , .


Wie Sie den Beispielen entnehmen können, sind alle Prozesse bedingt in „zyklisch“ unterteilt, deren Ausführung periodische Wiederholungen umfasst, und „linear“, die im Kontext spezifischer Anweisungen ausgeführt werden, und schließen jedoch das Vorhandensein einiger zyklischer Strukturen in sich nicht aus.



Betrachten wir ein Beispiel für einen der Prozesse, die in unserer Lösung zum Abrufen der Warteschlange für eingehende Anforderungen ausgeführt werden:



public class LoadRequestWf : IWorkflow<LoadRequestWfData>
{
  public const string DefinitionId = "LoadRequest";

  public string Id => DefinitionId;
  public int Version => 1;

  public void Build(IWorkflowBuilder<LoadRequestWfData> builder)
  {
    builder
      .StartWith(then => ExecutionResult.Next())
        .While(d => !d.Quit)
          .Do(x => x
            .StartWith<LoadRequestStep>() // *
              .Output(d => d.LoadRequest_Output, s => s.Output)
            .If(d => d.LoadRequest_Output.Exception != null)
              .Do(then => then
                .StartWith(ctx => ExecutionResult.Next()) // *
                  .Output((s, d) => d.Quit = true))
            .If(d => d.LoadRequest_Output.Exception == null
                && d.LoadRequest_Output.Result.SmevReqType
                  == ReqType.Unknown)
              .Do(then => then
                .StartWith<LogInfoAboutFaultResponseStep>() // *
                  .Input((s, d) =>
                    { s.Input = d.LoadRequest_Output?.Result?.Fault; })
                  .Output((s, d) => d.Quit = false))
            .If(d => d.LoadRequest_Output.Exception == null
               && d.LoadRequest_Output.Result.SmevReqType
                 == ReqType.DataRequest)
              .Do(then => then
                .StartWith<StartWorkflowStep>() // *
                  .Input(s => s.Input, d => BuildEpguNewApplicationWfData(d))
                  .Output((s, d) => d.Quit = false))
            .If(d => d.LoadRequest_Output.Exception == null
          	    && d.LoadRequest_Output.Result.SmevReqType == ReqType.Empty)
              .Do(then => then
                .StartWith(ctx => ExecutionResult.Next()) // *
                  .Output((s, d) => d.Quit = true))
          .If(d => d.LoadRequest_Output.Exception == null
             && d.LoadRequest_Output.Result.SmevReqType
               == ReqType.CancellationRequest)
            .Do(then => then
              .StartWith<StartWorkflowStep>() // *
                .Input(s => s.Input, d => BuildCancelRequestWfData(d))
                .Output((s, d) => d.Quit = false)));
  }
}


In den mit * gekennzeichneten Zeilen sehen Sie die Verwendung parametrisierter Erweiterungsmethoden, die die Engine anweisen, Schrittklassen (dazu später mehr) zu verwenden, die Typparametern entsprechen. Mit Hilfe von Erweiterungsmethoden Inputund haben Outputwir die Möglichkeit, die an den Schritt übergebenen Anfangsdaten vor Beginn der Ausführung festzulegen und dementsprechend die Prozessdaten (und sie werden durch eine Instanz der Klasse dargestellt LoadRequestWfData) in Verbindung mit den vom Schritt ausgeführten Aktionen zu ändern . Und so sieht der Prozess in einem BPMN-Diagramm aus:





Schritte



Wie oben erwähnt, ist es sinnvoll, die Logik der Schritte in separate Klassen einzuteilen. Sie können den Prozess nicht nur übersichtlicher gestalten, sondern auch wiederverwendbare Schritte für allgemeine Vorgänge erstellen.



Je nach dem Grad der Eindeutigkeit der in unserer Lösung ausgeführten Aktionen werden die Schritte in zwei Kategorien unterteilt: allgemein und spezifisch. Ersteres kann in beliebigen Modulen für beliebige Projekte wiederverwendet werden, sodass sie in der gemeinsam genutzten Lösungsbibliothek abgelegt werden. Letztere sind für jeden Kunden einzigartig, da sie in den entsprechenden Designmodulen Platz finden. Beispiele für allgemeine Schritte sind:



Senden von Bestätigungsanforderungen für eine Antwort.



  • Hochladen von Dateien in den Dateispeicher.
  • Extrahieren von Daten aus dem SMEV-Paket usw.


Spezifische Schritte:



  • Erstellung von Objekten im IAS, damit der Bediener einen Dienst bereitstellen kann.
  • .
  • ..


Bei der Beschreibung der Schritte im Prozess haben wir uns an den Grundsatz der beschränkten Haftung für jeden Schritt gehalten. Dies ermöglichte es, Fragmente der übergeordneten Geschäftsprozesslogik nicht schrittweise zu verbergen und in der Prozessbeschreibung explizit auszudrücken. Wenn beispielsweise in den Anwendungsdaten ein Fehler gefunden wird, muss eine Nachricht über die Verweigerung der Bearbeitung des Antrags an SMEV gesendet werden. Der entsprechende Block der Bedingung befindet sich direkt im Code des Geschäftsprozesses, und verschiedene Klassen entsprechen den Schritten zum Ermitteln des Fehlers und zum Antworten darauf.



Es ist zu beachten, dass Schritte im Abhängigkeitscontainer registriert werden müssen, damit die Engine Instanzen von Schritten verwenden kann, wenn sich jeder Prozess durch seinen Lebenszyklus bewegt.



Jeder Schritt ist eine Verbindung zwischen dem Code, der die allgemeine Beschreibung des Prozesses enthält, und dem Code, der Anwendungsprobleme löst - Dienste.



Dienstleistungen



Services stellen die nächste, niedrigere Ebene der Problemlösung dar. Jeder Schritt in der Erfüllung seiner Pflicht beruht in der Regel auf einem oder mehreren Diensten (Hinweis: Das Konzept des „Dienstes“ ist in diesem Zusammenhang näher am analogen Konzept des „Dienstes auf Anwendungsebene“ aus dem Bereich des domänenspezifischen Designs (DDD)).



Beispiele für Dienstleistungen sind:



  • Der Dienst zum Empfangen einer Antwort von der SMEV-Antwortwarteschlange bereitet das entsprechende Datenpaket im SOAP-Format vor, sendet es an das SMEV und konvertiert die Antwort in eine Form, die zur weiteren Verarbeitung geeignet ist.
  • Dienst zum Herunterladen von Dateien aus dem SMEV-Repository - Ermöglicht das Lesen von an die Anwendung angehängten Dateien aus dem Portal aus dem Datei-Repository mithilfe des FTP-Protokolls.
  • Der Dienst zum Abrufen des Ergebnisses der Bereitstellung eines Dienstes - liest Daten zu den Ergebnissen des Dienstes aus dem IAS und bildet das entsprechende Objekt, auf dessen Grundlage ein anderer Dienst eine SOAP-Anforderung zum Senden an das Portal erstellt.
  • Dienst zum Hochladen von Dateien, die sich auf das Ergebnis des Dienstes beziehen, in den SMEV-Dateispeicher.


Die Dienste in der Lösung werden basierend auf dem System in Gruppen unterteilt, mit denen sie interagieren:



  • KMU-Dienste.
  • IAS-Dienste.


Dienste für die Arbeit mit der internen Infrastruktur der Integrationslösung (Protokollierung von Informationen zu Datenpaketen, Verknüpfung der Entitäten der Integrationslösung mit IAS-Objekten usw.).



In architektonischer Hinsicht sind Dienste die niedrigste Ebene, sie können sich jedoch auch auf Dienstprogrammklassen verlassen, um ihre Probleme zu lösen. So gibt es in der Lösung beispielsweise eine Codeschicht, die die Probleme der Serialisierung und Deserialisierung von SOAP-Datenpaketen für verschiedene Versionen des SMEV-Protokolls löst. Im Allgemeinen kann die obige Beschreibung in einem Klassendiagramm zusammengefasst werden:





Die Schnittstelle IWorkflowund die abstrakte Klasse stehen in direktem Zusammenhang mit der Engine StepBodyAsync(Sie können jedoch den synchronen analogen StepBody verwenden). Das folgende Diagramm zeigt die Implementierung von "Bausteinen" - konkreten Klassen mit Beschreibungen der Workflow-Geschäftsprozesse und der darin verwendeten Schritte ( Step). Auf der unteren Ebene werden Services vorgestellt, die im Wesentlichen bereits spezifisch für diese spezielle Implementierung der Lösung sind und im Gegensatz zu Prozessen und Schritten optional sind.



Dienste müssen wie Schritte im Abhängigkeitscontainer registriert werden, damit Schritte, die ihre Dienste verwenden, die erforderlichen Instanzen von ihnen durch Injektion durch den Konstruktor erhalten können.



Einbetten des Motors in die Lösung



Zum Zeitpunkt des Beginns der Erstellung des Integrationssystems mit dem Portal war Version 2.1.2 der Engine im Nuget-Repository verfügbar. Es wird standardmäßig in einer ConfigureServicesKlassenmethode in den Abhängigkeitscontainer integriert Startup:



public void ConfigureServices(IServiceCollection services)
{
  // ...
  services.AddWorkflow(opts =>
    opts.UsePostgreSQL(connectionString, false, false, schemaName));
  // ...
}


Die Engine kann für eines der unterstützten Data Warehouses konfiguriert werden (es gibt andere unter ihnen : MySQL, MS SQL, SQLite, MongoDB). Im Fall von PostgreSQL verwendet die Engine Entity Framework Core in der Code First-Variante, um mit Prozessen zu arbeiten. Dementsprechend ist es bei einer leeren Datenbank möglich, die Migration anzuwenden und die gewünschte Tabellenstruktur abzurufen. Die Verwendung der Migration ist optional und kann mithilfe der Methodenargumente gesteuert werden: Mit den Argumenten UsePostgreSQLdes zweiten ( canCreateDB) und dritten ( canMigrateDB) Booleschen Typs können Sie der Engine mitteilen, ob sie eine Datenbank erstellen kann, wenn sie nicht vorhanden ist, und Migrationen anwenden.



Da bei der nächsten Aktualisierung der Engine die Wahrscheinlichkeit einer Änderung des Datenmodells ungleich Null besteht und die entsprechende Verwendung der nächsten Migration die bereits akkumulierten Daten beschädigen kann, haben wir beschlossen, diese Option aufzugeben und die Datenbankstruktur auf der Grundlage des Mechanismus der Datenbankkomponenten , der in unseren anderen Projekten verwendet wird, selbst beizubehalten .



Das Problem des Speicherns von Daten und des Registrierens der Engine im Abhängigkeitscontainer wurde behoben. Fahren wir nun mit dem Starten der Engine fort. Für diese Aufgabe wurde die gehostete Serviceoption und hier angezeigtsiehe ein Beispiel einer Basisklasse zum Erstellen eines solchen Dienstes). Der als Grundlage verwendete Code wurde geringfügig geändert, um die Modularität aufrechtzuerhalten. Dies bedeutet, dass die Integrationslösung ("Onyx" genannt) in einen gemeinsamen Teil unterteilt wird, der die Initialisierung und Ausführung einiger Serviceprozeduren für die Engine ermöglicht, sowie in einen für jeden Kunden spezifischen Teil (Integrationsmodule). ...



Jedes Modul enthält Prozessbeschreibungen, eine Infrastruktur zum Ausführen von Geschäftslogik sowie einen einheitlichen Code, damit das entwickelte Integrationssystem Prozessbeschreibungen erkennen und dynamisch in eine Instanz der Workflow Core-Engine laden kann:







Registrierung und Start von Geschäftsprozessen



Nachdem wir vorgefertigte Beschreibungen der Geschäftsprozesse und der mit der Lösung verbundenen Engine erstellt haben, ist es an der Zeit, der Engine mitzuteilen, mit welchen Prozessen sie arbeiten wird.



Dies erfolgt mit dem folgenden Code, der sich innerhalb des zuvor genannten Dienstes befindet (der Code, der die Registrierung von Prozessen in den angeschlossenen Modulen initiiert, kann auch hier platziert werden):



public async Task RunWorkflowsAsync(IWorkflowHost host,
  CancellationToken token)
{
  host.RegisterWorkflow<LoadRequestWf, LoadRequestWfData>();
  //   ...

  await host.StartAsync(token);
  token.WaitHandle.WaitOne();
  host.Stop();
}


Fazit



Im Allgemeinen haben wir die Schritte behandelt, die Sie ausführen müssen, um Workflow Core in einer Integrationslösung zu verwenden. Mit der Engine können Sie Geschäftsprozesse flexibel und bequem beschreiben. Angesichts der Tatsache, dass wir uns mit der Aufgabe der Integration in das "Gosuslug" -Portal über das SMEV befassen, ist zu erwarten, dass die geplanten Geschäftsprozesse eine Reihe recht unterschiedlicher Aufgaben abdecken (Warteschlangenabfrage, Hochladen / Herunterladen von Dateien, Sicherstellung der Einhaltung des Austauschprotokolls und Sicherstellen Bestätigung des Datenempfangs, Fehlerbehandlung in verschiedenen Phasen usw.). Daher ist es ganz natürlich zu erwarten, dass auf den ersten Blick einige nicht offensichtliche Momente der Umsetzung eintreten, und wir werden ihnen den nächsten, letzten Artikel des Zyklus widmen.



Studienlinks






All Articles