Projeto e implementação do mecanismo de fluxo de trabalho leve

1. O que é um mecanismo de fluxo de trabalho

Um mecanismo de fluxo de trabalho é um conjunto de código que impulsiona a execução do fluxo de trabalho.

Quanto ao que é workflow, por que existe workflow e os cenários de aplicação de workflow, os alunos podem dar uma olhada nas informações na Internet, mas não vou expandi-las aqui.

 

2. Por que repetir a roda

Existem muitos mecanismos de fluxo de trabalho de código aberto, como activiti, flowable, Camunda, etc., então por que não escolhê-los? Com base nas seguintes considerações:

  • Mais importante, ele não pode atender às necessidades de negócios e alguns cenários especiais não podem ser realizados.
  • Alguns requisitos são mais complicados de implementar.Além disso, é necessário modificar diretamente o banco de dados do mecanismo, o que traz enormes perigos ocultos para a operação estável do mecanismo e também cria algumas dificuldades para a atualização futura da versão do mecanismo.
  • Existem muitos materiais, volume de código e APIs, o custo de aprendizado é alto e a capacidade de manutenção é ruim.
  • Após análise e avaliação, nosso cenário de negócios requer menos elementos BPMN, e o custo de desenvolvimento e implementação não é alto.

Portanto, reinventar a roda, de fato, há uma consideração estratégica mais profunda, ou seja: como empresa de tecnologia, devemos ter nosso próprio núcleo de tecnologia subjacente! Desta forma, não pode ser controlado por pessoas (consulte o problema recente do chip).

 

3. Como fazer a roda

Para uma partilha de aprendizagem, o processo é mais importante do que o resultado.Aqueles partilhas que só falam do resultado, não do processo ou até falam sobre ele em detalhe, acho que é uma demonstração de músculos, não uma partilha verdadeira. Portanto, a seguir, este artigo se concentrará em descrever o principal processo de fabricação de uma roda.

A construção de um mecanismo de workflow maduro é muito complexa, como lidar com essa complexidade? De um modo geral, existem três métodos:

  • Entrega determinística: descubra quais são os requisitos, quais são os critérios de aceitação e, de preferência, escreva os casos de teste. Esta etapa é para esclarecer os objetivos.
  • Desenvolvimento iterativo: Comece resolvendo pequenos conjuntos de problemas e gradualmente faça a transição para resolver grandes conjuntos de problemas. Roma não foi construída em um dia e as pessoas não amadurecem em um dia. É preciso um processo.
  • Dividir e conquistar: Divida grandes problemas em pequenos problemas, e a solução de pequenos problemas promoverá a solução de grandes problemas (essa ideia é aplicável a muitos cenários, e os alunos podem experimentá-los e entendê-los cuidadosamente).

Se você seguir o método acima e expandi-lo passo a passo em detalhes, talvez precise de um livro. A fim de reduzir o espaço sem perder os produtos secos, este artigo descreverá várias iterações principais e, em seguida, elaborará o design e a implementação principal do mecanismo de fluxo de trabalho leve.

Então, o que significa leve? Aqui, refere-se principalmente aos seguintes pontos

  • Menos dependências: Na implementação java do código, exceto para jdk8, ele não depende de outros pacotes jar de terceiros, o que pode reduzir melhor os problemas causados ​​por dependências.
  • Kernelização: No design, o modo de arquitetura micro-kernel é adotado, e o kernel é pequeno e prático, proporcionando um certo grau de escalabilidade. Assim, o motor pode ser melhor compreendido e aplicado.
  • Especificação leve: Não implementa totalmente a especificação BPMN, nem é projetada de acordo com a especificação BPMN, mas apenas se refere à especificação, e implementa apenas uma pequena parte dos elementos que devem ser implementados. Como resultado, o custo do aprendizado é reduzido e você pode jogar livremente de acordo com suas necessidades.
  • Ferramentas: Em termos de código, é apenas uma ferramenta (UTIL), não um aplicativo. Assim, você pode simplesmente executá-lo, expandir sua própria camada de dados, camada de nó e integrá-lo mais facilmente a outros aplicativos.

OK, chega de bobagem, vamos começar a primeira iteração...

 

4. Olá ProcessEngine

De acordo com a convenção internacional, a primeira iteração é usada para implementar o hello world.

1. Demanda

Como administrador de processo, gostaria que o mecanismo de processo executasse o processo mostrado na imagem abaixo para que eu possa configurar o processo para imprimir strings diferentes.

 

2. Análise

  • O primeiro processo pode imprimir Hello ProcessEngine e o segundo processo pode imprimir ProcessEngine Hello. A diferença entre os dois processos é que apenas a ordem é diferente. As funções dos nós azuis e dos nós vermelhos não foram alteradas.
  • O nó azul e o nó vermelho são ambos nós e suas funções são diferentes, ou seja: o nó vermelho imprime Olá e o nó azul imprime ProcessEngine
  • Os nós inicial e final são dois nós especiais, um para iniciar o processo e outro para finalizar o processo
  • Os nós são conectados por linhas, após a execução de um nó, o próximo nó a ser executado é determinado pela seta.
  • Precisa de uma maneira de representar o processo, ou XML, ou JSON, ou algo assim, não imagens

3. Projeto

(1) Representação do processo

Comparado com o JSON, o XML tem uma semântica mais rica e pode expressar mais informações, então o XML é usado aqui para representar o processo, conforme mostrado abaixo

<definitions>
    <process id="process_1" name="hello">
        <startEvent id="startEvent_1">
            <outgoing>flow_1</outgoing>
        </startEvent>
        <sequenceFlow id="flow_1" sourceRef="startEvent_1" targetRef="printHello_1" />
        <printHello id="printHello_1" name="hello">
            <incoming>flow_1</incoming>
            <outgoing>flow_2</outgoing>
        </printHello>
        <sequenceFlow id="flow_2" sourceRef="printHello_1" targetRef="printProcessEngine_1" />
        <printProcessEngine id="printProcessEngine_1" name="processEngine">
            <incoming>flow_2</incoming>
            <outgoing>flow_3</outgoing>
        </printProcessEngine>
        <sequenceFlow id="flow_3" sourceRef="printProcessEngine_1" targetRef="endEvent_1"/>
        <endEvent id="endEvent_1">
            <incoming>flow_3</incoming>
        </endEvent>
    </process>
</definitions>

 

  • processo representa um processo
  • startEvent representa o nó inicial e endEvent representa o nó final
  • printHello significa imprimir o nó hello, que é o nó azul no requisito
  • processEngine significa imprimir o nó processEngine, que é o nó vermelho na demanda
  • sequenceFlow representa uma conexão, começando de sourceRef e apontando para targetRef, por exemplo: flow_3, que representa uma conexão de printProcessEngine_1 para endEvent_1.

(2) Representação de nós

  • Outgoing indica a borda de saída, ou seja, após a execução do nó, ele deve sair dessa borda.
  • entrada representa a borda de entrada, ou seja, de qual borda entrar neste nó.
  • Um nó tem apenas saída, mas nenhuma entrada, como: startEvent, ou pode ter apenas arestas de entrada, mas nenhuma aresta de saída, como: endEvent, ou pode ter arestas de entrada e saída, como: printHello, processEngine.

(3) A lógica do mecanismo de processo

Com base no XML acima, a lógica de operação do mecanismo de processo é a seguinte

  1. Encontre o nó inicial (startEvent)
  2. Encontre a borda de saída de startEvent (sequenceFlow)
  3. Encontre o nó (targetRef) apontado pela borda (sequenceFlow)
  4. Execute a lógica do próprio nó
  5. Encontre a borda de saída do nó (sequenceFlow)
  6. Repita 3-5 até que o nó final (endEvent) seja encontrado e o processo termine

4. Perceba

O primeiro passo é projetar a estrutura de dados, ou seja, mapear as informações do domínio do problema para os dados do computador.

Pode-se observar que um processo (PeProcess) consiste em vários nós (PeNode) e arestas (PeEdge). (para). .

As definições específicas são as seguintes:

public class PeProcess {
    public String id;
    public PeNode start;

    public PeProcess(String id, PeNode start) {
        this.id = id;
        this.start = start;
    }
}

public class PeEdge {
    private String id;
    public PeNode from;
    public PeNode to;

    public PeEdge(String id) {
        this.id = id;
    }
}

public class PeNode {
    private String id;

    public String type;
    public PeEdge in;
    public PeEdge out;

    public PeNode(String id) {
        this.id=id;
    }
}

PS: Para expressar a ideia principal, o código é mais "descontrolado e livre", não sendo permitido copiar e colar diretamente na produção!

Em seguida, crie um fluxograma com o seguinte código:

public class XmlPeProcessBuilder {
    private String xmlStr;
    private final Map<String, PeNode> id2PeNode = new HashMap<>();
    private final Map<String, PeEdge> id2PeEdge = new HashMap<>();

    public XmlPeProcessBuilder(String xmlStr) {
        this.xmlStr = xmlStr;
    }

    public PeProcess build() throws Exception {
        //strToNode : 把一段xml转换为org.w3c.dom.Node
        Node definations = XmlUtil.strToNode(xmlStr);
        //childByName : 找到definations子节点中nodeName为process的那个Node
        Node process = XmlUtil.childByName(definations, "process");
        NodeList childNodes = process.getChildNodes();

        for (int j = 0; j < childNodes.getLength(); j++) {
            Node node = childNodes.item(j);
            //#text node should be skip
            if (node.getNodeType() == Node.TEXT_NODE) continue;

            if ("sequenceFlow".equals(node.getNodeName()))
                buildPeEdge(node);
            else
                buildPeNode(node);
        }
        Map.Entry<String, PeNode> startEventEntry = id2PeNode.entrySet().stream().filter(entry -> "startEvent".equals(entry.getValue().type)).findFirst().get();
        return new PeProcess(startEventEntry.getKey(), startEventEntry.getValue());
    }

    private void buildPeEdge(Node node) {
        //attributeValue : 找到node节点上属性为id的值
        PeEdge peEdge = id2PeEdge.computeIfAbsent(XmlUtil.attributeValue(node, "id"), id -> new PeEdge(id));
        peEdge.from = id2PeNode.computeIfAbsent(XmlUtil.attributeValue(node, "sourceRef"), id -> new PeNode(id));
        peEdge.to = id2PeNode.computeIfAbsent(XmlUtil.attributeValue(node, "targetRef"), id -> new PeNode(id));
    }

    private void buildPeNode(Node node) {
        PeNode peNode = id2PeNode.computeIfAbsent(XmlUtil.attributeValue(node, "id"), id -> new PeNode(id));
        peNode.type = node.getNodeName();

        Node inPeEdgeNode = XmlUtil.childByName(node, "incoming");
        if (inPeEdgeNode != null)
            //text : 得到inPeEdgeNode的nodeValue
            peNode.in = id2PeEdge.computeIfAbsent(XmlUtil.text(inPeEdgeNode), id -> new PeEdge(id));

        Node outPeEdgeNode = XmlUtil.childByName(node, "outgoing");
        if (outPeEdgeNode != null)
            peNode.out = id2PeEdge.computeIfAbsent(XmlUtil.text(outPeEdgeNode), id -> new PeEdge(id));
    }
}

 

Em seguida, implemente a lógica principal do mecanismo de processo, o código é o seguinte:

public class ProcessEngine {
    private String xmlStr;

    public ProcessEngine(String xmlStr) {
        this.xmlStr = xmlStr;
    }

    public void run() throws Exception {
        PeProcess peProcess = new XmlPeProcessBuilder(xmlStr).build();

        PeNode node = peProcess.start;
        while (!node.type.equals("endEvent")) {
            if ("printHello".equals(node.type))
                System.out.print("Hello ");
            if ("printProcessEngine".equals(node.type))
                System.out.print("ProcessEngine ");

            node = node.out.to;
        }
    }
}

É isso? É isso para o mecanismo de fluxo de trabalho? Alunos, não entendam de forma tão simples, afinal, isso é apenas olá mundo, e a quantidade de vários códigos já é bastante.

Além disso, ainda há muito espaço para melhorias, como controle de exceção, generalização, padrões de projeto, etc., mas afinal, é apenas um alô mundo, o objetivo é facilitar o entendimento dos alunos e permitir que os alunos comecem .

Então, o próximo passo é chegar um pouco mais perto de alguns cenários específicos de aplicação prática e continuamos para a segunda iteração.

5. Aprovação simples

De um modo geral, o mecanismo de fluxo de trabalho pertence à tecnologia subjacente e aplicativos de fluxo de aprovação, fluxo de negócios, fluxo de dados etc. podem ser construídos sobre ele. Então, vamos pegar um cenário de aprovação simples na prática como exemplo e continuar aprofundando o mecanismo de fluxo de trabalho. Design, ok, aqui vamos nós.

1. Demanda

Como administrador de processos, gostaria que o mecanismo de processos executasse o processo mostrado na imagem abaixo para que eu possa configurar o processo para implementar um fluxo de aprovação simples.

 

Por exemplo: Xiao Zhang envia um formulário de inscrição e, em seguida, é aprovado pelo gerente. Após a aprovação, seja aprovada ou não, o resultado será enviado para Xiao Zhang na terceira etapa.

2. Análise

  • De um modo geral, esse processo ainda está em uma ordem linear, e basicamente parte do design da última iteração pode ser usada.
  • Os nós de aprovação podem levar muito tempo, até vários dias. A lógica do mecanismo de fluxo de trabalho chamando ativamente o próximo nó não é adequada para este cenário.
  • Com o aumento dos tipos de nós, a parte da lógica livre do tipo de nó escrita no mecanismo de fluxo de trabalho não é adequada.
  • As informações do formulário de inscrição, do aprovador e da notificação por e-mail do resultado também precisam do resultado da aprovação e outras informações durante a aprovação. Como transmitir essas informações também é um problema a ser considerado.

3. Projeto

  • Usando o mecanismo de registro, o tipo de nó e sua própria lógica são registrados no mecanismo de fluxo de trabalho, para que mais nós possam ser expandidos e o mecanismo de fluxo de trabalho e os nós possam ser desacoplados
  • O mecanismo de fluxo de trabalho adiciona lógica de unidade passiva, que permite que o mecanismo de fluxo de trabalho execute o próximo nó por meio de um
  • Adicione semântica de contexto e use-a como uma variável global para permitir que os dados fluam por cada nó

4. Perceba

A nova definição XML é a seguinte:

<definitions>
    <process id="process_2" name="简单审批例子">
        <startEvent id="startEvent_1">
            <outgoing>flow_1</outgoing>
        </startEvent>
        <sequenceFlow id="flow_1" sourceRef="startEvent_1" targetRef="approvalApply_1" />
        <approvalApply id="approvalApply_1" name="提交申请单">
            <incoming>flow_1</incoming>
            <outgoing>flow_2</outgoing>
        </approvalApply>
        <sequenceFlow id="flow_2" sourceRef="approvalApply_1" targetRef="approval_1" />
        <approval id="approval_1" name="审批">
            <incoming>flow_2</incoming>
            <outgoing>flow_3</outgoing>
        </approval>
        <sequenceFlow id="flow_3" sourceRef="approval_1" targetRef="notify_1"/>
        <notify id="notify_1" name="结果邮件通知">
            <incoming>flow_3</incoming>
            <outgoing>flow_4</outgoing>
        </notify>
        <sequenceFlow id="flow_4" sourceRef="notify_1" targetRef="endEvent_1"/>
        <endEvent id="endEvent_1">
            <incoming>flow_4</incoming>
        </endEvent>
    </process>
</definitions>

Em primeiro lugar, deve haver uma classe de objeto de contexto para passar variáveis, que é definida da seguinte forma:

public class PeContext {
    private Map<String, Object> info = new ConcurrentHashMap<>();

    public Object getValue(String key) {
        return info.get(key);
    }

    public void putValue(String key, Object value) {
        info.put(key, value);
    }
}

A lógica de processamento de cada nó é diferente, e uma certa abstração deve ser feita aqui.Para enfatizar que o papel dos nós no processo é o processamento lógico, é introduzido um novo operador de tipo, que é definido da seguinte forma:

public interface IOperator {
    //引擎可以据此来找到本算子
    String getType();

    //引擎调度本算子
    void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext);
}

Para o mecanismo, quando encontra um nó, ele precisa ser agendado, mas como agendá-lo? Primeiro, cada operador de nó precisa ser registrado (registerNodeProcessor()), para que o operador a ser escalonado possa ser encontrado.

Em segundo lugar, como o mecanismo sabe que a lógica do próprio operador do nó foi processada? De um modo geral, o motor não sabe, e só pode ser informado ao motor pelo operador, então o motor precisa fornecer uma função (nodeFinished()), que é chamada pelo operador.

Por fim, dissocie o agendamento das tarefas do operador do driver do motor e coloque-as em diferentes threads.

O código do ProcessEngine modificado é o seguinte:

public class ProcessEngine {
    private String xmlStr;

    //存储算子
    private Map<String, IOperator> type2Operator = new ConcurrentHashMap<>();
    private PeProcess peProcess = null;
    private PeContext peContext = null;

    //任务数据暂存
    public final BlockingQueue<PeNode> arrayBlockingQueue = new LinkedBlockingQueue();
    //任务调度线程
    public final Thread dispatchThread = new Thread(() -> {
        while (true) {
            try {
                PeNode node = arrayBlockingQueue.take();
                type2Operator.get(node.type).doTask(this, node, peContext);
            } catch (Exception e) {
            }
        }
    });

    public ProcessEngine(String xmlStr) {
        this.xmlStr = xmlStr;
    }

    //算子注册到引擎中,便于引擎调用之
    public void registNodeProcessor(IOperator operator) {
        type2Operator.put(operator.getType(), operator);
    }

    public void start() throws Exception {
        peProcess = new XmlPeProcessBuilder(xmlStr).build();
        peContext = new PeContext();

        dispatchThread.setDaemon(true);
        dispatchThread.start();

        executeNode(peProcess.start.out.to);
    }

    private void executeNode(PeNode node) {
        if (!node.type.equals("endEvent"))
            arrayBlockingQueue.add(node);
        else
            System.out.println("process finished!");
    }

    public void nodeFinished(String peNodeID) {
        PeNode node = peProcess.peNodeWithID(peNodeID);
        executeNode(node.out.to);
    }
}

Em seguida, implementação simples (rudimentar) dos três operadores necessários para este exemplo, o código é o seguinte:

/**
 * 提交申请单
 */
public class OperatorOfApprovalApply implements IOperator {
    @Override
    public String getType() {
        return "approvalApply";
    }

    @Override
    public void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {
        peContext.putValue("form", "formInfo");
        peContext.putValue("applicant", "小张");

        processEngine.nodeFinished(node.id);
    }
}

/**
 * 审批
 */
public class OperatorOfApproval implements IOperator {
    @Override
    public String getType() {
        return "approval";
    }

    @Override
    public void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {
        peContext.putValue("approver", "经理");
        peContext.putValue("message", "审批通过");

        processEngine.nodeFinished(node.id);
    }
}

/**
 * 结果邮件通知
 */
public class OperatorOfNotify implements IOperator {
    @Override
    public String getType() {
        return "notify";
    }

    @Override
    public void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {

        System.out.println(String.format("%s 提交的申请单 %s 被 %s 审批,结果为 %s",
                peContext.getValue("applicant"),
                peContext.getValue("form"),
                peContext.getValue("approver"),
                peContext.getValue("message")));

        processEngine.nodeFinished(node.id);
    }
}

Execute-o e veja qual é o resultado, o código é o seguinte:

public class ProcessEngineTest {

    @Test
    public void testRun() throws Exception {
        //读取文件内容到字符串
        String modelStr = Tools.readResoucesFile("model/two/hello.xml");
        ProcessEngine processEngine = new ProcessEngine(modelStr);

        processEngine.registNodeProcessor(new OperatorOfApproval());
        processEngine.registNodeProcessor(new OperatorOfApprovalApply());
        processEngine.registNodeProcessor(new OperatorOfNotify());

        processEngine.start();

        Thread.sleep(1000 * 1);

    }

}

 

小张 提交的申请单 formInfo 被 经理 审批,结果为 审批通过
process finished!

 

Neste ponto, a lógica central do mecanismo de fluxo de trabalho leve está quase introduzida. No entanto, é muito fino para suportar apenas a estrutura de sequência. Sabemos que as três estruturas básicas do fluxo do programa são sequência, ramificação e loop. Com essas três A estrutura pode basicamente representar a maior parte da lógica do processo. O laço pode ser considerado como uma estrutura combinada, ou seja: o laço pode ser derivado da sequência e do ramo. Implementamos a sequência, então só precisamos implementar o ramo, e existem vários tipos de ramos, como : escolha um de dois, escolha um de N , N escolha M (1<=M<=N), onde N escolhe um pode ser derivado da combinação de duas alternativas, e N escolhe M também pode ser derivado da combinação de duas alternativas, mas é mais detalhado e não tão intuitivo.Portanto, podemos satisfazer a maioria dos cenários de lógica de processo desde que implementemos o branch de duas escolhas.Bem, a terceira iteração começa.

 

6. Aprovação geral

Como administrador de processos, gostaria que o mecanismo de processos executasse o processo mostrado na imagem abaixo para que eu possa configurar o processo para implementar um fluxo de aprovação geral.

 

Por exemplo: Xiao Zhang envia um formulário de inscrição, que é aprovado pelo gerente. Após a aprovação, se a inscrição for aprovada, uma notificação por e-mail será enviada. Se a inscrição não for aprovada, ele ligará de volta e reescreverá o formulário de inscrição até que seja aprovado.

1. Análise

  • Um nó de ramificação precisa ser introduzido, que pode realizar uma transferência simples de duas vias
  • Os nós têm mais de uma borda de entrada e saída
  • Requer uma semântica de expressão lógica que pode configurar nós de ramificação

2. Projeto

  • Os nós devem suportar bordas de entrada e saída múltiplas
  • Operador de nó para decidir de qual borda de saída sair
  • Use um mecanismo de regras simples que suporte a análise de expressões lógicas simples
  • Definição XML de nó de ramificação simples

3. Perceba

A nova definição XML é a seguinte:

<definitions>
    <process id="process_2" name="简单审批例子">
        <startEvent id="startEvent_1">
            <outgoing>flow_1</outgoing>
        </startEvent>
        <sequenceFlow id="flow_1" sourceRef="startEvent_1" targetRef="approvalApply_1"/>
        <approvalApply id="approvalApply_1" name="提交申请单">
            <incoming>flow_1</incoming>
            <incoming>flow_5</incoming>
            <outgoing>flow_2</outgoing>
        </approvalApply>
        <sequenceFlow id="flow_2" sourceRef="approvalApply_1" targetRef="approval_1"/>
        <approval id="approval_1" name="审批">
            <incoming>flow_2</incoming>
            <outgoing>flow_3</outgoing>
        </approval>
        <sequenceFlow id="flow_3" sourceRef="approval_1" targetRef="simpleGateway_1"/>
        <simpleGateway id="simpleGateway_1" name="简单是非判断">
            <trueOutGoing>flow_4</trueOutGoing>
            <expr>approvalResult</expr>
            <incoming>flow_3</incoming>
            <outgoing>flow_4</outgoing>
            <outgoing>flow_5</outgoing>
        </simpleGateway>
        <sequenceFlow id="flow_5" sourceRef="simpleGateway_1" targetRef="approvalApply_1"/>
        <sequenceFlow id="flow_4" sourceRef="simpleGateway_1" targetRef="notify_1"/>
        <notify id="notify_1" name="结果邮件通知">
            <incoming>flow_4</incoming>
            <outgoing>flow_6</outgoing>
        </notify>
        <sequenceFlow id="flow_6" sourceRef="notify_1" targetRef="endEvent_1"/>
        <endEvent id="endEvent_1">
            <incoming>flow_6</incoming>
        </endEvent>
    </process>
</definitions>

 

Entre eles, o nó de ramificação simples simpleGateway é adicionado para representar uma ramificação alternativa simples.Quando a expressão em expr for verdadeira, vá para a borda de saída em trueOutGoing, caso contrário, vá para a outra borda de saída.

O nó suporta múltiplas arestas de entrada e múltiplas arestas de saída. O PeNode modificado é o seguinte:

public class PeNode {
    public String id;

    public String type;
    public List<PeEdge> in = new ArrayList<>();
    public List<PeEdge> out = new ArrayList<>();
    public Node xmlNode;

    public PeNode(String id) {
        this.id = id;
    }

    public PeEdge onlyOneOut() {
        return out.get(0);
    }

    public PeEdge outWithID(String nextPeEdgeID) {
        return out.stream().filter(e -> e.id.equals(nextPeEdgeID)).findFirst().get();
    }

    public PeEdge outWithOutID(String nextPeEdgeID) {
        return out.stream().filter(e -> !e.id.equals(nextPeEdgeID)).findFirst().get();
    }

}

No passado, quando havia apenas uma borda de saída, o nó atual determinava o próximo nó. Agora que há mais bordas de saída, cabe à borda decidir qual é o próximo nó. O código do mecanismo de processo modificado é o seguinte :

public class ProcessEngine {
    private String xmlStr;

    //存储算子
    private Map<String, IOperator> type2Operator = new ConcurrentHashMap<>();
    private PeProcess peProcess = null;
    private PeContext peContext = null;

    //任务数据暂存
    public final BlockingQueue<PeNode> arrayBlockingQueue = new LinkedBlockingQueue();
    //任务调度线程
    public final Thread dispatchThread = new Thread(() -> {
        while (true) {
            try {
                PeNode node = arrayBlockingQueue.take();
                type2Operator.get(node.type).doTask(this, node, peContext);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    });

    public ProcessEngine(String xmlStr) {
        this.xmlStr = xmlStr;
    }

    //算子注册到引擎中,便于引擎调用之
    public void registNodeProcessor(IOperator operator) {
        type2Operator.put(operator.getType(), operator);
    }

    public void start() throws Exception {
        peProcess = new XmlPeProcessBuilder(xmlStr).build();
        peContext = new PeContext();

        dispatchThread.setDaemon(true);
        dispatchThread.start();

        executeNode(peProcess.start.onlyOneOut().to);
    }

    private void executeNode(PeNode node) {
        if (!node.type.equals("endEvent"))
            arrayBlockingQueue.add(node);
        else
            System.out.println("process finished!");
    }

    public void nodeFinished(PeEdge nextPeEdgeID) {
        executeNode(nextPeEdgeID.to);
    }
}

O operador de nó simpleGateway recém-adicionado é o seguinte:

/**
 * 简单是非判断
 */
public class OperatorOfSimpleGateway implements IOperator {
    @Override
    public String getType() {
        return "simpleGateway";
    }

    @Override
    public void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {
        ScriptEngineManager manager = new ScriptEngineManager();
        ScriptEngine engine = manager.getEngineByName("js");
        engine.put("approvalResult", peContext.getValue("approvalResult"));

        String expression = XmlUtil.childTextByName(node.xmlNode, "expr");
        String trueOutGoingEdgeID = XmlUtil.childTextByName(node.xmlNode, "trueOutGoing");

        PeEdge outPeEdge = null;
        try {
            outPeEdge = (Boolean) engine.eval(expression) ?
                    node.outWithID(trueOutGoingEdgeID) : node.outWithOutID(trueOutGoingEdgeID);
        } catch (ScriptException e) {
            e.printStackTrace();
        }

        processEngine.nodeFinished(outPeEdge);
    }
}

Entre eles, o script js é usado simplesmente como expressão, claro, as desvantagens não são expandidas aqui.

Para facilitar o CC+CV dos alunos, outros códigos que foram alterados em conformidade são os seguintes:

/**
 * 审批
 */
public class OperatorOfApproval implements IOperator {
    @Override
    public String getType() {
        return "approval";
    }

    @Override
    public void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {
        peContext.putValue("approver", "经理");

        Integer price = (Integer) peContext.getValue("price");
        //价格<=200审批才通过,即:approvalResult=true
        boolean approvalResult = price <= 200;
        peContext.putValue("approvalResult", approvalResult);

        System.out.println("approvalResult :" + approvalResult + ",price : " + price);

        processEngine.nodeFinished(node.onlyOneOut());
    }
}

/**
 * 提交申请单
 */
public class OperatorOfApprovalApply implements IOperator {

    public static int price = 500;

    @Override
    public String getType() {
        return "approvalApply";
    }

    @Override
    public void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {
        //price每次减100
        peContext.putValue("price", price -= 100);
        peContext.putValue("applicant", "小张");

        processEngine.nodeFinished(node.onlyOneOut());
    }
}


/**
 * 结果邮件通知
 */
public class OperatorOfNotify implements IOperator {
    @Override
    public String getType() {
        return "notify";
    }

    @Override
    public void doTask(ProcessEngine processEngine, PeNode node, PeContext peContext) {

        System.out.println(String.format("%s 提交的申请单 %s 被 %s 审批,结果为 %s",
                peContext.getValue("applicant"),
                peContext.getValue("price"),
                peContext.getValue("approver"),
                peContext.getValue("approvalResult")));

        processEngine.nodeFinished(node.onlyOneOut());
    }
}


public class XmlPeProcessBuilder {
    private String xmlStr;
    private final Map<String, PeNode> id2PeNode = new HashMap<>();
    private final Map<String, PeEdge> id2PeEdge = new HashMap<>();

    public XmlPeProcessBuilder(String xmlStr) {
        this.xmlStr = xmlStr;
    }

    public PeProcess build() throws Exception {
        //strToNode : 把一段xml转换为org.w3c.dom.Node
        Node definations = XmlUtil.strToNode(xmlStr);
        //childByName : 找到definations子节点中nodeName为process的那个Node
        Node process = XmlUtil.childByName(definations, "process");
        NodeList childNodes = process.getChildNodes();

        for (int j = 0; j < childNodes.getLength(); j++) {
            Node node = childNodes.item(j);
            //#text node should be skip
            if (node.getNodeType() == Node.TEXT_NODE) continue;

            if ("sequenceFlow".equals(node.getNodeName()))
                buildPeEdge(node);
            else
                buildPeNode(node);
        }
        Map.Entry<String, PeNode> startEventEntry = id2PeNode.entrySet().stream().filter(entry -> "startEvent".equals(entry.getValue().type)).findFirst().get();
        return new PeProcess(startEventEntry.getKey(), startEventEntry.getValue());
    }

    private void buildPeEdge(Node node) {
        //attributeValue : 找到node节点上属性为id的值
        PeEdge peEdge = id2PeEdge.computeIfAbsent(XmlUtil.attributeValue(node, "id"), id -> new PeEdge(id));
        peEdge.from = id2PeNode.computeIfAbsent(XmlUtil.attributeValue(node, "sourceRef"), id -> new PeNode(id));
        peEdge.to = id2PeNode.computeIfAbsent(XmlUtil.attributeValue(node, "targetRef"), id -> new PeNode(id));
    }

    private void buildPeNode(Node node) {
        PeNode peNode = id2PeNode.computeIfAbsent(XmlUtil.attributeValue(node, "id"), id -> new PeNode(id));
        peNode.type = node.getNodeName();
        peNode.xmlNode = node;

        List<Node> inPeEdgeNodes = XmlUtil.childsByName(node, "incoming");
        inPeEdgeNodes.stream().forEach(n -> peNode.in.add(id2PeEdge.computeIfAbsent(XmlUtil.text(n), id -> new PeEdge(id))));

        List<Node> outPeEdgeNodes = XmlUtil.childsByName(node, "outgoing");
        outPeEdgeNodes.stream().forEach(n -> peNode.out.add(id2PeEdge.computeIfAbsent(XmlUtil.text(n), id -> new PeEdge(id))));
    }
}

Execute-o e veja qual é o resultado, o código é o seguinte:

public class ProcessEngineTest {

    @Test
    public void testRun() throws Exception {
        //读取文件内容到字符串
        String modelStr = Tools.readResoucesFile("model/third/hello.xml");
        ProcessEngine processEngine = new ProcessEngine(modelStr);

        processEngine.registNodeProcessor(new OperatorOfApproval());
        processEngine.registNodeProcessor(new OperatorOfApprovalApply());
        processEngine.registNodeProcessor(new OperatorOfNotify());
        processEngine.registNodeProcessor(new OperatorOfSimpleGateway());

        processEngine.start();

        Thread.sleep(1000 * 1);
    }

}

 

approvalResult :false,price : 400
approvalResult :false,price : 300
approvalResult :true,price : 200
小张 提交的申请单 200  经理 审批,结果为 true
process finished!

 

Até agora, a implementação deste requisito foi concluída.Além de implementar diretamente a semântica de ramificação, podemos ver que a semântica de loop também é implementada indiretamente aqui.

Como um mecanismo de fluxo de trabalho leve, este é basicamente o fim da história. Em seguida, vamos fazer um resumo e uma perspectiva.

 

7. Resumo e Perspectivas

Após as três iterações acima, podemos obter uma estrutura de mecanismo de fluxo de trabalho relativamente estável, conforme mostrado na figura a seguir:

 

A partir desta figura, podemos ver que há uma camada de mecanismo relativamente estável e, para fornecer escalabilidade, uma camada de operador de nó é fornecida e todas as adições de operadores de nó estão aqui.

Além disso, há algum grau de inversão de controle, ou seja: o operador decide para onde ir em seguida, não o motor. Desta forma, a flexibilidade do motor é muito melhorada e o encapsulamento é melhor.

Por fim, os contextos são usados, fornecendo um mecanismo de variáveis ​​globais para facilitar o fluxo de dados entre os nós.

Obviamente, as três iterações acima estão longe dos cenários reais de aplicativos online, e os seguintes pontos precisam ser percebidos e esperados, como segue:

  • Consideração e design de algumas situações anormais
  • O nó deve ser abstraído em uma função, com parâmetros de entrada, parâmetros de saída, tipos de dados, etc.
  • Adicione pontos enterrados em locais-chave para controlar o motor ou eventos de cuspir
  • Verificação de legitimidade semântica de gráficos, xsd, técnicas de verificação personalizadas, etc.
  • Detecção do algoritmo dag do gráfico
  • Histórico do processo do processo e reversão para qualquer nó
  • Modificação dinâmica do fluxograma, ou seja, o fluxograma pode ser modificado após o início do processo
  • Considerações no caso de modificações simultâneas
  • Considerações de eficiência
  • Para evitar a perda de informações de circulação após o reinício, é necessária a adição de um mecanismo de persistência
  • Cancelamento de processo, reset, entrada variável, etc.
  • Mecanismo de regras mais adequado e implementação e configuração de vários mecanismos de regras
  • Tela de front-end, definição e conversão da estrutura de dados do processo de front-end e back-end

 

Autor: Liu Yang

{{o.name}}
{{m.name}}

Acho que você gosta

Origin my.oschina.net/u/4090830/blog/5580501
Recomendado
Clasificación