Site Color

Text Color

Ad Color

Text Color

Evergreen

Duotone

Mysterious

Classic

Sign Up to Save Your Colors

or

Serverless and Bitcoin — creating price watchers dynamically by@jeffhollan

Serverless and Bitcoin — creating price watchers dynamically

image
Jeff Hollan Hacker Noon profile picture

Jeff Hollan

Senior Program Manager

Bitcoin has reached a new level of publicity in the last month. If you have spent any time looking at Bitcoin, you know it is very volatile and prices can fluctuate up and down in minutes. I’m a fairly risk-averse person so I’m not putting much of my personal money into Bitcoin, but was fortunate enough to buy a small amount back in 2013 as an experiment which has grown quite a bit. I’ve been doing a little bit of trading and exchanging to make the most of it — but struggle to stay up with current prices given heavy variability.

Instead of having to constantly check Coinbase or Bittrex, I wanted to get alerts on the coins I had or wanted. I wanted a way to automate purchase or sale of coins when the right thresholds were crossed. What I set out to build the other night was a serverless cryptocurrency watcher — one that I could assign new watchers just by sending a text like “watch btc 18000” from my phone — and it would alert me when the coin crossed that limit. I’m happy to say I built just that this week, and have been using successfully ever since. Here’s how it came together.

The pattern for serverless “watching”

Watching for events in serverless can be fairly straight forward when you have a single or static number of watchers. For example, the other week I built an Azure Function to watch for new events from my ring.com doorbell. The pattern there was a single function on a timer that would wake up and check for items. However, in this case I wanted to have a dynamic ‘pool’ of watchers. One day I may request a watcher to watch Ethereum if it crosses $813, and three hours later want a parallel watcher to be checking Litecoin. While the task itself seems simple enough — building this hosted durably in the cloud can be tricky. How do you spin up new watchers? How long can a watcher run? Are they durable? Can a watcher be terminated? And additionally when working with serverless: How can I make my watcher exist for longer than the traditional 5–10 minutes?

I considered a few options to build this out on the serverless stack:

  1. Create new Azure Functions for each watcher. There would be a “master” function that would receive the text message requests, and then deploy a full Azure Function on a timer trigger based on the threshold I want. Downsides: Not a “first class” pattern for this type of scenario. Creating a brand new function is a relatively expensive operation — especially for something as short-lived as I wanted these watchers to be (only watch for about a week, and once the threshold has been crossed it should go away). Dealing with teardown and management of workers could get quite complex with this option.
  2. Use Azure Logic Apps to fire off an orchestration of a “watcher” whenever a request comes in. Functionally all the pieces are there and this would work. Downsides: Much better than option 1, but would mean I need to manage both Azure Functions and Logic Apps as independent resources. Doing more complex conditions for things like the “while” loop can often be more complex than just writing a C# expression.
  3. Use Azure Durable Functions to orchestrate a “watcher” whenever a request comes in. Allows me to write Azure Functions that can exist for an indefinite amount of time. Very similar to option 2, yet all orchestration exists in function apps with the durable extension. Allows me to write orchestration logic in code alongside my functions in the same app. Downsides: Tricker to monitor the active watchers than Logic Apps where I can see all active runs, what step they are at, and failures. Also need to write all code for the steps instead of leveraging connectors like “Twilio” for SMS in Logic Apps.

Both 2 and 3 are perfectly valid, but since I wanted a little more flexibility into things like the “success condition” for a watcher, I went with Durable Functions. It was the most complex orchestration I’d built but it worked without any hiccups and happy with my choice.

Creating durable watchers with Durable Functions

The first function receives the request to start watching a coin, and starts a new watcher process. I created an HttpWebhook function which is listening to a Twilio webhook to notify my function whenever I send a text to a specific number. I parse the command sent (“watch” or “stop”), and then spin up a new orchestration for a watcher.

A coin watcher is an instance of a durable function. In this case the durable function is a while loop: “While the current price is less than the threshold price, keep checking the price.” To make sure I don’t have watchers checking for years, I also have a limit on the while loop (settable via an app setting, right now I have a single watcher timing out after 1 week if threshold isn’t hit). I also add in a delay so I don’t keep checking the price in rapid succession.

Here’s the code to the durable orchestrator for a coin watcher:

A few callouts worth amplifying: When a durable function runs, the way it manages running long-term processes in short-lived functions is by taking advantage of state retrieved in the context and replaying the function to resume at the next step. This means you need to be very careful in your durable function so that replays provide deterministic and consistent results. You’ll notice to get the current time, I’m actually leveraging the context with the expression context.CurrentUtcDateTime instead of something like DateTime.Now as the context will return the same value on replays so not to throw off other conditions.

I’m also adding in a delay during the while loop, so I’m not getting throttled by the exchange API (in this case, Bittrex). I add a 15 minute delay with the following line which pulls down the delay interval from my Azure App Settings (so I can customize without having to re-deploy).

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

What are the actions being called like “send_message” and “watcher_getticker?” Those are just simple Azure Functions like below:

Now whenever I text something like “watch eth 900” a durable instance will spin up and continue to check the price of Ethereum until it crosses $900 or the while loop times out (and sends me a nice text message to inform me it timed out if I want to renew).

Terminating an active instance

One of the most pleasant surprises when building this was how easy it was to terminate an active instance. You may notice from the screenshot above I provide a way to cancel a watcher by texting “STOP” and some ID. Whenever a durable orchestrator starts, it returns an instance ID. I easily could have just texted back “stop {instanceId}” and been done. But an instance ID is a GUID like fe5cb9b39e0445f4b751d95fc6410ade which would be a pain to type on a phone. So whenever I create an instance, I create an alias for it as well in Azure Table Storage. In my case, I generate a number between 1–1000. I know, I know.. this means collisions will happen 1/1000 times per phone number, but I only ever plan to have about 4 watchers active at a time and opted for ease of typing instead of high level of entropy. If someone wants to open a pull request to generate a 4 character case-sensitive alphanumeric alias I will gladly accept it 😀.

Once a stop command is received, I map it to the instance ID and can terminate the instance with a single line of code from my master function:

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

Just what I needed and was super easy to implement.

Extending the watcher model

This was really just the beginning of what I want to build out. I plan to extend the “send_event” function above to also emit an event to Azure Event Grid. As I supplement my request with more info, it could also trigger an automated purchase or sale of coins when a threshold is crossed.

I can’t state enough how much I love that I can create these little pieces of logic up in the cloud, have them run “all day, every day,” but only ever pay for the minutes of compute I actively consume.

The entire solution is checked into GitHub here, and should be compatible with the Azure Functions tools in Visual Studio 2017 if you want to deploy your own. It is written in .NET Core so compilable and runnable cross-platform as well.

Tags