Bibliothek entwickelt für das Erstellen benutzerdefinierter Workflows, oder die Frage zwischen Komponenten, Microservices und anderen Automatisierungsdienstprogrammen, die auf Dotnet Version 6 erstellt wurden
Diese Bibliothek wurde so konzipiert, dass ein Ökosystem von Entitäten auf der Orchestrierung jeglicher Art von Prozess ausgerichtet ist. Von den einfachsten Prozessen wie Aufrufen zwischen mehreren unserer Klassen bis hin zu komplexeren Prozessen wie Aufrufen von Microservices usw. Allerdings auf deklarative Weise und auf der Suche nach Code in Bezug auf die mühsame und komplexe Verschachtelung von „wenn“, „sonst
In diesem Repository werden die Kernklassen dieses Ökosystems unter dem Namespace Magnett.Automation.core gruppiert. Einige dieser Klassen können außerhalb des Rahmens der Erstellung eines Workflows verwendet werden, da sie generisch genug sind, um unabhängig voneinander nützlich zu sein.
In diesem Namespace haben wir die Dienstprogrammklasse gefunden, die in den Libray -Klassen verwendet werden
Der Konzeptkontext ist etwas sehr generisches und wird in mehreren Bereichen verwendet. Für uns ist ein Kontext ein gemeinsamer Raum, in dem die Werte gespeichert werden, die zwischen mehreren Komponenten geteilt werden sollen, da sie Werte des heterogenen Typs sind.
Wir stehen daher vor einem Schlüssel-/Wert -System, wobei die Werte von jedem Typ sind.
Die Struktur des Kontextes ist einfach, er wird nur durch die Kontextklasse gebildet, die unsere Eingabe und Abruf der Werte sein wird, und die IcontextVault -Schnittstelle, die die Definition des Tresors ist, in dem die Werte gespeichert sind.
Standardmäßig werden wir eine Implementierung des IcontextVault haben, in dem die Werte im Speicher gespeichert werden, aber für jede andere Implementierung geöffnet sind, die diese Werte auf andere Weise speichert.
Wir werden das Klassenkontextfeld verwenden, um Werte in einem Kontext zu erhalten und festzulegen. Mit dieser Klasse können wir den Typ und den Namen der Klasse definieren.
Beispiel dafür, wie der Wert in einem Kontext erhalten / festgelegt wird / festgelegt wird
var context = Context . Create ( ) ;
var field = ContextField < int > . Create ( "FieldName" ) ;
//Set Value
context . Store ( field , random . Next ( 1000 ) ) ;
//Get Value
var value = context . Value ( field ) ; Wir haben uns mehrere Schnittstellen zur Definition einer Zustandsmaschine sowie ihrer Ausführung zur Verfügung. Die Definition der Maschine wird von der Ausführung der Maschine selbst getrennt, um Kopplungen und eindeutige Trennung der Verantwortlichkeiten zu vermeiden.
Die Hauptschnittstelle ist Imachine . Mit dieser Schnittstelle haben wir Zugriff auf den aktuellen Status, die ISTATE -Schnittstelle und die Möglichkeit, mit Aktionscodes, die einen Übergang, Itransaction -Entität, zu einem anderen Zustand zu erzeugen, in einen anderen Zustand zu übergehen.
Es ist nicht möglich, direkt durch einen Übergang von einem Zustand in einen anderen zu wechseln, damit wir ein Modell haben, zu dem wir von einem von einem ausgehen können.
Ein Zustand ohne definierte Übergänge kann gegeben werden, und dies bedeutet, dass der Zustand terminal ist. Auf diese Weise können wir endliche oder nicht finanzierte Maschinen definieren.
In Bezug auf den Laufzeitteil erfolgt die Definition einer Maschine von der Imaachinedefinition -Schnittstelle, die aus der maschinellen Definitionbuilder -Klasse generiert wird.
Beispiel des Maschinendefinitionscode
//Helper class with states enumeration
public class State : Enumeration
{
public static readonly State Init = new State ( 1 , nameof ( Init ) ) ;
public static readonly State Working = new State ( 2 , nameof ( Working ) ) ;
public static readonly State Paused = new State ( 3 , nameof ( Paused ) ) ;
public static readonly State Finished = new State ( 4 , nameof ( Finished ) ) ;
private State ( int id , string name ) : base ( id , name )
{
}
}
//Helper class with action enumerations
public class Action : Enumeration
{
public static readonly Action Start = new Action ( 1 , nameof ( Start ) ) ;
public static readonly Action Pause = new Action ( 2 , nameof ( Pause ) ) ;
public static readonly Action Continue = new Action ( 3 , nameof ( Continue ) ) ;
public static readonly Action Finish = new Action ( 4 , nameof ( Finish ) ) ;
private Action ( int id , string name ) : base ( id , name )
{
}
}
//Now we can create a definition
_definition = MachineDefinitionBuilder . Create ( )
. InitialState ( State . Init )
. OnAction ( Action . Start ) . ToState ( State . Working )
. Build ( )
. AddState ( State . Working )
. OnAction ( Action . Pause ) . ToState ( State . Paused )
. OnAction ( Action . Finish ) . ToState ( State . Finished )
. Build ( )
. AddState ( State . Paused )
. OnAction ( Action . Continue ) . ToState ( State . Working )
. Build ( )
. AddState ( State . Finished )
. Build ( )
. BuildDefinition ( ) ;Beispiel für die Erstellung von Maschinen und den Nutzungscode.
var machine = Machine
. Create ( SimpleMachineDefinition . GetDefinition ( ) ) ;
machine . Dispatch ( Action . Start ) ;
var currentState = machine . State ; Unter diesem Namespace haben wir die erforderlichen Klassen, um einen Workflow zu definieren und ihn auszuführen. Wie im vorherigen Abschnitt werden wir die Workflow -Definition von der Ausführung getrennt halten.
Diese Trennung erfolgt mit der IWORKFLOWDEFINITION- und IWORKFLOWRUNNER -Schnittstellen.
Um die Definition und Ausführung zu verkapulieren, haben wir die IFLOW -Schnittstelle, diese Schnittstelle ermöglicht uns auch in Zukunft, Unterflüsse zu erstellen und Flows zu erstellen, die als Dienst in komplexeren Anwendungen eingekapselt werden ... usw.
Wenn wir in einem grundlegenden Fluss, einem gerechten und anfänglichen Knoten, um Feldwerte zurückzusetzen, nach dem nächsten Knoten nur zum Caculat auf zufällige Zahlen und eines endgültigen Knotens zur Fassungsfunktion beider Werte sollte die Definition so doppelt so sein.
Beispiel Workflow Definitionscode.
var contextDefinition = ContextDefinition . Create ( ) ;
var definition = FlowDefinitionBuilder . Create ( )
. WithInitialNode ( ResetValue . Create ( Node . Reset , contextDefinition ) )
. OnExitCode ( ResetValue . ExitCode . Ok ) . GoTo ( Node . SetValue )
. Build ( )
. WithNode ( SetValue . Create ( Node . SetValue , contextDefinition ) )
. OnExitCode ( SetValue . ExitCode . Assigned ) . GoTo ( Node . SumValue )
. Build ( )
. WithNode ( SumValue . Create ( Node . SumValue , contextDefinition ) ) . Build ( )
. BuildDefinition ( ) ;Zuvor haben Sie einige Helferklassen wie ContextDefinition definiert. Es ist nur eine Klasse, um das Kontextfeld zu enthält und Doppelarbeit mit Namensdefinitionen zu vermeiden.
internal class ContextDefinition
{
public ContextField < int > FirstDigit { get ; }
public ContextField < int > SecondDigit { get ; }
public ContextField < int > Result { get ; }
private ContextDefinition ( )
{
FirstDigit = ContextField < int > . Create ( "FieldOne" ) ;
SecondDigit = ContextField < int > . Create ( "FieldTwo" ) ;
Result = ContextField < int > . Create ( "FieldResult" ) ;
}
public static ContextDefinition Create ( )
{
return new ContextDefinition ( ) ;
}
}Wir haben auch die abstrakte Klasse erstellt, die wir als Basisklasse für alle unsere Knotenklassen verwenden werden. Wir werden verwenden, um sicherzustellen
internal abstract class Common : Core . WorkFlows . Implementations . Node
{
protected ContextDefinition ContextDefinition { get ; }
protected Common ( CommonNamedKey key , ContextDefinition contextDefinition ) : base ( key )
{
ContextDefinition = contextDefinition
?? throw new ArgumentNullException ( nameof ( contextDefinition ) ) ;
}
}Wir haben zwei Synchronisierungs- und Async -Knotentypen unter den Schnittstellen von Inode und Inodeasync , sodass wir Knoten als Wrapper beider Prozesstypen verwenden können. In diesem Beispiel haben wir nur die SYNC -Implementierung.
In unserem Beispiel verwenden wir nur Synchronisierungsknoten.
Beispielknoten
internal class ResetValue : Common
{
#region ExitCodes
public class ExitCode : Enumeration
{
public static readonly ExitCode Ok = new ExitCode ( 1 , "Ok" ) ;
private ExitCode ( int id , string name ) : base ( id , name )
{
}
}
#endregion
private ResetValue ( CommonNamedKey key , ContextDefinition contextDefinition ) :
base ( key , contextDefinition )
{
}
public override NodeExit Execute ( Context context )
{
context . Store ( ContextDefinition . FirstDigit , 0 ) ;
context . Store ( ContextDefinition . SecondDigit , 0 ) ;
context . Store ( ContextDefinition . Result , 0 ) ;
return NodeExit . Create ( ExitCode . Ok . Name ) ;
}
public static ResetValue Create ( CommonNamedKey name , ContextDefinition contextDefinition )
{
return new ResetValue ( name , contextDefinition ) ;
}
}Die Inner Class ExitCodes ist nur eine weitere Helferklasse. Erstellen Sie die Aufzählungsklasse mit der Definition von durchschnittlichen Exit -Codes für diesen Knoten. Wir verwenden auch Somthin Similira
Ein Läufer muss die Workflow -Definition und eine Kontextinstanz erhalten, mit der Informationen zwischen Knoten austausch werden. Sobald der Läufer ausgeführt wurde, können wir Rückgabewerte aus dem Kontext abrufen, wenn es welche gibt.
Wir haben die abstrakte Klasse Flowrunnerbase , damit wir unsere benutzerdefinierten Läufer, Schritt zu Schritt, verteilt usw. implementieren können.
Beispiel Flow Runner
var flowRunner = FlowRunner . Create ( definition , Context . Create ( ) ) ;
var exit = await flowRunner . Start ( ) ;Der Klassenfluss, wie wir bereits erwähnt haben, ist es ein Wrapper für all diesen Prozess, der jetzt grundlegende Funktionen hat, aber in zukünftigen Versionen werden als Hauptklasse für das Workflow -Management verwendet.
var definition = SimpleFlowDefinition . GetDefinition ( ) ;
var context = Context . Create ( ) ;
var flow = Flow . Create ( FlowRunner . Create ( definition , context ) ) ;
var exit = await flow . Run ( ) ;