Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of the task - code review #6

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

obiwanconobi
Copy link

No description provided.

<Project Sdk="Microsoft.NET.Sdk.Web">

<PropertyGroup>
<TargetFramework>net7.0</TargetFramework>
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

With net7.0 being "end of support", it would be good practice to update to the latest patch of net8.0.

var builder = WebApplication.CreateBuilder(args);
var app = builder.Build();

ExchangeRateService service = new ExchangeRateService();
Copy link
Author

@obiwanconobi obiwanconobi Aug 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 7: You'll want to use dependency injection rather than initialising the ExchangeRateService directly. This will help us when it comes to testing and if we ever need to swap implementations in the future.

ExchangeRateService service = new ExchangeRateService();

// REST API to get exchange rates based on input currencies
app.MapPost("/", service.GetExchangeRates);
Copy link
Author

@obiwanconobi obiwanconobi Aug 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 10: The endpoint is correctly mapped, however, for returning data you'll want to use a GET request instead of POST. Generally you'll only want to use POST for saving data.
In addition to this, although the current setup is fine for the current use, as the service grows it could get complicated. So think about moving this endpoints into its own controller class which should inherit from ControllerBase.
See more info here: https://learn.microsoft.com/en-us/aspnet/core/web-api/?view=aspnetcore-8.0

Comment on lines +14 to +23
public struct CnbRatesResponse
{
[JsonPropertyName("rates")] public List<Rate> Rates { get; set; }
}

public struct Rate
{
[JsonPropertyName("currencyCode")] public string CurrencyCode { get; set; }
[JsonPropertyName("rate")] public float RateValue { get; set; }
}
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

These look great, however moving these into their own files will improve the structure of the code, especially if we come to expand functionality with different data sources.


app.Run();

public struct CnbRatesResponse
Copy link
Author

@obiwanconobi obiwanconobi Aug 3, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Line 14: Using a class instead of a struct would also be more suitable here.
Here is some info of when to use a struct vs a class:
https://learn.microsoft.com/en-us/dotnet/standard/design-guidelines/choosing-between-class-and-struct

[JsonPropertyName("rate")] public float RateValue { get; set; }
}

internal class ExchangeRateService
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

To enhance testability and adhere to SOLID principles, consider making the ExchangeRateService public and implementing an interface. This will make it easier to mock the service in unit tests and potentially swap implementations in the future.
To improve the code structure, it would be worth moving the ExchangeRateService into its file.


internal class ExchangeRateService
{
private const string MainCurrenciesUrl = "https://api.cnb.cz/cnbapi/exrates/daily";
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a good idea to move the URL into the config file or environment variable. This will mean if it changes in future we can update the URL without having to recompile the entire project.

{
private const string MainCurrenciesUrl = "https://api.cnb.cz/cnbapi/exrates/daily";

public IEnumerable<Rate> GetExchangeRates(List<string> currencies)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You're on the right track here, however making the method async will improve performance.


public IEnumerable<Rate> GetExchangeRates(List<string> currencies)
{
HttpClient client = new HttpClient();
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You might want to consider making HttpClient static as creating a new one for each request can have a performance impact as the service scales.

{
HttpClient client = new HttpClient();

var data = client.GetStringAsync(MainCurrenciesUrl).Result;
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Along with the change to make the method async on line 29, consider using await rather than ".Result".

It would also be worth adding some error handling, although everything works at the moment of creation, anything could change and it would be useful to have those errors logged.

HttpClient client = new HttpClient();

var data = client.GetStringAsync(MainCurrenciesUrl).Result;
var response = JsonSerializer.Deserialize<CnbRatesResponse>(data);
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You've got the right idea for deserializing the data, however some error handling here would also be useful. The format or makeup of the data may change in the future and as with line 33, its useful to have these errors logged.

var data = client.GetStringAsync(MainCurrenciesUrl).Result;
var response = JsonSerializer.Deserialize<CnbRatesResponse>(data);

return response.Rates.Select(rate => rate).Where(rate => currencies.Contains(rate.CurrencyCode));
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your LINQ query works as expected, however, the Select(rate => rate) is redundant and can be removed.

@obiwanconobi
Copy link
Author

For new projects setting up swagger can be useful for helping diagnosing issues. Here is some more information about swagger:
https://learn.microsoft.com/en-us/aspnet/core/tutorials/web-api-help-pages-using-swagger?view=aspnetcore-8.0

@obiwanconobi
Copy link
Author

Along with swagger, it would be useful to setup some unit tests, here are some examples of things we could test:

  • Test successful retrieval of exchange rates
  • Test filtering of rates based on requested currencies
  • Test handling of API errors (e.g., network issues, invalid responses)

@obiwanconobi
Copy link
Author

Here is an overview of my comment:

  • This is a good foundation to build from. The basic code works to achieve the objective, but can be improved.
  • Update to net8.0
  • Add asynchronous calls and implement error handling.
  • Move variables to config/env files.
  • Improve folder structure

Here is an example of how the folder structure could look:

ExchangeRateAPI/

├── Controllers/
│ └── ExchangeRatesController.cs

├── Services/
│ ├── Interfaces/
│ │ └── IExchangeRateService.cs
│ └── ExchangeRateService.cs

├── Models/
│ ├── CnbRatesResponse.cs
│ └── Rate.cs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants