Biblioteca projetada para criar fluxo de trabalho personalizado, orquestação entre componentes, microsserviços e outros utilitários de automação, criados no Dotnet versão 6
Esta biblioteca foi projetada para ter um ecossistema de entidades orientadas para a orquestração de qualquer tipo de processo. Desde os processos mais simples, como chamadas entre várias de nossas classes, até processos mais complexos, como chamadas para microsserviços, etc. Sempre, no entanto, de maneira declarativa e procurando salvar código em termos de aninhamento tedioso e complexo de "se", "else" ... etc. Estrutura
Neste repositório, serão as principais classes desse ecossistema, agrupadas no espaço de nome magnett.automation.core. Algumas dessas classes podem ser usadas fora do escopo de criar um fluxo de trabalho, pois são genéricas o suficiente para serem úteis de forma independente.
Neste espaço de nome, encontramos a classe de utilitários usados nas aulas de Libray
O contexto conceitual é algo muito genérico e é usado em vários campos. Para nós, um contexto será um espaço comum em que os valores são armazenados para serem compartilhados entre vários componentes, sendo que eles são valores do tipo heterogêneo.
Portanto, estamos na frente de um sistema de chave/valor, onde os valores serão de qualquer tipo.
A estrutura do contexto é simples, é formada apenas pela classe de contexto , que será nossa entrada e recuperação da classe dos valores, e a interface icontextvault que será a definição do cofre em que os valores são armazenados.
Por padrão, teremos uma implementação do icontextvault , onde ele armazenará os valores na memória, mas estará aberto para qualquer outra implementação que armazena esses valores de qualquer outra maneira.
Usaremos o Class Contextfield para obter e definir valores em um contexto, com esta classe podemos definir o tipo e o nome da classe.
Exemplo de como obter / definir valor em um contexto
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 ) ; Temos à nossa disposição várias interfaces para a definição de uma máquina de estado, bem como sua execução. A definição da máquina será separada da execução da própria máquina, para evitar acoplamentos e uma separação clara de responsabilidades.
A interface principal é o iMachine . Com essa interface, teremos acesso ao estado atual, interface do ISTATE e a possibilidade de fazer a transição para outro estado usando códigos de ação que geram uma transição, entidade ITRANSACTION , para outro estado.
Não é possível ir de um estado para outro diretamente, apenas através de uma transição, para que tenhamos um modelo para o qual estados que possamos ir de um em particular.
Um estado sem transições definidas pode ser dado e isso significa que o estado é terminal. Dessa forma, podemos definir máquinas finitas ou não-finitas.
Em relação à parte de tempo de execução, a definição de uma máquina será feita na interface IMACHINEFENCIONITE , que será gerada a partir da classe MachinedEfinitionBuilder .
Exemplo de código de definição da máquina
//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 ( ) ;Exemplo do código de criação e uso da máquina.
var machine = Machine
. Create ( SimpleMachineDefinition . GetDefinition ( ) ) ;
machine . Dispatch ( Action . Start ) ;
var currentState = machine . State ; Sob esse espaço de nome, teremos as classes necessárias para definir um fluxo de trabalho e executá -lo. Como na seção anterior, manteremos a definição de fluxo de trabalho separada da execução.
Essa separação será feita usando as interfaces iWorkFlowDefinition e iWorkFlowRunner .
Para encapsular a definição e a execução, temos a interface IFLOW , essa interface também nos permitirá no futuro construir subfluentes, criar fluxos que são encapsulados como um serviço em aplicativos mais complexos ... etc.
Se pensarmos em um fluxo básico, o nó justo e inicial para redefinir os valores do campo, o próximo nó apenas para cacular para números aleatórios e um nó final para somar ambos os valores, a definição deve ser somthina como esse.
Exemplo de código de definição do fluxo de trabalho.
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 ( ) ;Anteriormente, você definiu algumas classes de auxiliares como o ContextDefinition, é apenas uma classe para contém campo de contexto e evitar duplicação com definições de nome.
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 ( ) ;
}
}Criamos também a classe abstrata comum que usaremos como classe base para todas as nossas classes de nós, usaremos como uma maneira de garantir que todos os nós tenham ConxTextDefinition altos e disponíveis
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 ) ) ;
}
}Temos dois tipos de nós sincronizados e assíncronos, sob as interfaces inodeia e inodeseSync , para que possamos usar nós como um invólucro de ambos os tipos de processo. Neste exemplo, temos apenas a implementação de sincronização.
Em nosso exemplo, usaremos apenas nós de sincronização.
Exemplo de nó
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 ) ;
}
}A classe interna ExitCodes é apenas mais uma classe auxiliar, construir sobre a classe de enumeração com a definição de códigos de saída avaliáveis para este nó, usamos também Somthin Similira
Um corredor, para se instanciar, precisará receber a definição do fluxo de trabalho e uma instância de contexto que será usada para compartilhar informações entre nós. Depois que o corredor for executado, podemos recuperar valores de retorno do contexto, se houver algum.
Temos a classe abstrata FlowRunnerBase para que possamos implementar nossos corredores personalizados, passo a passo, distribuídos, etc.
Exemplo de corredor de fluxo
var flowRunner = FlowRunner . Create ( definition , Context . Create ( ) ) ;
var exit = await flowRunner . Start ( ) ;O fluxo de classe, como dissemos antes, é um invólucro para todo esse processo, agora tem funcionalidades básicas, mas em futuras versões serão usadas como classe principal para gerenciamento de fluxo de trabalho.
var definition = SimpleFlowDefinition . GetDefinition ( ) ;
var context = Context . Create ( ) ;
var flow = Flow . Create ( FlowRunner . Create ( definition , context ) ) ;
var exit = await flow . Run ( ) ;