Workflow Core - un moteur de processus métier pour .Net Core

image


Bonjour à tous!


Nous avons décidé de soutenir le thème de la migration du projet à l'aide de Windows Workflow Foundation vers .Net Core , qui a été lancé par des collègues de DIRECTUM, car nous avons rencontré un problème similaire il y a quelques années et avons suivi notre propre chemin.


Commençons par l'histoire


Notre produit phare, Avanpost IDM, est un système de gestion du cycle de vie des comptes et de l'accès des employés. Il sait gérer l'accès à la fois automatiquement en fonction du modèle de rôle et en fonction des demandes. À l'aube de la formation du produit, nous avions un système de libre-service assez simple avec un flux de travail étape par étape simple, pour lequel le moteur n'était pas requis en principe.


image


Cependant, face à de gros clients, nous avons réalisé qu'un outil beaucoup plus flexible était nécessaire, car leurs exigences pour les processus de coordination des droits d'accès recherchaient les règles d'un bon workflow feuillu. Après avoir analysé les exigences, nous avons décidé de développer notre propre éditeur de processus au format BPMN, adapté à nos besoins. Nous parlerons du développement de l'éditeur à l'aide de React.js + SVG un peu plus tard, et aujourd'hui nous discuterons du sujet principal - le moteur de workflow ou le moteur de processus métier.


Prérequis


Au début du développement du système, nous avions les exigences suivantes pour le moteur:


  • Prise en charge des diagrammes de processus, un format compréhensible, la possibilité de diffuser de notre format au format moteur
  • Stockage de l'état du processus
  • Prise en charge de la gestion des versions de processus
  • Prise en charge de l'exécution parallèle (branches) du processus
  • Une licence appropriée pour utiliser la solution dans un produit commercial répliqué
  • Prise en charge de la mise à l'échelle horizontale

Après avoir analysé le marché (pour 2014), nous avons opté pour une solution pratiquement non alternative pour .Net: Windows Workflow Foundation.


Windows Workflow Foundation (WWF)


Le WWF est la technologie de Microsoft pour définir, exécuter et gérer les workflows.


La base de sa logique est un ensemble de conteneurs pour les actions (activités) et la possibilité de construire des processus séquentiels à partir de ces conteneurs. Le conteneur peut être ordinaire - une certaine étape du processus où l'activité est effectuée. Il peut s'agir d'un gestionnaire - contenant la logique de branchement.


Vous pouvez dessiner un processus directement dans Visual Studio. Le diagramme de processus métier compilé est stocké dans Haml, ce qui est très pratique - le format est décrit, il est possible de faire un concepteur de processus auto-écrit. C'est d'une part. D'un autre côté, Xaml n'est pas le format le plus pratique pour stocker une description - le schéma compilé pour un processus plus ou moins réel s'avère énorme, notamment en raison de la redondance. C'est très difficile à comprendre, mais vous devrez le comprendre.


Mais si tôt ou tard on peut comprendre zen avec des schémas et apprendre à les lire, alors le manque de transparence du moteur lui-même ajoute déjà aux tracas lors du fonctionnement du système par les utilisateurs. Lorsque l'erreur vient des entrailles de Wf, il n'est pas toujours possible de découvrir à 100% quelle était exactement la raison de l'échec. La source fermée et la monstruosité relative n'aide pas le cas. Souvent, les corrections de bugs étaient dues à des symptômes.


En toute honnêteté, il convient de préciser ici que les problèmes décrits ci-dessus, pour la plupart, nous tourmentent en raison de la forte personnalisation de Wf. L'un des lecteurs dira avec certitude que nous avons nous-mêmes créé un tas de problèmes, puis les avons résolus héroïquement. Il était nécessaire de fabriquer un moteur self-made dès le début. En général, ils auront raison.


En fin de compte, la solution a fonctionné de manière suffisamment stable et est entrée en production avec succès. Mais la transition de nos produits vers .Net Core nous a obligés à abandonner le WWF et à chercher un autre moteur de processus métier, car Depuis mai 2019, Windows Workflow Foundation n'a pas été migré vers .Net Core. Comme nous recherchions un nouveau moteur - le sujet d'un article séparé, mais finalement nous nous sommes installés sur Workflow Core.


Noyau de workflow


Workflow Core est un moteur de processus métier gratuit. Il est développé sous licence MIT, c'est-à-dire qu'il peut être utilisé en toute sécurité dans le développement commercial.


Il est activement effectué par une seule personne, plusieurs autres font périodiquement une demande de pull. Il existe des ports pour d'autres langages (Java, Python et plusieurs autres).


Le moteur est positionné comme léger. En fait, il ne s'agit que d'un hôte pour l'exécution séquentielle d'actions regroupées par n'importe quelle règle métier.


Le projet a une documentation wiki . Malheureusement, il ne décrit pas toutes les fonctionnalités du moteur. Cependant, il sera impudent d'exiger une documentation complète - le projet opensource est soutenu par un passionné. Par conséquent, le Wiki sera tout à fait suffisant pour commencer.


Dès la sortie de la boîte, il est possible de stocker l'état du processus dans un stockage externe (stockage persistant). Les fournisseurs sont standard pour:


  • Mongodb
  • SQL Server
  • PostgreSQL
  • Sqlite
  • Amazon DynamoDB

Écrivez votre fournisseur n'est pas un problème. Nous prenons les sources de toute norme et faisons comme exemple.


La mise à l'échelle horizontale est prise en charge, c'est-à-dire que vous pouvez exécuter le moteur sur plusieurs nœuds à la fois, tout en ayant un point de stockage des états de processus (un stockage de persistance). Dans ce cas, le placement de la file d'attente de tâches interne du moteur doit être dans le stockage général (rabbitMQ, en option). Pour exclure l'exécution d'une tâche par plusieurs nœuds, un gestionnaire de verrouillage est fourni en même temps. Par analogie avec les fournisseurs de stockage externes, il existe des implémentations standard:


  • Baux de stockage Azure
  • Redis
  • AWS DynamoDB
  • SQLServer (dans la source il y en a, mais rien n'est dit dans la documentation)

Il est plus facile d'apprendre à connaître quelque chose de nouveau avec un exemple. Alors faisons-le. Je décrirai la construction d'un processus simple dès le début, avec une explication. Un exemple peut sembler incroyablement simple. Je suis d'accord - c'est simple. Le plus pour commencer.


Allons-y.


Étape


Une étape est une étape du processus au cours de laquelle des actions sont effectuées. L'ensemble du processus est construit à partir d'une séquence d'étapes. Une étape peut effectuer de nombreuses actions, peut être répétée, par exemple, pour un événement extérieur. Il y a un ensemble d'étapes qui sont dotées de la logique «prête à l'emploi»:


  • Attendre
  • Si
  • Alors que
  • Foreach
  • Retard
  • Parallèle
  • Horaire
  • Récidive

Bien sûr, sur certaines primitives intégrées, vous ne pouvez pas supporter le processus. Nous avons besoin d'étapes qui complètent les tâches commerciales. Par conséquent, pour l'instant, mettez-les de côté et prenez des mesures avec notre propre logique. Pour ce faire, vous devez hériter de l'abstraction StepBody .


public abstract class StepBody : IStepBody { public abstract ExecutionResult Run(IStepExecutionContext context); } 

La méthode Run est exécutée lorsque le processus entre dans une étape. Il faut y mettre la logique nécessaire.


  public abstract class StepBody : IStepBody { public abstract ExecutionResult Run(IStepExecutionContext context); } 

Les étapes prennent en charge l'injection de dépendance. Pour ce faire, enregistrez-les simplement dans le même conteneur que les dépendances nécessaires.


De toute évidence, le processus a besoin de son propre contexte - un endroit où des résultats intermédiaires d'exécution peuvent être ajoutés. Wf core a son propre contexte pour l'exécution d'un processus qui stocke des informations sur son état actuel. Vous pouvez y accéder en utilisant la variable de contexte de la méthode Run (). En plus de la fonction intégrée, nous pouvons utiliser notre contexte.


Nous analyserons les façons de décrire et d'enregistrer le processus plus en détail ci-dessous, pour l'instant, nous définissons simplement une certaine classe - le contexte.


  public class ProcessContext { public int Number1 {get;set;} public int Number2 {get;set;} public string StepResult {get;set;} public ProcessContext() { Number1 = 1; Number2 = 2; } } 

Dans les variables Nombre, nous écrivons des nombres; dans la variable StepResult - le résultat de l'étape.


Nous avons décidé du contexte. Vous pouvez écrire votre propre étape:


  public class CustomStep : StepBody { private readonly Ilogger _log; public int Input1 { get; set; } public int Input2 { get; set; } public string Action { get; set; } public string Result { get; set; } public CustomStep(Ilogger log) { _log = log; } public override ExecutionResult Run(IStepExecutionContext context) { Result = ”none”; if (Action ==”sum”) { Result = Number1 + Number2; } if (Action ==”dif”){ Result = Number1 - Number2; } return ExecutionResult.Next(); } } 

La logique est extrêmement simple: deux chiffres et le nom de l'opération viennent à l'entrée. Le résultat de l'opération est écrit dans la variable de sortie Résultat . Si l'opération n'est pas définie, le résultat sera nul .


Nous avons décidé du contexte, il y a une étape avec la logique dont nous avons aussi besoin. Maintenant, nous devons enregistrer notre processus dans le moteur.


Description du processus. Inscription dans le moteur.


Il existe deux façons de décrire un processus. Le premier est la description dans le code - le code dur.


Le processus est décrit via l' interface fluide . Il est nécessaire d'hériter de l' interface IWorkflow <T> généralisée, où T est la classe de contexte du modèle. Dans notre cas, il s'agit d'un ProcessContext .


Cela ressemble à ceci:


  public class SimpleWorkflow : IWorkflow<ProcessContext> { public void Build(IWorkflowBuilder<ProcessContext> builder) { //    } public string Id => "SomeWorkflow"; public int Version => 1; } 

La description elle-même se trouvera dans la méthode Build . Les champs Id et Version sont également obligatoires. Wf core prend en charge la gestion des versions de processus - vous pouvez enregistrer n versions de processus avec le même identifiant. Ceci est pratique lorsque vous devez mettre à jour un processus existant et en même temps donner "en direct" aux tâches existantes.


Nous décrivons un processus simple:


  public class SimpleWorkflow : IWorkflow<ProcessContext> { public void Build(IWorkflowBuilder<ProcessContext> builder) { builder.StartWith<CustomStep>() .Input(step => step.Input1, data => data.Number1) .Input(step => step.Input2, data => data.Number2) .Input(step => step.Action, data => “sum”) .Output(data => data.StepResult, step => step.Result) .EndWorkflow(); } public string Id => "SomeWorkflow"; public int Version => 1; } 

S'il est traduit en langage «humain», cela se traduira par quelque chose comme ceci: le processus commence par l'étape CustomStep . La valeur du champ d'étape Input1 est tirée du champ de contexte Number1 , La valeur du champ d'étape Input2 est tirée du champ de contexte Number2 , le champ d' action est codé en dur à la valeur «somme» . La sortie du champ Résultat est écrite dans le champ contextuel StepResult . Terminez le processus.


D'accord, le code s'est avéré très lisible, il est tout à fait possible de le comprendre, même sans connaissances particulières en C #.


Ajoutez une étape supplémentaire à notre processus, qui affichera le résultat de l'étape précédente dans le journal:


  public class CustomStep : StepBody { private readonly Ilogger _log; public string TextToOutput { get; set; } public CustomStep(Ilogger log) { //    _log = log; } public override ExecutionResult Run(IStepExecutionContext context) { _log.Debug(TextToOutput); return ExecutionResult.Next(); } } 

Et mettez à jour le processus:


  public class SimpleWorkflow : IWorkflow<ProcessContext> { public void Build(IWorkflowBuilder<ProcessContext> builder) { builder.StartWith<CustomStep>() .Input(step => step.Input1, data => data.Number1) .Input(step => step.Input2, data => data.Number2) .Input(step => step.Action, data => “sum”) .Output(data => data.StepResult, step => step.Result) .Then<OutputStep>.Input(step => step.TextToOutput, data => data.StepResult) .EndWorkflow(); } public string Id => "SomeWorkflow"; public int Version => 2; } 

Maintenant, après l'étape avec l'opération d'addition, l'étape de sortie du résultat dans le journal suit. À l'entrée, nous passons la variable Résultat et contexte dans laquelle le résultat de l'exécution a été écrit à la dernière étape. Je prendrai la liberté d'affirmer qu'une telle description à travers un code (hardcode) dans des systèmes réels serait de peu d'utilité. Sauf pour certains processus bureautiques. Il est beaucoup plus intéressant de pouvoir stocker le circuit séparément. Au minimum, nous n'avons pas à remonter le projet chaque fois que nous devons changer quelque chose dans le processus ou en ajouter un nouveau. Wf core fournit cette fonctionnalité en stockant le schéma json. Nous continuons d'élargir notre exemple.


Description du processus Json


De plus, je ne fournirai pas de description via le code. Ce n'est pas particulièrement intéressant et ne fera que gonfler l'article.


Le noyau Wf prend en charge la description de schéma dans json. À mon avis, json est plus visuel que xaml (un bon sujet pour holivar dans les commentaires :)). La structure du fichier est assez simple:


 { "Id": "SomeWorkflow", "Version": 1, "DataType": "App.ProcessContext, App", "Steps": [ { /*step1*/ }, { /*step2*/ } ] } 

Le champ DataType indique le nom complet de la classe de contexte et le nom de l'assembly dans lequel il est décrit. Steps stocke une collection de toutes les étapes du processus. Remplissez l'élément Steps :


 { "Id": "SomeWorkflow", "Version": 1, "DataType": "App.ProcessContext, App", "Steps": [ { "Id": "Eval", "StepType": "App.CustomStep, App", "NextStepId": "Output", "Inputs": { "Input1": "data.Number1", "Input2": "data.Number2" }, "Outputs": { "StepResult": "step.Result" } }, { "Id": "Output", "StepType": "App.OutputStep, App", "Inputs": { "TextToOutput": "data.StepResult" } } ] } 

Examinons de plus près la structure de la description de l'étape via json.


Les champs Id et NextStepId stockent l'identifiant de cette étape et un indicateur de l'étape qui peut être la suivante. De plus, l'ordre des éléments de la collection est sans importance.


StepType est similaire au champ DataType , il contient le nom de classe complet de l'étape (un type qui hérite de StepBody et implémente la logique d'étape) et le nom de l'assembly. Les objets Entrées et Sorties sont plus intéressants. Ils sont définis sous forme de cartographie.


Dans le cas des entrées, le nom de l'élément json est le nom du champ de classe de notre étape; la valeur de l'élément est le nom du champ dans la classe, le contexte du processus.


Pour les sorties, au contraire, le nom de l'élément json est le nom du champ dans la classe, le contexte du processus; La valeur de l'élément est le nom du champ de classe de notre étape.


Pourquoi les champs de contexte sont-ils spécifiés via les données. {Field_name} , et dans le cas de Output , étape. {Field_name} ? Étant donné que wf core, la valeur de l'élément est exécutée en tant qu'expression C # (la bibliothèque d'expressions dynamiques est utilisée). C'est une chose plutôt utile, avec son aide, vous pouvez mettre une logique métier directement dans le schéma, si, bien sûr, l'architecte approuve une telle honte :).


Nous diversifions le schéma avec des primitives standard. Ajouter une étape If conditionnelle et traiter un événement externe.


Si

Si primitif. Ici commencent les difficultés. Si vous avez l'habitude de bpmn et de dessiner des processus dans cette notation, vous trouverez une configuration facile. Selon la documentation, l'étape est décrite comme suit:


 { "Id": "IfStep", "StepType": "WorkflowCore.Primitives.If, WorkflowCore", "NextStepId": "nextStep", "Inputs": { "Condition": "<<expression to evaluate>>" }, "Do": [ [ { /*do1*/ }, { /*do2*/ } ] ] } 

Il n'y a aucun sentiment que quelque chose ne va pas ici? J'en ai un. L'entrée d'étape est définie sur Condition - expression. Ensuite, nous définissons la liste des étapes à l'intérieur du tableau Do (actions). Alors, où est la fausse branche? Pourquoi n'y a-t-il pas de tableau Do pour False? En fait, il y en a. Il est entendu que la branche False est simplement un passage plus loin dans le processus, c'est-à-dire en suivant le pointeur dans NextStepId . Au début, j'étais constamment confus à cause de cela. D'accord, trié. Mais non. Si les actions de processus dans le cas de True doivent être placées dans Do , c'est ce que sera le "beau" json. Et s'il y en a, si enfermés dans une douzaine? Tout ira de côté. Ils disent également que le schéma sur xaml est difficile à lire. Il y a un petit hack. Prenez simplement le moniteur plus large. Il a été mentionné un peu plus haut que l'ordre des étapes de la collection n'a pas d'importance, la transition suit les signes. Il peut être utilisé. Ajoutez une étape supplémentaire:


 { "Id": "Jump", "StepType": "App.JumpStep, App", "NextStepId": "" } 

Devinez à quoi je mène? Certes, nous introduisons une étape de service qui, en transit, amène le processus à une étape dans NextStepId .


Mettez à jour notre schéma:


 { "Id": "SomeWorkflow", "Version": 1, "DataType": "App.ProcessContext, App", "Steps": [ { "Id": "Eval", "StepType": "App.CustomStep, App", "NextStepId": "MyIfStep", "Inputs": { "Input1": "data.Number1", "Input2": "data.Number2" }, "Outputs": { "StepResult": "step.Result" } }, { "Id": "MyIfStep", "StepType": "WorkflowCore.Primitives.If, WorkflowCore", "NextStepId": "OutputEmptyResult", "Inputs": { "Condition": "!String.IsNullOrEmpty(data.StepResult)" }, "Do": [ [ { "Id": "Jump", "StepType": "App.JumpStep, App", "NextStepId": "Output" } ] ] }, { "Id": "Output", "StepType": "App.OutputStep, App", "Inputs": { "TextToOutput": "data.StepResult" } }, { "Id": "OutputEmptyResult", "StepType": "App.OutputStep, App", "Inputs": { "TextToOutput": "\"Empty result\"" } } ] } 

L'étape If vérifie si le résultat de l'étape Eval est vide. S'il n'est pas vide, alors nous affichons le résultat, s'il est vide, alors le message " Résultat vide ". L'étape Jump amène le processus à l'étape Output , qui se trouve en dehors de la collection Do. Ainsi, nous avons maintenu la «verticalité» du schéma. De cette façon, on peut aussi sauter n pas en arrière, c'est-à-dire organiser un cycle. Il existe des primitives intégrées pour les boucles dans le noyau wf, mais elles ne sont pas toujours pratiques. Dans bpmn, par exemple, les boucles sont organisées via If .


Utilisez cette approche ou la norme, c'est à vous de décider. Pour nous, une telle organisation était des étapes plus pratiques.


Attendre

La primitive WaitFor permet au monde extérieur d'influencer le processus lorsqu'il est déjà en cours d'exécution. Par exemple, si au stade du processus, l'approbation du cours par un utilisateur est requise. Le processus restera dans l'étape WaitFor jusqu'à ce qu'il reçoive un événement auquel il est abonné.


Structure primitive:


 { "Id": "Wait", "StepType": "WorkflowCore.Primitives.WaitFor, WorkflowCore", "NextStepId": "NextStep", "CancelCondition": "If(cancel==true)", "Inputs": { "EventName": "\"UserAction\"", "EventKey": "\"DoSum\"", "EffectiveDate": "DateTime.Now" } } 

Je vais expliquer un peu les paramètres.


CancelCondition - une condition pour interrompre une attente. Offre la possibilité d'interrompre l'attente d'un événement et de poursuivre le processus. Par exemple, si un processus attend n événements différents en même temps (le noyau wf prend en charge l'exécution parallèle des étapes), il n'est pas nécessaire d'attendre que tous arrivent , dans ce cas CancelCondition nous aidera. Nous ajoutons un indicateur logique aux variables de contexte et à la réception de l'événement, nous définissons l'indicateur sur true - toutes les étapes WaitFor sont terminées.


EventName et EventKey - nom et clé de l'événement. Des champs sont nécessaires pour distinguer les événements, c'est-à-dire dans un système réel avec un grand nombre de processus fonctionnant simultanément. afin que le moteur comprenne quel événement est destiné à quel processus et à quelle étape.


EffectiveDate - un champ facultatif qui ajoute un horodatage d'événement. Cela peut être utile si vous devez publier un événement «dans le futur». Pour qu'il soit publié immédiatement, vous pouvez laisser le paramètre vide ou régler l'heure actuelle.


Dans tous les cas, il n'est pas pratique de prendre une étape distincte pour traiter les réactions de l'extérieur, mais même généralement, cela sera redondant. Une étape supplémentaire peut être évitée en ajoutant à l'étape habituelle l'attente d'un événement externe et la logique de son traitement. Nous complétons l' étape CustomStep en vous abonnant à un événement externe:


  public class CustomStep : StepBody { private readonly Ilogger _log; public string TextToOutput { get; set; } public CustomStep(Ilogger log) { _log = log; } public override ExecutionResult Run(IStepExecutionContext context) { //-  return ExecutionResult.WaitForEvent("eventName", "eventKey", DateTime.Now); } } 

Nous avons utilisé la méthode d'extension WaitForEvent () standard. Il accepte les paramètres EventName , EventKey et EffectiveDate mentionnés précédemment. Après avoir terminé la logique d'une telle étape, le processus attendra l'événement décrit et appellera à nouveau la méthode Run () au moment où l'événement est publié dans le bus moteur. Cependant, dans la forme actuelle, nous ne pouvons pas distinguer les moments de l'entrée initiale dans l'étape et l'entrée après l'événement. Mais je voudrais en quelque sorte séparer la logique avant-après au niveau de l'étape. Et le drapeau EventPublished nous y aidera. Il est situé dans le contexte général du processus, vous pouvez l'obtenir comme ceci:


 var ifEvent=context.ExecutionPointer.EventPublished; 

Sur la base de cet indicateur, vous pouvez diviser la logique en toute sécurité avant et après un événement externe.


Une précision importante - selon l'idée du créateur du moteur, une étape ne peut être signée que sur un événement et y réagir une fois. Pour certaines tâches, c'est une limitation très désagréable. Il nous a même fallu «finir» le moteur pour sortir de cette nuance. Nous ignorerons leur description dans cet article, sinon l'article ne se terminera jamais :). Des pratiques d'utilisation plus complexes et des exemples d'améliorations seront traités dans les articles suivants.


Processus d'enregistrement dans le moteur. Publication de l'événement sur le bus.


Ainsi, avec la mise en œuvre de la logique des étapes et des descriptions du processus compris. Ce qui reste est la chose la plus importante, sans laquelle le processus ne fonctionnera pas - la description doit être enregistrée.


Nous utiliserons la méthode d'extension AddWorkflow () standard, qui placera ses dépendances dans notre conteneur IoC.


Cela ressemble à ceci:


 public static IServiceCollection AddWorkflow(this IServiceCollection services, Action<WorkflowOptions> setupAction = null) 

IServiceCollection - interface - un contrat pour une collection de descriptions de services. Il vit à l'intérieur de DI de Microsoft (plus d'informations à ce sujet peuvent être lues ici )


WorkflowOptions - paramètres de base du moteur. Il n'est pas nécessaire de les définir vous-même; les valeurs standard sont tout à fait acceptables pour la première connaissance. Nous allons plus loin.


Si le processus a été décrit dans le code, l'enregistrement se déroule comme suit:


 var host = _serviceProvider.GetService<IWorkflowHost>(); host.RegisterWorkflow<SomeWorkflow, ProcessContext>(); 

Si le processus est décrit via json, il doit être enregistré comme suit (bien sûr, la description json doit être préchargée à partir de l'emplacement de stockage):


 var host = _serviceProvider.GetService<IWorkflowHost>(); var definitionLoader = _serviceProvider.GetService<IDefinitionLoader>(); var definition = loader.LoadDefinition({*json  *}); 

De plus, pour les deux options, le code sera le même:


 host.Start(); //      host.StartWorkflow(definitionId, version, context); //      /// host.Stop(); / /     

Le paramètre definitionId est l'identifiant du processus. Ce qui est écrit dans le champ Id du processus. Dans ce cas, id = SomeWorkflow .


Le paramètre version spécifie la version du processus à exécuter. Le moteur offre la possibilité d'enregistrer immédiatement n versions de processus avec un identifiant. C'est pratique lorsque vous devez apporter des modifications à la description du processus sans interrompre les tâches en cours d'exécution - de nouvelles seront créées en fonction de la nouvelle version, les anciennes vivront tranquillement sur l'ancienne.


Le paramètre context est une instance du contexte de processus.


Les méthodes host.Start () et host.Stop () démarrent et arrêtent l'hébergement de processus. Si, dans l'application, le lancement des processus est une tâche appliquée et est effectuée périodiquement, l'hébergement doit être arrêté. Si l'application se concentre principalement sur la mise en œuvre de divers processus, l'hébergement ne peut pas être arrêté.


Il existe une méthode pour envoyer des messages du monde extérieur au bus moteur, qui les répartira ensuite entre les abonnés:


 Task PublishEvent(string eventName, string eventKey, object eventData, DateTime effectiveDate = null); 

La description de ses paramètres était plus élevée dans l'article ( voir la partie primitive WaitFor ).


Conclusion


Nous avons définitivement pris des risques lorsque nous avons opté pour le projet Workflow Core - opensource, qui est activement développé par une seule personne, et même avec une documentation très pauvre. Et vous ne trouverez probablement pas de réelles pratiques d'utilisation de wf core dans les systèmes de production (sauf le nôtre). Bien sûr, après avoir sélectionné une couche d'abstractions distincte, nous nous sommes assurés contre le cas d'échec et la nécessité de retourner rapidement au WWF, par exemple, ou une solution auto-écrite, mais tout s'est plutôt bien passé et l'échec n'est pas venu.


Le passage au moteur open source Workflow Core a résolu un certain nombre de problèmes qui nous empêchaient de vivre paisiblement sur WWF. Le plus important d'entre eux est, bien sûr, le support .Net Core et son absence, même dans les plans, WWF.


Voici l'open source. Travailler avec le WWF et obtenir diverses erreurs de ses entrailles, la capacité de lire au moins la source serait très utile. Sans parler de changer quelque chose en eux. Ici avec Workflow Core une liberté totale (y compris les licences - MIT). Si une erreur apparaît soudainement dans les entrailles du moteur, téléchargez simplement les sources depuis github et recherchez calmement la cause de son apparition. Oui, la simple possibilité de démarrer le moteur en mode débogage avec des points d'arrêt facilite déjà considérablement le processus.


Bien sûr, en résolvant certains problèmes, Workflow Core en a apporté de nouveaux. Nous avons dû apporter une quantité importante de modifications au cœur du moteur. Mais. Les travaux de «finition» pour eux-mêmes coûtent moins de temps que de développer votre propre moteur à partir de zéro. La solution finale était tout à fait acceptable en termes de vitesse et de stabilité, elle nous a permis d'oublier les problèmes avec le moteur et de nous concentrer sur le développement de la valeur commerciale du produit.


PS Si le sujet s'avère intéressant, alors il y aura plus d'articles sur wf core, avec une analyse plus approfondie du moteur et des solutions aux problèmes commerciaux complexes.

Source: https://habr.com/ru/post/fr475902/


All Articles