Skip to main content

Exchange rate monitoring example

Introduction

Before we start geeking out over code, let’s take a step back and ask:

What’s the actual problem we’re solving? 🤔

Understanding the problem is like reading the instructions before assembling IKEA furniture. Skipping this step is like ending up with a bookshelf that’s actually a coffee table 🤦‍♂️. Sure, it’s functional, but it’s not what anyone wanted 🤷‍♂️.

Problem

Imagine this:

You’re gearing up for an epic trip to a foreign land, and you need to exchange some cash. But here’s the catch—you’re not about to settle for a bad exchange rate. Nope, you’re a savvy traveler who wants the best deal possible.

So, what do you do? You think, “Why not build a console app that keeps an eye on those sneaky exchange rates and tells me when it’s time to pounce?

With this app, you’ll have real-time updates on currency rates, so you can decide whether to exchange your money now or wait for a better deal. It’s like having a personal financial advisor, but way cheaper (and probably less judgmental).

Requirements

  • Every 30 seconds the latest exchange rate should be retrieved
  • The latest rate should be compared to previous rate and lowest, highest, average and current exchange rates should be displayed to the user
  • Alert the user if the current exchange rate is the lowest or if it's lower than the average exchange rate

Initial solution

Program.cs
using System.Text.Json;

var min = 0d;
var max = 0d;
var mean = 0d;

while (true)
{
using var httpClient = new HttpClient();
using var response = await httpClient.GetAsync("/totally-real-url");
await using var responseStream = await response.Content.ReadAsStreamAsync();
var deserializedResponse = await JsonSerializer.DeserializeAsync<dynamic>(responseStream);
double rate = deserializedResponse?.rate;
Console.WriteLine($"Current: {rate}");
Console.WriteLine($"Min: {min}");
Console.WriteLine($"Max: {max}");
Console.WriteLine($"Mean: {mean}");
if (rate < min)
{
Console.Beep(100, 100);
}
else if (rate < mean)
{
Console.Beep(200, 100);
}
min = Math.Min(min, rate);
max = Math.Max(max, rate);
mean = (mean + rate) / 2;
await Task.Delay(30000);
}

Our current solution is like a overstuffed suitcase—it’s one giant bundle of instructions that runs every 30 seconds, cramming in way too much stuff. But here’s the catch: if you dig into it, you’ll realize it’s trying to do way too many things at once.

Time to refactor this spaghetti code into something cleaner, modular, and actually understandable. Let’s break it down! 🧹✨

Improved solution

Program

Now, our program is as clean as a freshly organized desk—no more chaos, no more mystery. Each module has its own clear role, and we can easily see:

  • What each component does: No more guessing games.
  • Where it fits in the process: Like puzzle pieces snapping into place.
  • When it runs: Timing is crystal clear, no more surprises.

It’s like upgrading from a cluttered junk drawer to a well-labeled toolbox. Everything has its place, and everything just makes sense.

Program.cs
using System.Text.Json;

// Module initialization
var statisticsStore = new StatisticsStore();
var exchangeRateRetriever = new ExchangeRateRetriever();
var notificationManager = new NotificationManager();
var printer = new Printer();
var analyzer = new ExchangeRateAnalyzer();

// Main loop
while (true)
{
var exchangeRate = await exchangeRateRetriever.GetExchangeRateAsync();

printer.Print(statisticsStore, exchangeRate);

analyzer.Analyze(statisticsStore, notificationManager, exchangeRate);

statisticsStore.Update(exchangeRate);

await Task.Delay(TimeSpan.FromSeconds(30));
}

ExchangeRateRetriever

ExchangeRateRetriever.cs
/// <summary>
/// Component used to retrieve the latest exchange rate.
/// </summary>
public class ExchangeRateRetriever
{
/// <summary>
/// Retrieves the latest exchange rate from the REST API.
/// </summary>
/// <returns>The latest exchange rate.</returns>
public async Task<ExchangeRate> GetExchangeRateAsync()
{
using var httpClient = new HttpClient();
using var response = await httpClient.GetAsync("/totally-real-url");
await using var responseStream = await response.Content.ReadAsStreamAsync();
var exchangeRate = await JsonSerializer.DeserializeAsync<ExchangeRate>(responseStream);
return exchangeRate;
}
}

Printer

Printer.cs
/// <summary>
/// Component used to display information to the user.
/// </summary>
public class Printer
{
/// <summary>
/// Display the exchange rate information.
/// </summary>
/// <param name="statisticsStore">Component used to store and retrieve exchange rate statistics.</param>
/// <param name="exchangeRate">The current exchange rate.</param>
public void Print(StatisticsStore statisticsStore, ExchangeRate exchangeRate)
{
Console.WriteLine($"Current: {exchangeRate.Rate}");
Console.WriteLine($"Lowest: {statisticsStore.LowestRate}");
Console.WriteLine($"Highest: {statisticsStore.HighestRate}");
Console.WriteLine($"Average: {statisticsStore.AverageRate}");
}
}

Notificationmanager

NotificationManager.cs

/// <summary>
/// Component used to notify the user about any alerts.
/// </summary>
public class NotificationManager
{
/// <summary>
/// The amount of milliseconds the beep sound will be played.
/// </summary>
private const int DurationMs = 100;

/// <summary>
/// The frequency value for low priority notifications.
/// </summary>
private const int LowFrequency = 100;

/// <summary>
/// The frequency value for high priority notifications.
/// </summary>
private const int HighFrequency = 200;

/// <summary>
/// Notify the user about a low priority notification.
/// </summary>
public void SendLowPriorityNotification()
{
Console.Beep(LowFrequency, DurationMs);
}

/// <summary>
/// Notify the user about a high priority notification.
/// </summary>
public void SendHighPriorityNotification()
{
Console.Beep(HighFrequency, 100);
}
}

StatisticsStore

StatisticsStore.cs

/// <summary>
/// Component used to store and retrieve exchange rate statistics.
/// </summary>
public class StatisticsStore
{
/// <summary>
/// The lowest rate registered by this store.
/// </summary>
public double LowestRate { get; private set; } = double.MaxValue;

/// <summary>
/// The highest rate registered by this store.
/// </summary>
public double HighestRate { get; private set; } = double.MinValue;

/// <summary>
/// The average rate registered by this store.
/// </summary>
public double AverageRate { get; private set; } = 0d;

/// <summary>
/// Update registered values based on the current exchange rate.
/// </summary>
/// <param name="exchangeRate">The current exchange rate.</param>
public void Update(ExchangeRate exchangeRate)
{
LowestRate = double.Min(LowestRate, exchangeRate.Rate);
HighestRate = double.Max(HighestRate, exchangeRate.Rate);
AverageRate = (AverageRate + exchangeRate.Rate) / 2;
}
}

ExchangeRateAnalyzer

ExchangeRateAnalyzer.cs
/// <summary>
/// Component used to analyze the exchange rate and notify the user about values of interest.
/// </summary>
public class ExchangeRateAnalyzer
{
/// <summary>
/// Analyze the exchange rate and notify the user about values of interest.
/// </summary>
/// <param name="statisticsStore">Component used to store and retrieve exchange rate statistics.</param>
/// <param name="notificationManager">Component used to notify the user about any alerts.</param>
/// <param name="exchangeRate">The current exchange rate.</param>
public void Analyze(StatisticsStore statisticsStore, NotificationManager notificationManager, ExchangeRate exchangeRate)
{
if (exchangeRate.Rate < statisticsStore.LowestRate)
{
notificationManager.SendHighPriorityNotification();
}
else if (exchangeRate.Rate < statisticsStore.AverageRate)
{
notificationManager.SendLowPriorityNotification();
}
}
}

ExchangeRate

ExchangeRate.cs
/// <summary>
/// The model of the REST API response
/// </summary>
public class ExchangeRate
{
/// <summary>
/// The value of the exchange rate
/// </summary>
public double Rate { get; set; }
}