Serverless e Bitcoin – criando dinheiros dinamicamente

A Bitcoin alcançou um novo nível de publicidade no último mês. Se você passou algum tempo olhando para Bitcoin, você sabe que é muito volátil e os preços podem flutuar para cima e para baixo em minutos. Eu sou uma pessoa bastante aversa ao risco, então não estou colocando muito do meu dinheiro pessoal em Bitcoin, mas tive a sorte de comprar uma pequena quantidade em 2013 como um experimento que cresceu um pouco. Eu tenho feito um pouco de negociação e troco para aproveitar ao máximo – mas luta para ficar com os preços atuais dada a grande variabilidade.

Em vez de ter que verificar constantemente a Coinbase ou Bittrex, queria obter alertas sobre as moedas que eu tinha ou queria. Eu queria uma maneira de automatizar a compra ou a venda de moedas quando os limiares certos fossem cruzados. O que eu estabeleci para construir a outra noite era um observador de cryptocurrency sem servidor – um que eu poderia atribuir novos observadores apenas enviando um texto como "watch btc 18000" do meu telefone – e isso me avisaria quando a moeda cruzasse esse limite. Estou feliz em dizer que construí apenas essa semana, e tenho usado com sucesso desde então. É assim que se juntou.

O padrão para "assistir" sem servidor

A observação de eventos sem servidor pode ser bastante direta quando você tem um número único ou estático de observadores. Por exemplo, na outra semana eu criei uma Função Azure para assistir a novos eventos a partir da campainha do meu ring.com. O padrão havia uma única função em um temporizador que iria acordar e verificar itens. No entanto, neste caso, eu queria ter um "pool" dinâmico de observadores. Um dia, eu posso solicitar um observador para assistir Ethereum se ele atravessar US $ 813, e três horas depois quer um observador paralelo para verificar Litecoin. Embora a tarefa em si pareça suficientemente simples – construir isso hospedado de forma durável na nuvem pode ser complicado. Como você gera novos espectadores? Há quanto tempo um observador pode correr? Eles são duráveis? Um observador pode ser encerrado? E, adicionalmente, ao trabalhar com sem servidor: como posso fazer o meu observador por mais tempo do que os tradicionais 5-10 minutos?

Eu pensei em algumas opções para construir isso na pilha sem servidor:

  1. Crie novas funções Azure para cada observador. Haveria uma função "mestre" que receberia as solicitações de mensagens de texto e, em seguida, implantaria uma Função Azure completa em um gatilho temporizador com base no limite que eu quero. Desvantagens: não é um padrão de "primeira classe" para esse tipo de cenário. A criação de uma nova função é uma operação relativamente dispendiosa – especialmente para algo tão curto como eu queria que esses observadores fossem (apenas assistam por cerca de uma semana e, uma vez que o limite foi cruzado, deveria desaparecer). Lidar com destruição e gerenciamento de trabalhadores pode ser bastante complexo com esta opção.
  2. Use aplicativos de lógica Azure para disparar uma orquestração de um "observador" sempre que um pedido entrar. Funcionalmente, todas as peças estão lá e isso funcionaria. Desvantagens: muito melhor do que a opção 1, mas significaria que eu preciso gerenciar as Funções Azure e os Aplicativos Lógicos como recursos independentes. Fazer condições mais complexas para coisas como o loop "while" geralmente pode ser mais complexo do que apenas escrever uma expressão C #.
  3. Use funções duradouras Azure para orquestrar um "observador" sempre que uma solicitação entrar. Permite-me escrever funções Azure que podem existir por um tempo indefinido. Muito semelhante à opção 2, contudo toda a orquestração existe em aplicativos de função com a extensão durável. Permite-me escrever lógica de orquestração em código ao lado de minhas funções no mesmo aplicativo. Desvantagens: Tricker para monitorar os observadores ativos do que Logic Apps, onde eu posso ver todas as corridas ativas, em que passo eles estão e falhas. Também precisa escrever todo o código para as etapas em vez de alavancar conectores como "Twilio" para SMS em aplicativos de lógica.

Tanto os 2 quanto os 3 são perfeitamente válidos, mas, como queria um pouco mais de flexibilidade em coisas como a "condição de sucesso" para um observador, fui com funções duráveis. Foi a orquestração mais complexa que eu construí, mas funcionou sem qualquer soluço e feliz com minha escolha.

Criando observadores duráveis ??com funções duradouras

A primeira função recebe o pedido para começar a assistir uma moeda, e inicia um novo processo de vigilância. Eu criei uma função HttpWebhook que está ouvindo um webove do Twilio para notificar minha função sempre que envio um texto para um número específico. Eu analiso o comando enviado ("assistir" ou "parar"), e depois giro uma nova orquestra para um observador.

Um observador de moeda é uma instância de uma função durável. Neste caso, a função durável é um loop de tempo: "Enquanto o preço atual é menor que o preço de limiar, continue marcando o preço." Para garantir que não tenha observadores que verifiquem por anos, também tenho um limite no tempo loop (configurável através de uma configuração de aplicativo, agora eu tenho um único tempo de observação após 1 semana se o limite não for atingido). Eu também adiciono um atraso, então eu não continuo a verificar o preço em rápida sucessão.

Aqui está o código para o orquestrador durável para um observador de moeda:

Algumas indicações valem a ser ampliadas: quando uma função durável funciona, a maneira como ele administra executar processos de longo prazo em funções de curta duração é aproveitando o estado recuperado no context e reproduzindo a função para retomar no próximo passo. Isso significa que você precisa ter muito cuidado em sua função durável para que as repetições ofereçam resultados deterministas e consistentes. Você notará obter a hora atual, eu realmente estou alavancando o contexto com o context.CurrentUtcDateTime expressão.CurrentUtcDateTime em vez de algo como DateTime.Now como o context retornará o mesmo valor em repetições para não descartar outras condições.

Eu também estou adicionando um atraso durante o loop de tempo, então não estou ficando estrangulado pela API de troca (neste caso, Bittrex). Eu adiciono um atraso de 15 minutos com a seguinte linha, que puxa o intervalo de atraso das Configurações da Aplicação Azure (para que eu possa personalizar sem ter que re-implantar).

await context.CreateTimer(context.CurrentUtcDateTime.AddMinutes(double.Parse(Constants.DelayInterval)), CancellationToken.None);

Quais são as ações chamadas como "send_message" e "watcher_getticker?" Essas são apenas simples Funções Azure como abaixo:

Agora, sempre que escrevo algo como "watch eth 900", uma instância durável girará e continuará a verificar o preço do Ethereum até que ele cruza $ 900 ou o loop while termina (e me envia uma mensagem de texto agradável para me informar que expirou se Eu quero renovar).

Terminando uma instância ativa

Uma das surpresas mais agradáveis ??ao construir isso foi o quão fácil foi encerrar uma instância ativa. Você pode notar a partir da captura de tela acima, eu forneço uma maneira de cancelar um observador por mensagem de texto "STOP" e algum ID. Sempre que um orquestrador durável é iniciado, ele retorna um ID de instância. Eu facilmente poderia ter enviado mensagens de texto de volta "stop {instanceId}" e foi feito. Mas uma identificação de instância é um GUID como fe5cb9b39e0445f4b751d95fc6410ade que seria uma dor para digitar em um telefone. Então, sempre que eu crio uma instância, crio também um alias no Azure Table Storage. No meu caso, gere um número entre 1-1000. Eu sei, eu sei … isso significa que as colisões acontecerão 1/1000 vezes por número de telefone, mas eu sempre planejo ter cerca de 4 observadores ativos por vez e optando pela facilidade de digitação em vez de alto nível de entropia. Se alguém quiser abrir uma solicitação de puxar para gerar um alias alfanuméricos sensíveis a maiúsculas e minúsculas de 4 caracteres, irei com prazer.

Uma vez que um comando de parada é recebido, eu o mapeio para o ID da instância e pode terminar a instância com uma única linha de código da minha função mestre:

await starter.TerminateAsync(((Alias)result.Result).Id, “User requested terminate”);

Apenas o que eu precisava e era super fácil de implementar.

Estendendo o modelo observador

Este foi realmente o começo do que quero construir. Eu planejo estender a função "send_event" acima para também emitir um evento para Azure Event Grid. À medida que complemento meu pedido com mais informações, também pode desencadear uma compra automática ou venda de moedas quando um limite é cruzado.

Eu não posso declarar o quanto eu adoro que eu possa criar esses pequenos pedaços de lógica na nuvem, que eles funcionem "o dia todo, todos os dias", mas só pagam os minutos de cálculo que eu consome ativamente.

Toda a solução é verificada no GitHub aqui e deve ser compatível com as ferramentas de Funções do Azure no Visual Studio 2017 se você quiser implementar o seu próprio. Ele está escrito no .NET Core, assim compilável e executável multi-plataforma também.

https://github.com/jeffhollan/functions-durable-csharp-bittrex-watcher