Bibliothèque conçue pour créer un flux de travail personnalisé, Orquestation entre les composants, les microservices et autres utilitaires d'automatisation, créé sur Dotnet version 6
Cette bibliothèque a été conçue pour avoir un écosystème d'entités orienté vers l'orchestration de tout type de processus. Des processus les plus simples tels que les appels entre plusieurs de nos classes à des processus plus complexes tels que les appels vers des microservices, etc. Toujours, cependant, de manière déclarative et cherchant à enregistrer le code en termes de nidification fastielle et complexe de «si», «else» ... etc. Structure
Dans ce référentiel, sera les classes de base de cet écosystème, regroupées sous l'espace de noms Magnett.automation.core. Certaines de ces classes peuvent être utilisées en dehors de la portée de la création d'un flux de travail, car elles sont suffisamment génériques pour être utiles indépendamment.
Dans cet espace de nom, nous avons trouvé des cours d'utilitaires utilisés à l'intérieur des classes de libray
Le contexte du concept est quelque chose de très générique et est utilisé dans plusieurs domaines. Pour nous, un contexte sera un espace commun où les valeurs sont stockées pour être partagées entre plusieurs composants, étant que ce sont des valeurs du type hétérogène.
Nous sommes donc devant un système de clé / valeur, où les valeurs seront de tout type.
La structure du contexte est simple, elle n'est formée que par la classe de contexte , qui sera notre entrée et la récupération de la classe de valeurs, et l'interface icontextvault qui sera la définition du coffre-fort où les valeurs sont stockées.
Par défaut, nous aurons une implémentation de l' IcontextVault , où il stockera les valeurs en mémoire, mais sera ouvert à toute autre implémentation qui stocke ces valeurs de toute autre manière.
Nous utiliserons la classe Contextfield pour obtenir et définir des valeurs dans un contexte, avec cette classe, nous pouvons définir le type et le nom de la classe.
Exemple de la façon d'obtenir / définir la valeur dans un contexte
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 ) ; Nous avons à notre disposition plusieurs interfaces pour la définition d'une machine d'état ainsi que son exécution. La définition de la machine sera séparée de l'exécution de la machine elle-même, pour éviter les couplages et éliminer la séparation des responsabilités.
L'interface principale est l'imachine . Avec cette interface, nous aurons accès à l'état actuel, à l'interface Istate et à la possibilité de passer à un autre état en utilisant des codes d'action qui génèrent une entité de transition, Itransaction , vers un autre état.
Il n'est pas possible de passer d'un État à un autre directement, uniquement par une transition, afin que nous ayons un modèle dans lequel nous pouvons passer de l'un en particulier.
Un état sans transitions défini peut être donné et cela signifie que l'état est terminal. De cette façon, nous pouvons définir des machines finies ou non finies.
En ce qui concerne la pièce d'exécution, la définition d'une machine sera effectuée à partir de l'interface ImachinedEFinition , qui sera générée à partir de la classe MachineFinIitionBuilder .
Exemple de code de définition de la machine
//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 ( ) ;Exemple de création de machines et de code d'utilisation.
var machine = Machine
. Create ( SimpleMachineDefinition . GetDefinition ( ) ) ;
machine . Dispatch ( Action . Start ) ;
var currentState = machine . State ; Dans cet espace de noms, nous aurons les classes nécessaires pour définir un flux de travail et l'exécuter. Comme dans la section précédente, nous garderons la définition du flux de travail séparé de l'exécution.
Cette séparation sera effectuée en utilisant les interfaces iworkflowDefinition et iworkflowrunner .
Pour encapsuler la définition et l'exécution, nous avons l'interface iflow , cette interface nous permettra également à l'avenir de créer des sous-flux, de créer des flux qui sont encapsulés en tant que service dans des applications plus complexes ... etc.
Si nous pensons que dans un flux de base, le nœud juste et initial pour réinitialiser les valeurs de champ, le nœud suivant juste pour caculer à des nombres aléatoires, et un nœud final pour résumer les deux valeurs, la définition devrait être comme ça.
Exemple de code de définition de workflow.
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 ( ) ;Auparavant, vous avez défini certaines classes d'assistance comme ContextDefinition, c'est juste une classe pour contenir un champ de contexte et pour éviter la duplication avec les définitions de noms.
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 ( ) ;
}
}Nous avons également créé la classe abstraite commune que nous utiliserons comme classe de base pour toutes nos classes de nœuds, nous utiliserons comme moyen de nous assurer que tous les nœuds ont une conxtextdefinition avaliable
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 ) ) ;
}
}Nous avons deux types de nœuds Sync et Async, sous les interfaces Inode et Inodeasync , afin que nous puissions utiliser les nœuds comme un emballage de deux types de processus. Dans cet exemple, nous n'avons que l'implémentation de synchronisation.
Dans notre exemple, nous n'utiliserons que les nœuds de synchronisation.
Exemple de nœud
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 ) ;
}
}La classe intérieure EXITCODES est juste une autre classe d'assistance, construire sur la classe d'énumération avec la définition des codes de sortie avaliables pour ce nœud, nous utilisons également Somthin Similira
Un coureur, pour instancier lui-même, devra recevoir la définition de workflow et une instance de contexte qui sera utilisée pour partager des informations entre les nœuds. Une fois le coureur exécuté, nous pouvons récupérer les valeurs de retour du contexte s'il y en a.
Nous avons la classe abstraite FlowRunnerBase afin que nous puissions implémenter nos coureurs personnalisés, pas à pas, distribué, etc.
Exemple de coureur de flux
var flowRunner = FlowRunner . Create ( definition , Context . Create ( ) ) ;
var exit = await flowRunner . Start ( ) ;Le flux de classe comme nous l'avons dit auparavant, c'est un wrapper pour tout ce processus, a maintenant des fonctionnalités de base, mais dans les versions futures sera utilisée comme classe principale pour la gestion du flux de travail.
var definition = SimpleFlowDefinition . GetDefinition ( ) ;
var context = Context . Create ( ) ;
var flow = Flow . Create ( FlowRunner . Create ( definition , context ) ) ;
var exit = await flow . Run ( ) ;