Библиотека, разработанная для создания пользовательского рабочего процесса, Orquestation между компонентами, микросервисами и другими утилитами автоматизации, созданная на Dotnet версии 6
Эта библиотека была разработана, чтобы иметь экосистему сущностей, ориентированной на оркестровку любого типа процесса. От самых простых процессов, таких как вызовы между несколькими нашими классами, до более сложных процессов, таких как вызовы к микросервисам и т. Д. Всегда, однако, декларативным образом и стремясь сохранить код с точки зрения утомительного и сложного гнездования «если», «иначе» ... и т. Д.
В этом репозитории будут основные классы этой экосистемы, сгруппированной под пространством имен Magnett.Automation.core. Некоторые из этих классов могут быть использованы вне объема создания рабочего процесса, так как они достаточно общие, чтобы быть полезными независимо.
В этом пространстве имен мы нашли класс утилит, используемый в классах Libray
Концепт концепции - это нечто очень общее и используется в нескольких областях. Для нас контекст будет общим пространством, где значения хранятся для использования между несколькими компонентами, так как они являются значениями гетерогенного типа.
Поэтому мы находимся перед системой ключей/значений, где значения будут иметь любой тип.
Структура контекста проста, она сформирована только с помощью класса контекста , который будет нашим вводом и поиском класса значений, и интерфейсом IcontextVault , который будет определением хранилища, где хранятся значения.
По умолчанию у нас будет реализация IcontextVault , где он будет хранить значения в памяти, но будет открыта для любой другой реализации, которая хранит эти значения любым другим способом.
Мы будем использовать класс Contextfield , чтобы получить и установить значения в контексте, с этим классом мы можем определить тип и имя класса.
Пример того, как получить / установить значение в контексте
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 ) ; Мы имеем в нашем распоряжении несколько интерфейсов для определения государственной машины, а также ее выполнения. Определение машины будет отделено от выполнения самой машины, чтобы избежать связей и четкого разделения обязанностей.
Основным интерфейсом является Imachine . С помощью этого интерфейса мы будем иметь доступ к текущему состоянию, интерфейсу ISTATE и возможности перехода в другое состояние с использованием кодов действий, которые генерируют переход, сущность Itransaction в другое состояние.
Невозможно перейти от одного состояния в другое напрямую, только через переход, так что у нас есть модель, к которой мы можем перейти от одного, в частности.
Можно предоставить состояние без определенных переходов, и это означает, что состояние является терминальным. Таким образом, мы можем определить конечные или нефинитные машины.
Что касается части выполнения, определение машины будет сделано из интерфейса imachinedefinition , который будет генерироваться из класса MachinedefinitionBuilder .
Пример кода определения машины
//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 ( ) ;Пример создания машины и кода использования.
var machine = Machine
. Create ( SimpleMachineDefinition . GetDefinition ( ) ) ;
machine . Dispatch ( Action . Start ) ;
var currentState = machine . State ; В этом пространстве имен у нас будут необходимые классы для определения рабочего процесса и выполнить его. Как и в предыдущем разделе, мы будем держать определение рабочего процесса отдельно от выполнения.
Это разделение будет выполнено с использованием интерфейсов iworkflowdefinition и i workflowrunner .
Чтобы инкапсулировать определение и выполнение, у нас есть интерфейс iflow , этот интерфейс также позволит нам в будущем создавать подтоки, создавать потоки, которые инкапсулируются в качестве услуги в более сложных приложениях ... и т. Д.
Если мы думаем в основном потоке, справедливом и начальном узеле для сброса значений поля, следующий узел только для какутации к случайным числам и окончательный узел, чтобы суммировать оба значения, определение должно быть так же.
Пример кода определения рабочего процесса.
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 ( ) ;Ранее вы определяли некоторые вспомогательные классы, такие как контекст -рефинирование, это просто класс для содержимого контекстного поля и избежать дублирования с определениями имен.
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 ( ) ;
}
}Мы также создали общий класс абстрактного класса, который мы будем использовать в качестве базового класса для всех наших классов узлов, мы будем использовать в качестве способа, чтобы убедиться, что у всех узлов есть доступный ConxTextDefinition
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 ) ) ;
}
}У нас есть два типа узлов синхронизированных и асинхронных интерфейсов, под интерфейсами inode и inodeasync , поэтому мы можем использовать узлы в качестве обертки обоих типов процесса. В этом примере у нас есть только реализация синхронизации.
В нашем примере мы будем использовать только синхронизационные узлы.
Пример узела
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 ) ;
}
}Внутренний класс Exitcodes - это всего лишь еще один вспомогательный класс, постройте класс перечисления с определением доступных кодов выхода для этого узла, мы также используем Somthin Similira
Бегун, чтобы создать себе экземпляр, необходимо получить определение рабочего процесса и экземпляр контекста, который будет использоваться для обмена информацией между узлами. Как только бегун будет выполнен, мы можем получить возвратные значения из контекста, если есть.
У нас есть абстрактная класс Flowrunnerbase , чтобы мы могли реализовать наших пользовательских бегунов, шаг в шаг, распределение и т. Д.
Пример Flow Runner
var flowRunner = FlowRunner . Create ( definition , Context . Create ( ) ) ;
var exit = await flowRunner . Start ( ) ;Поток класса, как мы уже говорили ранее, это обертка для всего этого процесса, теперь имеет основные функциональные возможности, но в будущих версиях будут использоваться в качестве основного класса для управления рабочими процессами.
var definition = SimpleFlowDefinition . GetDefinition ( ) ;
var context = Context . Create ( ) ;
var flow = Flow . Create ( FlowRunner . Create ( definition , context ) ) ;
var exit = await flow . Run ( ) ;