마이크로소프트 스택 위에서 결제 백엔드, SaaS 빌링 엔진, 또는 엔터프라이즈 가격 책정 서비스를 만들고 있다면, 이번 주에 출시할 수 있는 가장 임팩트 큰 작업 중 하나가 C# / .NET 환율 API 통합입니다. 현대 .NET은 필요한 모든 것을 기본 제공합니다 — HttpClient, IHttpClientFactory, System.Text.Json, IMemoryCache, 그리고 일급 의존성 주입 — 그런데도 대부분의 튜토리얼은 어떤 운영 코드 리뷰도 통과하지 못할 WebClient.DownloadString 한 줄 예제만 보여줍니다. 이 가이드는 첫 요청부터 ASP.NET Core, Blazor 앱, WPF 데스크톱 도구, Azure Function 등에 그대로 투입할 수 있는 타입 안전·캐시·복원력 있는 클라이언트까지 안내합니다.
이 튜토리얼이 끝날 즈음에는 Finexly API 문서에 접근하는 재사용 가능한 Finexly 클라이언트, 작은 콘솔 환산기, 그리고 스택 전체에 실시간 환율을 제공하는 ASP.NET Core Minimal API를 갖추게 됩니다 — 모두 마이크로소프트가 직접 권장하는 패턴을 따른 관용적인 .NET 8 / .NET 9 코드입니다.
왜 C# / .NET이 통화 통합에 강력한 선택인가
운영 환경의 통화 변환은 주로 I/O 문제입니다 — 환율 가져오기, 캐싱, 소수 곱셈, JSON 반환. .NET 런타임은 지난 10년간 정확히 이 워크로드에 맞춰 최적화돼 왔습니다:
IHttpClientFactory는 소켓 고갈을 해결합니다.HttpMessageHandler인스턴스를 풀링해서, 소켓을 누수하지 않고 어디서든HttpClient를 주입할 수 있습니다 — 레거시 .NET 코드에서 보이는 환율 API 간헐적 실패의 가장 큰 원인입니다.System.Text.Json은 빠르고 할당이 적습니다. 일반적인 170개 통화 응답을 1밀리초보다 훨씬 짧은 시간에, 서드파티 의존성 없이 역직렬화합니다.decimal은 언어에 내장돼 있습니다. 돈을 정확히 다루기 위해 외부BigDecimal이 전혀 필요하지 않습니다 — 변환된 청구서 수천 줄을 합산할 때 이 차이가 큽니다.- Polly는 사실상의 복원력 라이브러리입니다. 재시도, 서킷 브레이커, 타임아웃이
AddPolicyHandler호출 한 번이면 됩니다. - Minimal API와 AOT. 30줄로 작은 환율 마이크로서비스를 띄우는 것이 현실적이고, 콜드 스타트는 밀리초 단위입니다.
다른 스택을 검토 중이라면 Node.js, Python, Go, PHP용 가이드도 있습니다. 본 글은 C# 전용이며 async/await, Generic Hosting, 의존성 주입에 익숙하다고 가정합니다.
사전 요건 및 프로젝트 설정
.NET 8 또는 .NET 9가 필요합니다. SDK를 확인하세요:
dotnet --version클라이언트용 클래스 라이브러리와 그것을 테스트할 콘솔 앱이 들어 있는 새 솔루션을 만듭니다:
mkdir Finexly.Sample && cd Finexly.Sample
dotnet new sln -n Finexly.Sample
dotnet new classlib -n Finexly.Client
dotnet new console -n Finexly.ConsoleApp
dotnet sln add Finexly.Client/Finexly.Client.csproj
dotnet sln add Finexly.ConsoleApp/Finexly.ConsoleApp.csproj
dotnet add Finexly.ConsoleApp/Finexly.ConsoleApp.csproj reference Finexly.Client/Finexly.Client.csprojFinexly API 키도 필요합니다. 무료로 가입하세요 — 신용카드 불필요, 무료 플랜은 월 1,000회 요청을 제공하므로 개발과 CI에 충분합니다.
키는 절대 코드에 하드코딩하지 마세요. 환경 변수에 저장합니다:
# macOS / Linux
export FINEXLY_API_KEY="your_api_key_here"# Windows PowerShell
$env:FINEXLY_API_KEY = "your_api_key_here"운영에서는 Azure Key Vault, AWS Secrets Manager, 또는 로컬 개발에는 dotnet user-secrets를 사용하세요.
HttpClient로 첫 요청 보내기
가장 단순한 동작 예제부터 시작합시다. Finexly.ConsoleApp/Program.cs를 엽니다:
using System.Net.Http;
using System.Net.Http.Json;
var apiKey = Environment.GetEnvironmentVariable("FINEXLY_API_KEY")
?? throw new InvalidOperationException("FINEXLY_API_KEY not set");
using var http = new HttpClient
{
BaseAddress = new Uri("https://api.finexly.com/v1/")
};
var url = $"latest?base=USD&apikey={apiKey}";
var payload = await http.GetFromJsonAsync<Dictionary<string, object>>(url);
Console.WriteLine(payload?["rates"]);실행:
dotnet run --project Finexly.ConsoleAppUSD 대비 통화 코드와 환율의 딕셔너리가 출력됩니다. 동작은 하지만 10줄짜리 예제의 모든 냄새를 다 갖추고 있습니다 — 타입 없는 object, 캐시 없음, 재시도 없음, HttpClient 수동 생성, URL에 노출된 API 키. 이제 전부 고쳐 봅시다.
강타입 응답 모델 정의
System.Text.Json은 C# record 타입으로 깔끔하게 역직렬화합니다. Finexly.Client 프로젝트에 Models/LatestRatesResponse.cs를 만듭니다:
using System.Text.Json.Serialization;
namespace Finexly.Client.Models;
public sealed record LatestRatesResponse(
[property: JsonPropertyName("base")] string Base,
[property: JsonPropertyName("date")] DateOnly Date,
[property: JsonPropertyName("rates")] IReadOnlyDictionary<string, decimal> Rates
);주의할 점:
double대신decimal. 부동소수점 오차는 돈에 허용되지 않습니다.decimal은 유효 자릿수 28–29자리와 정확한 10진 산술을 제공합니다.IReadOnlyDictionary. 응답은 읽기 전용입니다 — 타입에 그것을 표현하세요.DateOnly. 현대 .NET은 제대로 된 날짜 타입을 제공합니다. 시각 성분이 없을 때DateTime대신 사용하세요.
이제 dynamic이나 Dictionary<string, object> 같은 꼼수 없이 역직렬화할 수 있습니다.
IHttpClientFactory로 타입드 HttpClient 만들기
.NET 권장 패턴은 IHttpClientFactory를 통해 등록되는 Typed Client입니다. 연결 풀링, 설정 가능한 핸들러, 쉬운 DI를 제공합니다. Finexly.Client 프로젝트에 FinexlyClient.cs:
using System.Net.Http.Json;
using Finexly.Client.Models;
namespace Finexly.Client;
public sealed class FinexlyClient
{
private readonly HttpClient _http;
private readonly string _apiKey;
public FinexlyClient(HttpClient http, FinexlyOptions options)
{
_http = http;
_apiKey = options.ApiKey;
_http.BaseAddress = new Uri("https://api.finexly.com/v1/");
_http.DefaultRequestHeaders.Add("X-Api-Key", _apiKey);
_http.Timeout = TimeSpan.FromSeconds(5);
}
public async Task<LatestRatesResponse> GetLatestRatesAsync(
string baseCurrency = "USD",
IEnumerable<string>? symbols = null,
CancellationToken cancellationToken = default)
{
var query = $"latest?base={baseCurrency}";
if (symbols is not null)
{
query += $"&symbols={string.Join(",", symbols)}";
}
var response = await _http.GetFromJsonAsync<LatestRatesResponse>(
query, cancellationToken);
return response
?? throw new InvalidOperationException("Empty response from Finexly");
}
public async Task<decimal> ConvertAsync(
string from, string to, decimal amount,
CancellationToken cancellationToken = default)
{
var rates = await GetLatestRatesAsync(from, new[] { to }, cancellationToken);
if (!rates.Rates.TryGetValue(to, out var rate))
{
throw new ArgumentException($"Unknown currency: {to}");
}
return amount * rate;
}
}
public sealed class FinexlyOptions
{
public required string ApiKey { get; init; }
}짚어 둘 부분:
- API 키는 URL이 아닌 헤더에 들어갑니다 — 로그·프록시·트레이싱에 더 안전.
- 5초
Timeout은 멎은 환율 API가 결제 페이지를 느리게 만들지 않도록 막습니다. - 클라이언트의 모든 메서드가
CancellationToken을 받습니다 — 현대 ASP.NET에서는 협상 불가. GetFromJsonAsync<T>를 사용해 GET·상태 검증·역직렬화를 할당이 적은 한 번의 호출에 묶습니다.
의존성 주입에 클라이언트 등록
Program.cs (Console, ASP.NET Core, Worker — 어디서나 같은 API)에서 연결합니다:
using Finexly.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddSingleton(new FinexlyOptions
{
ApiKey = Environment.GetEnvironmentVariable("FINEXLY_API_KEY")!
});
builder.Services.AddHttpClient<FinexlyClient>();
using var host = builder.Build();
var finexly = host.Services.GetRequiredService<FinexlyClient>();
var eur = await finexly.ConvertAsync("USD", "EUR", 100m);
Console.WriteLine($"100 USD = {eur:0.00} EUR");AddHttpClient<FinexlyClient>()는 FinexlyClient를 transient로 등록하고 팩토리에서 풀링된 HttpClient를 제공합니다. 이제 FinexlyClient를 어떤 컨트롤러, Minimal API 엔드포인트, 백그라운드 서비스에도 주입할 수 있습니다.
Polly로 재시도와 서킷 브레이커 추가
외부 API는 가끔 요청을 떨구거나 스로틀하거나 네트워크 단에서 딸꾹질을 합니다. Polly는 .NET의 표준 복원력 라이브러리이며 IHttpClientFactory와 두 줄이면 통합됩니다.
패키지 설치:
dotnet add Finexly.ConsoleApp package Microsoft.Extensions.Http.Polly백오프 재시도와 서킷 브레이커 설정:
using Polly;
using Polly.Extensions.Http;
builder.Services.AddHttpClient<FinexlyClient>()
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError()
.OrResult(r => (int)r.StatusCode == 429)
.WaitAndRetryAsync(
retryCount: 3,
sleepDurationProvider: attempt =>
TimeSpan.FromMilliseconds(200 * Math.Pow(2, attempt))))
.AddPolicyHandler(HttpPolicyExtensions
.HandleTransientHttpError()
.CircuitBreakerAsync(
handledEventsAllowedBeforeBreaking: 5,
durationOfBreak: TimeSpan.FromSeconds(30)));이 한 번의 등록으로 5xx, 408, 429에 대해 지수 백오프(200ms, 400ms, 800ms)로 재시도하고, API가 완전히 다운되면 서킷 브레이커가 트립됩니다 — 결제 페이지는 몇 분간 연결을 잡고 있는 대신 우아하게 디그레이드됩니다.
IMemoryCache로 응답 캐시하기
실시간 환율은 분당 한 번 이상 변하는 일이 드물어, 작은 인프로세스 캐시만으로도 보통 상류 호출을 95% 이상 줄일 수 있습니다. IMemoryCache는 기본 제공됩니다.
dotnet add Finexly.Client package Microsoft.Extensions.Caching.MemoryCachedFinexlyClient.cs:
using Finexly.Client.Models;
using Microsoft.Extensions.Caching.Memory;
namespace Finexly.Client;
public sealed class CachedFinexlyClient
{
private readonly FinexlyClient _inner;
private readonly IMemoryCache _cache;
private static readonly TimeSpan DefaultTtl = TimeSpan.FromSeconds(60);
public CachedFinexlyClient(FinexlyClient inner, IMemoryCache cache)
{
_inner = inner;
_cache = cache;
}
public Task<LatestRatesResponse> GetLatestRatesAsync(
string baseCurrency,
CancellationToken cancellationToken = default)
{
var key = $"finexly:latest:{baseCurrency}";
return _cache.GetOrCreateAsync(key, entry =>
{
entry.AbsoluteExpirationRelativeToNow = DefaultTtl;
return _inner.GetLatestRatesAsync(baseCurrency,
cancellationToken: cancellationToken);
})!;
}
}IMemoryCache와 함께 등록:
builder.Services.AddMemoryCache();
builder.Services.AddSingleton<CachedFinexlyClient>();60초 TTL은 결제 가격, 빌링 미리보기, 관리자 대시보드에 훌륭한 기본값입니다. 이력 데이터는 TTL을 시간 또는 일 단위까지 늘리세요 — 이력 환율은 변하지 않습니다.
동작하는 콘솔 환율 환산기
전부 합치면 currencyconverter --amount 100 --from USD --to JPY 같은 작은 CLI가 만들어집니다:
using Finexly.Client;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
var builder = Host.CreateApplicationBuilder(args);
builder.Services.AddMemoryCache();
builder.Services.AddSingleton(new FinexlyOptions
{
ApiKey = Environment.GetEnvironmentVariable("FINEXLY_API_KEY")!
});
builder.Services.AddHttpClient<FinexlyClient>();
builder.Services.AddSingleton<CachedFinexlyClient>();
using var host = builder.Build();
decimal amount = 1m;
string from = "USD", to = "EUR";
for (int i = 0; i < args.Length - 1; i++)
{
switch (args[i])
{
case "--amount": amount = decimal.Parse(args[i + 1]); break;
case "--from": from = args[i + 1].ToUpperInvariant(); break;
case "--to": to = args[i + 1].ToUpperInvariant(); break;
}
}
var finexly = host.Services.GetRequiredService<CachedFinexlyClient>();
var rates = await finexly.GetLatestRatesAsync(from);
if (!rates.Rates.TryGetValue(to, out var rate))
{
Console.Error.WriteLine($"Unknown currency: {to}");
return 1;
}
Console.WriteLine($"{amount:0.00} {from} = {amount * rate:0.00} {to}");
return 0;어떤 서버에도 떨굴 수 있는 단일 파일 바이너리 빌드:
dotnet publish Finexly.ConsoleApp -c Release -r linux-x64 \
--self-contained -p:PublishSingleFile=trueASP.NET Core Minimal API로 환율 노출
대부분의 팀에서 올바른 아키텍처는 상류 API를 감싸고 캐시를 더해 다른 모든 서비스에 빠르고 무료인 VPC 내 엔드포인트를 제공하는 사내 환율 마이크로서비스입니다. ASP.NET Core Minimal API는 이를 30줄짜리 파일로 만듭니다:
using Finexly.Client;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddMemoryCache();
builder.Services.AddSingleton(new FinexlyOptions
{
ApiKey = builder.Configuration["Finexly:ApiKey"]!
});
builder.Services.AddHttpClient<FinexlyClient>();
builder.Services.AddSingleton<CachedFinexlyClient>();
var app = builder.Build();
app.MapGet("/rates", async (
string? @base,
CachedFinexlyClient finexly,
CancellationToken ct) =>
{
var rates = await finexly.GetLatestRatesAsync(@base ?? "USD", ct);
return Results.Ok(rates);
});
app.MapGet("/convert", async (
string from, string to, decimal amount,
CachedFinexlyClient finexly,
CancellationToken ct) =>
{
var rates = await finexly.GetLatestRatesAsync(from, ct);
return rates.Rates.TryGetValue(to, out var rate)
? Results.Ok(new { from, to, amount, converted = amount * rate })
: Results.NotFound(new { error = $"Unknown currency {to}" });
});
app.Run();평소 사용하는 API 게이트웨이 뒤에 배포하면 플릿 내 어떤 서비스든 캐시 히트 시 10밀리초 미만의 지연으로 GET /convert?from=USD&to=EUR&amount=99.95를 호출할 수 있습니다. 브라우저 경험을 원한다면 같은 데이터를 호스팅된 통화 변환기에서도 사용할 수 있습니다.
C#에서 돈을 올바르게 다루기
결제 원장을 처음 감사할 때 본전을 뽑는 방어적 패턴:
- 항상
decimal을 쓰고,double이나float은 절대로 쓰지 마세요. 부동소수점에서0.1 + 0.2는 유명하게도0.3이 아닙니다 — 그 한 번의 반올림 오차가 차지백 분쟁의 비용이 될 수 있습니다. - 계산이 아닌 표현 단계에서 반올림하세요. 모든 곱셈을 통해 완전한 정밀도를 유지하고, 표시·저장 시에만
Math.Round(value, 2, MidpointRounding.ToEven)을 호출하세요. - 반올림 모드를 고정하세요. 은행 반올림(
ToEven)이 IEEE 754와 ISO 80000-1의 금융 업계 기본입니다. - 통화를 불투명한 코드로 다루세요. ISO 4217 문자열을 사용하고, 모든 통화가 두 자리 소수를 가진다고 가정하지 마세요. JPY는 0자리, BHD는 3자리입니다. 엣지 케이스는 ISO 4217 가이드에서 다룹니다.
운영 환경 모범 사례
취미 통합과 수년간 운영 가능한 시스템을 가르는 추가 패턴:
- HTTP 타임아웃을 빡빡하게 설정하세요. 기본
HttpClient.Timeout100초는 부하 시 스레드 풀을 블로킹합니다. 환율 API에는 5초면 충분합니다. - 모든 외부 호출을
CancellationToken으로 감싸세요. ASP.NET Core가 무료로 전달해 줍니다 — 존중해야 끊긴 요청이 상류와 계속 대화하지 않습니다. - 상태 코드는 로깅하고 본문은 로깅하지 마세요. 환율 API 응답은 유용하게 로깅하기에 너무 크고, Splunk에 넣고 싶지 않은 환율 데이터를 담을 수 있습니다.
- 쿼터를 모니터링하세요. 요금제를 비교하고 월 한도의 80%에서 CloudWatch / Application Insights 알림을 설정하세요.
- 실제 사용하는 통화만 핀하세요.
symbols=EUR,GBP,JPY를 보내면 응답이 약 10KB에서 200바이트 미만으로 줄어듭니다. - 공격적으로 캐시하세요. 지연에 민감한 결제는 30초 캐시로, 관리자 대시보드는 5분 캐시로 충분합니다.
공급자를 검토 중이라면 무료 vs 유료 환율 API 비교가 주요 옵션의 정확성, 가동시간, 가격을 다룹니다.
자주 묻는 질문
C#과 .NET에 가장 잘 맞는 환율 API는?
깔끔한 JSON과 안정된 가동시간을 갖춘 어떤 REST API든 IHttpClientFactory를 통해 잘 통합됩니다. Finexly는 언어 비종속으로 설계되어 있습니다 — 같은 엔드포인트를 Node, Python, Go, Java, .NET 클라이언트가 SDK 락인 없이 소비합니다. 자세한 내용은 무료 환율 API 개요를 참고하세요.
Newtonsoft.Json과 System.Text.Json 중 어느 것을 써야 하나요?
.NET 6 이상의 신규 코드에는 System.Text.Json을 사용하세요. 2~3배 빠르고, 할당이 더 적으며, BCL의 일부라 따로 관리할 패키지가 없습니다. Newtonsoft.Json도 여전히 훌륭하고 레거시 코드에서는 유지할 가치가 있지만, 새로운 C# 환율 통합은 기본을 System.Text.Json으로 잡으세요.
NuGet SDK가 필요한가요, 아니면 HttpClient만으로 충분한가요? 수작업으로 작성한 타입드 클라이언트(약 50줄의 C#)가 거의 항상 정답입니다. 캐싱, 재시도, 텔레메트리, 직렬화에 대한 완전한 통제권을 주고, 어떤 서드파티 SDK에도 따라오는 버전 표류 위험을 피합니다. 이 튜토리얼의 예제는 운영 가능한 상태입니다.
.NET 애플리케이션에서 환율을 얼마나 자주 갱신해야 하나요? 대부분의 경우 — 전자상거래 결제, SaaS 빌링, 청구 — 30~60초 캐시가 안전합니다. 트레이딩 시스템은 당연히 실시간 데이터가 필요하지만, 그 외 모든 경우에 분 단위 신선도는 사용자에게 보이지 않으면서 API 비용을 크게 줄입니다.
Azure Functions나 AWS Lambda에서 실행할 수 있나요?
네. 격리 프로세스 Azure Functions와 Microsoft.Extensions.Hosting을 사용하는 Lambda 핸들러에서 IHttpClientFactory는 같은 방식으로 동작합니다. 콜드 스타트가 민감한 워크로드에서는 AOT(PublishAot=true)로 게시하면 함수가 100ms 미만에 시작합니다.
decimal은 double에 비해 느린가요?
decimal은 산술 연산당 대략 10~20배 더 느리지만, 환율 변환은 기껏해야 몇 번의 곱셈을 합니다. 실제 워크로드에서 차이를 측정할 일이 없으며 — 돈에 대한 정확성의 이득은 협상 불가입니다.
마무리
이제 C# / .NET 환율 API를 통합하기 위한 완전하고 운영급 청사진을 얻었습니다 — 강타입 모델, IHttpClientFactory 기반 클라이언트, Polly 기반 재시도와 서킷 브레이커, 인프로세스 캐싱, 콘솔 환산기, ASP.NET Core Minimal API. 여기 있는 모든 패턴은 마이크로소프트 공식 가이드라인에서 직접 가져왔고 실제 핀테크 운영 코드에서 검증되었습니다.
.NET 애플리케이션에 실시간 환율을 출시할 준비가 됐나요? 무료 Finexly API 키 받기 — 신용카드 불필요. 월 1,000회 무료 요청으로 시작해 제품 성장에 맞춰 업그레이드하세요. 다른 공급자와 비교하고 싶다면 환율 API 비교를 먼저 읽어 보세요.
Explore More
Vlado Grigirov
Senior Currency Markets Analyst & Financial Strategist
Vlado Grigirov is a senior currency markets analyst and financial strategist with over 14 years of experience in foreign exchange markets, cross-border finance, and currency risk management. He has wo...
View full profile →