Como criar um sistema de delivery baseado em máquina de estado

2022-01-18

Digamos que você precisa enviar um pacote para João. Você está na sua casa e João está na casa dele.

O que uma pessoa normal faria? Certamente usaria algum aplicativo de delivery para enviar o pacote até o João... Mas você não é uma pessoa normal, você desenvolve software, você faz as suas próprias ferramentas, você faz os seus próprios serviços!

E o mais importate: você reinventa a roda toda semana.

Então, o que você faria se precisasse enviar um pacote para João? Obviamente criaria um serviço de entrega de encomendas dentro da sua cidade 🤓.

Como você é hipster o suficiente, você vai implementar esse applicativo usando máquinas de estado!

##O MVP

Digamos que você está no ponto A e João está no ponto B. O primeiro passo é criar uma caixa preta onde você possa jogar a sua posição e a posição de João e obter o melhor trajeto para alguém fazer a entrega do seu pacote.

Veja no diagrama abaixo.

Descobrir o caminho até João não é uma tarefa fácil. Digamos que você delegue essa demanda para algum serviço terceiro. Vamos chamar o esquema que vai encapsular a lógica de roteamento de Pathfinder. Imagine Pathfinder como uma classe na sua linguagem de programação favorita, onde é exposto um método chamado Pathfinder#optimalPath que recebe dois pontos em um mapa -- as posições sua e de João.

Vamos desenhar no mapa para que fique mais claro o que Pathfinder vai fazer.

Depois de encontrar o melhor caminho entre a origem e o destino o seu sistema precisa fazer uma oferta pública para entregadores próximos, o primeiro entregador que aceitar a oferta, fica com a entrega.

Não considere o custo da entrega. Sonhe que você está em um mundo onde as pessoas fazem as coisas porque... querem.

Daqui em diante vamos começar a modelar a máquina de estados que vai controlar o fluxo de entrega do seu pacote. Para modelar a máquina de estados vamos usar o XState para facilitar a nossa vida.

Vamos começar importando a função createMachine da biblioteca xstate:

machine.js
import { createMachine } from "xstate";

Agora vamos criar a nossa máquina. É uma boa prática definir um identificador único para a especificação da máquina de estados, assim é mais fácil de debugar caso ocorra algum erro em tempo de execução.

Também vamos definir o estado inicial como IDLE.

machine.js
import { createMachine } from "xstate";

export const machine = createMachine({
  id: "awesome-delivery-machine",
  initial: "IDLE",
  states: {
    IDLE: {},
  }
});

Acima criamos a máquina de estados com um estado que faz nada, chamado de IDLE. Digamos que, quando a máquina receber o evento START_OFFERING o estado da máquina deve transicionar de IDLE para OFFERING.

OFFERING é o estado onde o transporte do seu pacote será ofertado para entregadores próximos. Vamos ignorar o mecanismo de oferta, imagine que essa responsabilidade será delegada para algum microsserviço -- lembre-se, você é hipster, deveria estar usando microsserviços.

machine.js
import { createMachine } from "xstate";

export const machine = createMachine({
  id: "awesome-delivery-machine",
  initial: "IDLE",
  states: {
    IDLE: {
      on: {
        START_OFFERING: "OFFERING",
      },
    },
    OFFERING: {}
  }
});

O desenho abaixo ilustra esse processo.

Quando alguma pessoa aceitar o transporte do seu pacote, a máquina de estados vai receber um evento chamado ACCEPT. Quando isso acontecer precisamos salvar informações do entregador.

Essas informações deveriam ser salvas em algum armazenamento como um banco de dados. Mas vamos salvar no contexto da máquina de estados. O XState oferece uma função bem legal chamada assign, essa função recebe uma definição de update no contexo.

Talvez um exemplo seja de grande ajuda, então veja só como ficaria a definição da nossa máquina:

machine.js
import { createMachine, assign } from "xstate";

export const machine = createMachine({
  id: "awesome-delivery-machine",
  context: {
    deliveryMan: null
  },
  initial: "IDLE",
  states: {
    IDLE: {
      on: {
        START_OFFERING: "OFFERING",
      },
    },
    OFFERING: {
      on: {
        ACCEPT: {
          target: "OFFER_ACCEPTED",
          actions: ["assignDeliveryMan"],
        },
      },
    },
    OFFER_ACCEPTED: {}
  }
}, {
  actions: {
    assignDeliveryMan: assign({
      deliveryMan: (_ctx, ev) => ev.payload.deliveryMan,
    }),
  }
});

Aqui, quando a máquina estiver no estado OFFERING e receber um evento ACCEPT, a máquina vai transicionar para o estado OFFER_ACCEPTED e executar a ação assignDeliveryMan.

A ação assignDeliveryMan recebe uma especificação de atualização de contexto. Cada chave do objeto de especificação da atualização de contexto representa um parâmetro no objeto context.

É justo você não entender na primeira, talvez nem na segunda tentativa. Mas juro que se você ler a documentação do XState tudo vai fazer sentido.

Por enquanto acredite que quando a função assignDeliveryMan for chamada, vai ser salvo no contexto da máquina de estados informações da pessoa que irá fazer o transporte do seu pacote até a cada de João.

⚠️ Atenção
No XState, ações são funções que são invocadas automaticamente mas que são esquecidas logo em seguida. Isso significa que caso a sua ação estourar um erro, a máquina vai ignorar o erro. Também significa que não há qualquer tipo de garantia de ordem ao processar ações.

Nosso diagrama até agora:

No estado OFFER_ACCEPTED vamos notificar o usuário que está tentando enviar o pacote -- você, e vamos transicionar diretamente para o estado PICKING.

PICKING significa que a pessoa responsável por fazer o transporte está se dirigindo até a sua casa. Nesse caso você esperaria ter um acompanhamento em real time da posição dessa pessoa no mapa.

Vamos ignorar o front-end; mas imagine a interface de qualquer aplicativo de delivery onde aparece o entregador navegando pelo mapa.

Na nossa máquina de estados, podemos definir estes novos comportamentos da seguinte forma:

machine.js
import { createMachine, assign } from "xstate";

export const machine = createMachine({
  id: "awesome-delivery-machine",
  context: {
    deliveryMan: null
  },
  initial: "IDLE",
  states: {
    IDLE: {
      on: {
        START_OFFERING: "OFFERING",
      },
    },
    OFFERING: {
      on: {
        ACCEPT: {
          target: "OFFER_ACCEPTED",
          actions: ["assignDeliveryMan"],
        },
      },
    },
    OFFER_ACCEPTED: {
      entry: ["notifyUserDeliveryHasAccepted"],
      always: "PICKING",
    },
    PICKING: {
      on: {
        UPDATE_POSITION: {
          target: "PICKING",
          actions: ["updateDeliveryPosition", "sendDeliveryManPosition"],
        },
        PICKED: {
          target: "DELIVERING",
        },
      },
    },
  }
}, {
  actions: {
    assignDeliveryMan: assign({
      deliveryMan: (_ctx, ev) => ev.payload.deliveryMan,
    }),

    updateDeliveryPosition: assign({
      deliveryManPosition: (_ctx, ev) => ev.payload.position,
    }),

    sendDeliveryManPosition(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" is at ${ctx.deliveryManPosition}`);
    },

    notifyUserDeliveryHasAccepted(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" has accepted the delivery`);
    },
  }
});

Aqui, a ação notifyUserDeliveryHasAccepted vai ser responsável por notificar você de que um entregador aceitou seu pedido de transporte.

O estado PICKING sabe lidar com dois eventos. O evento de atualização da posição do entregador no mapa UPDATE_POSITION e o evento de objeto coletado, PICKED. Sempre que o estado PICKING receber o evento UPDATE_POSITION, vai ser disparado uma ação que vai atualizar a posição do entregador no contexto e logo em seguida vai ser disparada uma ação para enviar a posição desse entregador para você.

Quando o entregador chegar na sua casa e você entregar o pacote ao entregador; você ou o entregador podem emitir o evento PICKED. Quando a máquina receber esse evento, ela vai transicionar para o estado DELIVERING.

Ainda não definimos o estado DELIVERING, mas este será o estado onde o entregador vai levar o seu pacote até o destino. Nesse estado muitas coisas podem acontecer. O entregador pode pegar um caminho errado, pode sumir do mapa, pode ser abduzido ou se transformar em um zumbi. Para cada um desses casos, poderíamos definir no estado DELIVERING transições para outros estados onde esses problemas serão tratados.

Por exemplo, caso o entregador vire um zumbi durante o trajeto, talvez seja interessante notificar você para se esconder, trancar as portas e não fazer barulho.

Veja abaixo como a nossa máquina de estados está tomando forma:

machine.js
import { createMachine, assign } from "xstate";

export const machine = createMachine({
  id: "awesome-delivery-machine",
  context: {
    deliveryMan: null
  },
  initial: "IDLE",
  states: {
    IDLE: {
      on: {
        START_OFFERING: "OFFERING",
      },
    },
    OFFERING: {
      on: {
        ACCEPT: {
          target: "OFFER_ACCEPTED",
          actions: ["assignDeliveryMan"],
        },
      },
    },
    OFFER_ACCEPTED: {
      entry: ["notifyUserDeliveryHasAccepted"],
      always: "PICKING",
    },
    PICKING: {
      on: {
        UPDATE_POSITION: {
          target: "PICKING",
          actions: ["updateDeliveryPosition", "sendDeliveryManPosition"],
        },
        PICKED: {
          target: "DELIVERING",
        },
      },
    },
    DELIVERING: {
      entry: ["notifyPackagePicked"],
      on: {
        UPDATE_POSITION: {
          target: "DELIVERING",
          actions: ["updateDeliveryPosition", "sendDeliveryManPosition"],
        },
        ARRIVED: {
          target: "ARRIVED",
        },
        TURNED_ZUMBI: {
          target: "END_OF_THE_WORLD",
          actions: ["notifyZumbiIsComming"]
        }
      },
    },
  }
}, {
  actions: {
    assignDeliveryMan: assign({
      deliveryMan: (_ctx, ev) => ev.payload.deliveryMan,
    }),

    updateDeliveryPosition: assign({
      deliveryManPosition: (_ctx, ev) => ev.payload.position,
    }),

    sendDeliveryManPosition(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" is at ${ctx.deliveryManPosition}`);
    },

    notifyUserDeliveryHasAccepted(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" has accepted the delivery`);
    },

    notifyPackagePicked(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" has picked the package`);
    },

    notifyZumbiIsComming() {
      console.log("RUN ZUMBIES ARE COMMING TO EAT YOU ALIVE")
    },
  }
});

Caso tudo ocorra como esperado, a máquina vai receber eventos constantes de atualização da posição do entregador. Em cada atualização de posição, a nova posição do entregador será enviada para você para que você possa acompanhar no mapa no seu celular.

Quando o entregador chegar no destino, a máquina vai receber um evento chamado ARRIVED e então vai transicionar para o estado ARRIVED. Sim, é possível que estados e eventos tenham o mesmo nome.

Caso o entregador se transforme em um zumbi durante a entrega, a máquina vai executar a ação notifyZumbiIsComming, no qual vai enviar uma notificação para você se esconder e então vai transicionar para o estado END_OF_THE_WORLD que é bem descritivo.

Podemos já definir o estado END_OF_THE_WORLD, por que esse é um estado final. Ou seja, depois que a máquina chega neste estado, ela não vai mais transicionar para nenhum outro estado.

machine.js
import { createMachine, assign } from "xstate";

export const machine = createMachine({
  id: "awesome-delivery-machine",
  context: {
    deliveryMan: null
  },
  initial: "IDLE",
  states: {
    IDLE: {
      on: {
        START_OFFERING: "OFFERING",
      },
    },
    OFFERING: {
      on: {
        ACCEPT: {
          target: "OFFER_ACCEPTED",
          actions: ["assignDeliveryMan"],
        },
      },
    },
    OFFER_ACCEPTED: {
      entry: ["notifyUserDeliveryHasAccepted"],
      always: "PICKING",
    },
    PICKING: {
      on: {
        UPDATE_POSITION: {
          target: "PICKING",
          actions: ["updateDeliveryPosition", "sendDeliveryManPosition"],
        },
        PICKED: {
          target: "DELIVERING",
        },
      },
    },
    DELIVERING: {
      entry: ["notifyPackagePicked"],
      on: {
        UPDATE_POSITION: {
          target: "DELIVERING",
          actions: ["updateDeliveryPosition", "sendDeliveryManPosition"],
        },
        ARRIVED: {
          target: "ARRIVED",
        },
        TURNED_ZUMBI: {
          target: "END_OF_THE_WORLD",
          actions: ["notifyZumbiIsComming"]
        }
      },
    },
    END_OF_THE_WORLD: {
      type: "final",
    },
  }
}, {
  actions: {
    assignDeliveryMan: assign({
      deliveryMan: (_ctx, ev) => ev.payload.deliveryMan,
    }),

    updateDeliveryPosition: assign({
      deliveryManPosition: (_ctx, ev) => ev.payload.position,
    }),

    sendDeliveryManPosition(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" is at ${ctx.deliveryManPosition}`);
    },

    notifyUserDeliveryHasAccepted(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" has accepted the delivery`);
    },

    notifyPackagePicked(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" has picked the package`);
    },

    notifyZumbiIsComming() {
      console.log("RUN ZUMBIES ARE COMMING TO EAT YOU ALIVE")
    },
  }
});

Ainda precisamos definir o estado ARRIVED que vai ser responsável pelos eventos de finalização da entrega. É esperado que quando o entregador chege no destino, que seja enviada uma notificação para o João. Lembra que você está tentando enviar um pacote para João?

machine.js
import { createMachine, assign } from "xstate";

export const machine = createMachine({
  id: "awesome-delivery-machine",
  context: {
    deliveryMan: null
  },
  initial: "IDLE",
  states: {
    IDLE: {
      on: {
        START_OFFERING: "OFFERING",
      },
    },
    OFFERING: {
      on: {
        ACCEPT: {
          target: "OFFER_ACCEPTED",
          actions: ["assignDeliveryMan"],
        },
      },
    },
    OFFER_ACCEPTED: {
      entry: ["notifyUserDeliveryHasAccepted"],
      always: "PICKING",
    },
    PICKING: {
      on: {
        UPDATE_POSITION: {
          target: "PICKING",
          actions: ["updateDeliveryPosition", "sendDeliveryManPosition"],
        },
        PICKED: {
          target: "DELIVERING",
        },
      },
    },
    DELIVERING: {
      entry: ["notifyPackagePicked"],
      on: {
        UPDATE_POSITION: {
          target: "DELIVERING",
          actions: ["updateDeliveryPosition", "sendDeliveryManPosition"],
        },
        ARRIVED: {
          target: "ARRIVED",
        },
        TURNED_ZUMBI: {
          target: "END_OF_THE_WORLD",
          actions: ["notifyZumbiIsComming"]
        }
      },
    },
    ARRIVED: {
      entry: ["notifyDestinationPackageArrived"],
      on: {
        RECEIVED: {
          target: "DELIVERED",
        },
      },
    },
    END_OF_THE_WORLD: {
      type: "final",
    },
  }
}, {
  actions: {
    assignDeliveryMan: assign({
      deliveryMan: (_ctx, ev) => ev.payload.deliveryMan,
    }),

    updateDeliveryPosition: assign({
      deliveryManPosition: (_ctx, ev) => ev.payload.position,
    }),

    sendDeliveryManPosition(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" is at ${ctx.deliveryManPosition}`);
    },

    notifyUserDeliveryHasAccepted(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" has accepted the delivery`);
    },

    notifyPackagePicked(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" has picked the package`);
    },

    notifyDestinationPackageArrived(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" arrived with your package.`);
    },

    notifyZumbiIsComming() {
      console.log("RUN ZUMBIES ARE COMMING TO EAT YOU ALIVE")
    },
  }
});

A ação notifyDestinationPackageArrived será responsável para notificar que o entregador chegou.

Quando João ou o entregador indicar que o pacote foi entregue, vai ser emitido o evento RECEIVED. E, quando a máquina receber o evento RECEIVED, deve transicionar para o estado DELIVERED e finalizar a entrega.

machine.js
import { createMachine, assign } from "xstate";

export const machine = createMachine({
  id: "awesome-delivery-machine",
  context: {
    deliveryMan: null
  },
  initial: "IDLE",
  states: {
    IDLE: {
      on: {
        START_OFFERING: "OFFERING",
      },
    },
    OFFERING: {
      on: {
        ACCEPT: {
          target: "OFFER_ACCEPTED",
          actions: ["assignDeliveryMan"],
        },
      },
    },
    OFFER_ACCEPTED: {
      entry: ["notifyUserDeliveryHasAccepted"],
      always: "PICKING",
    },
    PICKING: {
      on: {
        UPDATE_POSITION: {
          target: "PICKING",
          actions: ["updateDeliveryPosition", "sendDeliveryManPosition"],
        },
        PICKED: {
          target: "DELIVERING",
        },
      },
    },
    DELIVERING: {
      entry: ["notifyPackagePicked"],
      on: {
        UPDATE_POSITION: {
          target: "DELIVERING",
          actions: ["updateDeliveryPosition", "sendDeliveryManPosition"],
        },
        ARRIVED: {
          target: "ARRIVED",
        },
        TURNED_ZUMBI: {
          target: "END_OF_THE_WORLD",
          actions: ["notifyZumbiIsComming"]
        }
      },
    },
    ARRIVED: {
      entry: ["notifyDestinationPackageArrived"],
      on: {
        RECEIVED: {
          target: "DELIVERED",
        },
      },
    },
    DELIVERED: {
      type: "final",
      entry: "notifyOriginPackageDelivered",
    },
    END_OF_THE_WORLD: {
      type: "final",
    },
  }
}, {
  actions: {
    assignDeliveryMan: assign({
      deliveryMan: (_ctx, ev) => ev.payload.deliveryMan,
    }),

    updateDeliveryPosition: assign({
      deliveryManPosition: (_ctx, ev) => ev.payload.position,
    }),

    sendDeliveryManPosition(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" is at ${ctx.deliveryManPosition}`);
    },

    notifyUserDeliveryHasAccepted(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" has accepted the delivery`);
    },

    notifyPackagePicked(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" has picked the package`);
    },

    notifyDestinationPackageArrived(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" arrived with your package.`);
    },

    notifyOriginPackageDelivered(ctx) {
      console.log(`[LOG] "${ctx.deliveryMan.name}" delivered the package.`);
    },

    notifyZumbiIsComming() {
      console.log("RUN ZUMBIES ARE COMMING TO EAT YOU ALIVE")
    },
  }
});

Bem complicadinho não acha? Ignoramos vários casos aqui como lock e release do entregador. Durante o fluxo de entrega esse entregador não deve receber novas ofertas de entrega.

Porém talvez devido a alguma nova regra de negócio, próximo do fim da entrega talvez o entregador poderia começar a receber ofertas de novas entregas. Esse caso poderia ser modelado como um novo estado, como DELIVERY_ENDING por exemplo.

Custos também foram ignorados. Os custos de uma entrega devem ser dinâmicos. Se o entregador pegar muito trânsito, o custo da entrega deve ser maior quando comparado ao mesmo trajeto porém sem trânsito. Subir morros também consome mais gasolina, o preço da entrega também deveria aumentar nesse caso.

##Testando o MVP em ambiente controlado

Vamos colocar essa máquina para rodar! Vamos começar importando a função interpret do xstate. Essa função vai criar um interpretador da nossa máquina de estados.

index.js
import { interpret } from "xstate";

Agora vamos importar a máquina de estados:

index.js
import { interpret } from "xstate";
import { machine } from "./machine.js";

O próximo passo é criar o interpretador:

index.js
import { interpret } from "xstate";
import { machine } from "./machine.js";

const interpreter = interpret(machine)
  .onTransition((state, ev) => {
    console.log({
      state: state.value,
    });
  })
  .start();

Agora podemos começar a fazer mágica. Vamos enviar o evento START_OFFERING através do método send.

index.js
import { interpret } from "xstate";
import { machine } from "./machine.js";

const interpreter = interpret(machine)
  .onTransition((state, ev) => {
    console.log({
      state: state.value,
    });
  })
  .start();

interpreter.send("START_OFFERING");

Veja só a saída no terminal quando executamos esse script no Node:

$ node index.js
{ state: 'IDLE' }
{ state: 'OFFERING' }

Aparentemente a transição de estado já está funcionando. Vamos simular uma entrega completa.

index.js
import { interpret } from "xstate";
import { machine } from "./machine.js";

const interpreter = interpret(machine)
  .onTransition((state, ev) => {
    console.log({
      state: state.value,
    });
  })
  .start();

interpreter.send("START_OFFERING");

interpreter.send({
  type: "ACCEPT",
  payload: {
    deliveryMan: {
      id: "xxxxxyyyyyy",
      name: "Delivery Man Name",
    },
  },
});

interpreter.send({
  type: "UPDATE_POSITION",
  payload: {
    position: [0, 0],
  },
});
interpreter.send({
  type: "UPDATE_POSITION",
  payload: {
    position: [0, 1],
  },
});
interpreter.send({
  type: "UPDATE_POSITION",
  payload: {
    position: [0, 2],
  },
});

interpreter.send("PICKED");

interpreter.send({
  type: "UPDATE_POSITION",
  payload: {
    position: [0, 3],
  },
});

interpreter.send({
  type: "UPDATE_POSITION",
  payload: {
    position: [1, 3],
  },
});

interpreter.send({
  type: "UPDATE_POSITION",
  payload: {
    position: [2, 3],
  },
});

interpreter.send("ARRIVED");

interpreter.send("RECEIVED");

Executando o script novamente no terminal, temos a seguinte saída:

$ node index.js
{ state: 'IDLE' }
{ state: 'OFFERING' }
[LOG] "Delivery Man Name" has accepted the delivery
{ state: 'PICKING' }
[LOG] "Delivery Man Name" is at 0,0
{ state: 'PICKING' }
[LOG] "Delivery Man Name" is at 0,1
{ state: 'PICKING' }
[LOG] "Delivery Man Name" is at 0,2
{ state: 'PICKING' }
[LOG] "Delivery Man Name" has picked the package
{ state: 'DELIVERING' }
[LOG] "Delivery Man Name" is at 0,3
[LOG] "Delivery Man Name" has picked the package
{ state: 'DELIVERING' }
[LOG] "Delivery Man Name" is at 1,3
[LOG] "Delivery Man Name" has picked the package
{ state: 'DELIVERING' }
[LOG] "Delivery Man Name" is at 2,3
[LOG] "Delivery Man Name" has picked the package
{ state: 'DELIVERING' }
[LOG] "Delivery Man Name" arrived with your package.
{ state: 'ARRIVED' }
[LOG] "Delivery Man Name" delivered the package.
{ state: 'DELIVERED' }

Conseguimos ver toda a transição desde o início do fluxo até o fim, passando por atualizações de posicionamento do entregador!

Como eu disse antes, esse esquema é extremamente simples, mas já dá uma ideia do tamanho da complexidade de fazer tal esquema de entrega de produtos. Nem chegamos perto o suficiente pra sentir o cheirinho da complexidade de traçar rotas dentro da cidade e gerenciar de milhares a milhões de entregas simultâneas.

O esquema de interpretação da máquina de estados é fácil de testar e explorar, mas na vida real o processador de entrega deveria ser um ou mais serviços com alta disponibilidade; possivelmente escalado em dezenas (talvez centenas) de pods para aguentar a carga.

Por fim, não sei você, mas quero testar o que acontece se o entregador virar um zumbi no meio do transporte do pacote.

index.js
import { interpret } from "xstate";
import { machine } from "./machine.js";

const interpreter = interpret(machine)
  .onTransition((state, ev) => {
    console.log({
      state: state.value,
    });
  })
  .start();

interpreter.send("START_OFFERING");

interpreter.send({
  type: "ACCEPT",
  payload: {
    deliveryMan: {
      id: "xxxxxyyyyyy",
      name: "Delivery Man Name",
    },
  },
});

interpreter.send({
  type: "UPDATE_POSITION",
  payload: {
    position: [0, 0],
  },
});
interpreter.send({
  type: "UPDATE_POSITION",
  payload: {
    position: [0, 1],
  },
});
interpreter.send({
  type: "UPDATE_POSITION",
  payload: {
    position: [0, 2],
  },
});

interpreter.send("PICKED");

interpreter.send({
  type: "UPDATE_POSITION",
  payload: {
    position: [0, 3],
  },
});

interpreter.send({
  type: "TURNED_ZUMBI",
});

Agora executando no terminal:

$ node index.js
{ state: 'IDLE' }
{ state: 'OFFERING' }
[LOG] "Delivery Man Name" has accepted the delivery
{ state: 'PICKING' }
[LOG] "Delivery Man Name" is at 0,0
{ state: 'PICKING' }
[LOG] "Delivery Man Name" is at 0,1
{ state: 'PICKING' }
[LOG] "Delivery Man Name" is at 0,2
{ state: 'PICKING' }
[LOG] "Delivery Man Name" has picked the package
{ state: 'DELIVERING' }
[LOG] "Delivery Man Name" is at 0,3
[LOG] "Delivery Man Name" has picked the package
{ state: 'DELIVERING' }
RUN ZUMBIES ARE COMMING TO EAT YOU ALIVE
{ state: 'END_OF_THE_WORLD' }

It's the end game

#arquitetura

📝 Edite esta página