Sin servidor y Bitcoin: creación de observadores de precios dinámicamente

Bitcoin ha alcanzado un nuevo nivel de publicidad en el último mes. Si ha dedicado algún tiempo a mirar Bitcoin, sabe que es muy volátil y los precios pueden fluctuar hacia arriba y hacia abajo en minutos. Soy una persona bastante reacia al riesgo, así que no estoy dedicando mucho de mi dinero personal a Bitcoin, pero tuve la suerte de comprar una pequeña cantidad en 2013 como un experimento que ha crecido bastante. He estado haciendo un poco de trading e intercambio para aprovecharlo al máximo, pero lucho por mantenerme al día con los precios actuales dada la gran variabilidad.

En lugar de tener que revisar constantemente Coinbase o Bittrex, quería recibir alertas sobre las monedas que tenía o quería. Quería una forma de automatizar la compra o venta de monedas cuando se cruzaban los umbrales correctos. Lo que me propuse construir la otra noche fue un observador de criptomonedas sin servidor, uno que podía asignar a los nuevos observadores simplemente enviando un mensaje de texto como "watch btc 18000" desde mi teléfono, y me alertaría cuando la moneda cruzara ese límite. Me complace decir que construí solo eso esta semana, y he estado usando con éxito desde entonces. Así es como se unieron.

El patrón para "mirar" sin servidor

Ver eventos en servidores sin servidor puede ser bastante directo cuando tienes un número único o estático de observadores. Por ejemplo, la otra semana construí una función de Azure para ver los nuevos eventos de mi timbre de ring.com. El patrón tenía una función única en un temporizador que se activaba y buscaba elementos. Sin embargo, en este caso, quería tener un "grupo" dinámico de observadores. Algún día puedo pedirle a un vigilante que vea Ethereum si cruza $ 813, y tres horas más tarde quiere que un observador paralelo esté revisando Litecoin. Si bien la tarea en sí misma parece lo suficientemente simple, construir esta alojada de forma duradera en la nube puede ser complicado. ¿Cómo se activan los nuevos observadores? ¿Cuánto tiempo puede correr un observador? ¿Son duraderos? ¿Puede un vigilante ser terminado? Y, además, cuando se trabaja sin servidor: ¿cómo puedo hacer que mi observador exista por más tiempo que los 5-10 minutos tradicionales?

Consideré algunas opciones para construir esto en la pila sin servidor:

  1. Crea nuevas funciones de Azure para cada observador. Habría una función "maestra" que recibiría las solicitudes de mensajes de texto, y luego implementaría una función de Azure completa en un disparador de temporizador basado en el umbral que quiero. Desventajas: no es un patrón de "primera clase" para este tipo de escenario. Crear una nueva función es una operación relativamente costosa, especialmente por algo tan efímero como yo quería que fuesen estos observadores (solo observé durante una semana, y una vez que se cruzó el umbral debería desaparecer). Lidiar con el derribo y la gestión de los trabajadores podría ser bastante complejo con esta opción.
  2. Usa las aplicaciones de Azure Logic para disparar una orquestación de un "observador" siempre que entre una solicitud. Funcionalmente todas las piezas están ahí y esto funcionaría. Desventajas: Mucho mejor que la opción 1, pero significaría que necesito administrar las funciones de Azure y las aplicaciones lógicas como recursos independientes. Hacer condiciones más complejas para cosas como el ciclo "while" a menudo puede ser más complejo que simplemente escribir una expresión C #.
  3. Use las Funciones duraderas de Azure para orquestar a un "observador" cada vez que reciba una solicitud. Me permite escribir funciones Azure que pueden existir por un período de tiempo indefinido. Muy similar a la opción 2, pero toda la orquestación existe en las aplicaciones de función con la extensión duradera. Me permite escribir lógica de orquestación en el código junto con mis funciones en la misma aplicación. Desventajas: Tricker para monitorear a los observadores activos que Logic Apps, donde puedo ver todas las carreras activas, en qué etapa están y fallas. También necesita escribir todo el código para los pasos en lugar de aprovechar conectores como "Twilio" para SMS en Logic Apps.

Tanto el 2 como el 3 son perfectamente válidos, pero como quería un poco más de flexibilidad en cosas como la "condición de éxito" para un observador, recurrí a las Funciones duraderas. Era la orquestación más compleja que había construido, pero funcionó sin contratiempos y feliz con mi elección.

Crear observadores duraderos con funciones duraderas

La primera función recibe la solicitud para comenzar a mirar una moneda e inicia un nuevo proceso de observación. Creé una función HttpWebhook que está escuchando un webhook de Twilio para notificar a mi función cada vez que envío un texto a un número específico. Analizo el comando enviado ("mirar" o "parar"), y luego hago girar una nueva orquestación para un observador.

Un observador de monedas es una instancia de una función duradera. En este caso, la función duradera es un ciclo while: "Si bien el precio actual es menor que el precio de umbral, siga revisando el precio". Para asegurarme de que no tengo vigilantes durante años, también tengo un límite en el tiempo loop (ajustable a través de la configuración de una aplicación, ahora tengo un tiempo de espera de un solo monitor después de 1 semana si no se alcanza el umbral). También agrego un retraso así que no sigo verificando el precio en una sucesión rápida.

Aquí está el código para el orquestador duradero para un observador de monedas:

Algunas afirmaciones que vale la pena amplificar: cuando se ejecuta una función duradera, la forma en que gestiona la ejecución de procesos a largo plazo en funciones de corta duración es aprovechando el estado recuperado en el context y reproduciendo la función para reanudar en el siguiente paso. Esto significa que debe tener mucho cuidado con su función duradera para que las repeticiones proporcionen resultados deterministas y consistentes. Notarás que para obtener la hora actual, en realidad estoy aprovechando el contexto con la expresión context.CurrentUtcDateTime lugar de algo como DateTime.Now ya que el context devolverá el mismo valor en las repeticiones para no descartar otras condiciones.

También estoy agregando un retraso durante el ciclo while, así que no estoy siendo acelerado por la API de intercambio (en este caso, Bittrex). Agrego un retraso de 15 minutos con la siguiente línea que reduce el intervalo de demora desde la configuración de mi aplicación Azure (para que pueda personalizarla sin tener que volver a implementarla).

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

¿Cómo se llaman las acciones como "send_message" y "watcher_getticker?" Esas son simples funciones de Azure como a continuación:

Ahora cada vez que texto algo como "watch eth 900" una instancia duradera se activará y continuará verificando el precio de Ethereum hasta que cruce $ 900 o que el tiempo while se agote (y me envíe un bonito mensaje de texto para informarme que se agotó el tiempo si Quiero renovar).

Terminar una instancia activa

Una de las sorpresas más agradables al construir esto fue lo fácil que era terminar una instancia activa. Puede observar en la captura de pantalla anterior que proporciono una forma de cancelar a un observador enviando un mensaje de texto con el mensaje "DETENER" y una identificación. Cada vez que se inicia un orquestador duradero, devuelve una ID de instancia. Fácilmente podría haber enviado un mensaje de texto "stop {instanceId}" y haberlo hecho. Pero una ID de instancia es un GUID como fe5cb9b39e0445f4b751d95fc6410ade que sería un dolor escribir en un teléfono. Entonces, cada vez que creo una instancia, también creo un alias en Azure Table Storage. En mi caso, genero un número entre 1-1000. Lo sé, lo sé … esto significa que las colisiones ocurrirán 1/1000 veces por número de teléfono, pero solo planeo tener 4 vigilantes activos a la vez y opté por la facilidad de mecanografía en lugar de un alto nivel de entropía. Si alguien quiere abrir una solicitud de extracción para generar un alias alfanumérico sensible a las mayúsculas y minúsculas de 4 caracteres, con mucho gusto lo aceptaré ?.

Una vez que se recibe un comando de detención, lo mapeo a la ID de la instancia y puedo terminar la instancia con una sola línea de código de mi función maestra:

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

Justo lo que necesitaba y fue súper fácil de implementar.

Extender el modelo de observador

Esto fue realmente solo el comienzo de lo que quiero construir. Planeo extender la función "send_event" arriba para también emitir un evento a Azure Event Grid. Como complemento mi solicitud con más información, también podría desencadenar una compra o venta automática de monedas cuando se supera un umbral.

No puedo afirmar lo mucho que me encanta que puedo crear estos pequeños elementos de la lógica en la nube, hacerlos funcionar "todo el día, todos los días", pero solo pagar los minutos de cálculo que consumo activamente.

Toda la solución está registrada aquí en GitHub , y debería ser compatible con las herramientas de Azure Functions en Visual Studio 2017 si desea implementar la suya propia. Está escrito en .NET Core tan compilable y ejecutable multiplataforma también.

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