C# i .NET w chmurze – od „zwykłej” aplikacji do cloud-native
Co w praktyce znaczy „praca w chmurze” dla C# i .NET
Dla programisty C# hasło „aplikacja w chmurze” przestaje się dziś kojarzyć z samym przeniesieniem kodu na maszynę wirtualną. Chodzi o zupełnie inny sposób projektowania: aplikacja ma być skalowalna, odporna na błędy, łatwa do wdrożenia na nowych środowiskach i dobrze zintegrowana z usługami platformy chmurowej.
Warto uporządkować podstawowe modele, bo od nich zależy dobór bibliotek i narzędzi:
- IaaS (Infrastructure as a Service) – masz klasyczne VM-ki (np. Azure Virtual Machines, EC2 w AWS) i sam instalujesz .NET, serwery, wszystko. To najmniej „chmurowy” wariant, często tylko lekko poprawia obecną sytuację on‑prem.
- PaaS (Platform as a Service) – np. Azure App Service, Azure SQL, Azure Cache for Redis. Kod C# skupia się na logice biznesowej, a platforma bierze na siebie zarządzanie infrastrukturą.
- Serverless / Functions – Azure Functions, AWS Lambda, Google Cloud Functions. Piszesz funkcje reagujące na zdarzenia (HTTP, kolejki, timery), a skalowanie i infrastruktura są „po drugiej stronie mocy”.
- Aplikacje kontenerowe – Docker + Kubernetes (AKS w Azure, EKS w AWS, GKE w GCP). Kod .NET działa w kontenerach, które są zarządzane przez orkiestrator.
„Praca w chmurze” w C# to więc nie tylko inne środowisko uruchomieniowe, ale szereg decyzji: czy używasz App Service, Functions, kontenerów, jak przechowujesz dane, jak obsługujesz kolejki, jak logujesz i monitorujesz działanie aplikacji. Tu wchodzą do gry konkretne biblioteki i narzędzia .NET, które potrafią zadecydować o tym, czy projekt będzie utrzymywalny, czy raczej zamieni się w kosztowny spaghetti‑as‑a‑Service.
Od monolitu on-prem do rozproszonej aplikacji cloud-native
Tradycyjna aplikacja on-premise to najczęściej jeden monolit: API, logika biznesowa, dostęp do danych, przetwarzanie wsadowe – wszystko w jednej paczce. W chmurze pojawia się pokusa (a czasem konieczność) rozbicia tego na wiele usług: API, mikroserwisy, worker services, funkcje serverless, kolejki, eventy.
To wprost przekłada się na sposób, w jaki myślisz o narzędziach:
- potrzebujesz bibliotek do komunikacji asynchronicznej (Azure.Messaging.ServiceBus, Azure.Messaging.EventHubs),
- musisz dbać o odporność na błędy sieciowe – retry, circuit breakers (np. Polly),
- konfiguracja nie może być „na sztywno” – musi wspierać zmienne środowiskowe, Key Vault, różne środowiska,
- logowanie i monitoring przestają być dodatkiem; stają się „zmysłem wzroku” aplikacji.
Przesiadka na podejście cloud-native to też zmiana nawyków. Nagle:
- lepiej projektować API pod skalowanie poziome (stateless, bez sesji trzymanej w pamięci serwera),
- częściej wprowadza się komunikację event-driven: zdarzenia trafiają do Event Hubs czy Service Busa, a różne komponenty aplikacji reagują na nie niezależnie,
- logika, która wcześniej była „zadaniem CRON-a” na jednym serwerze, teraz działa jako Azure Function wyzwalana timerem lub komunikatem z kolejki.
Główne platformy chmurowe a C#/.NET
.NET jest naturalnie powiązany z Azure – w końcu obie technologie pochodzą z Microsoftu. Azure ma najbogatszy ekosystem gotowych bibliotek i narzędzi dla .NET: Azure SDK for .NET, świetna integracja z Visual Studio, Application Insights, Azure DevOps, Azure Functions z C# jako „pierwszym obywatelem”.
To jednak nie znaczy, że C# nie nadaje się do AWS czy GCP. Obydwie platformy udostępniają SDK dla .NET:
- AWS SDK for .NET – obsługa S3, DynamoDB, SQS, SNS, Lambda i masy innych usług w jednym spójnym pakiecie.
- Google Cloud .NET libraries – klienty do Pub/Sub, Cloud Storage, BigQuery itd.
Strategia wyboru zależy od projektu:
- Jeśli środowisko firmowe jest „niebieskie” po korek – pójście „all in” na Azure zwykle jest najprostszym rozwiązaniem: lepsza integracja narzędzi, wsparcie, gotowe sample.
- Jeśli organizacja stawia na multi-cloud lub vendor neutrality – warto trzymać warstwę domenową i integracje w poziomie abstrakcji, a SDK konkretnych chmur opakować we własne interfejsy.
Typowe scenariusze dla C# w chmurze
W codziennej praktyce najczęściej pojawiają się kilka powtarzalnych typów aplikacji .NET w chmurze:
- API / backendy – ASP.NET Core w formie Minimal APIs lub klasycznego MVC, hostowane na App Service, w kontenerach (AKS) czy jako Functions HTTP.
- Worker Services / background jobs – aplikacje .NET, które przetwarzają kolejki, integrują się z zewnętrznymi systemami, synchronizują dane.
- Integracje B2B – usługi wymieniające dane przez kolejki, Event Grid, Service Bus, webhooki.
- Serverless / event-driven – systemy, w których rdzeń stanowią Azure Functions reagujące na zdarzenia z kolejki, BLOB-a, Event Hubs.
W każdym z tych scenariuszy przydatny jest inny zestaw bibliotek: od Azure.Storage.* po narzędzia do monitoringu i bezpieczeństwa (Application Insights, Azure.Identity, Azure.Security.KeyVault). Dobrze dobrany „stack” to mniej kodu infrastrukturą, więcej kodu domeną.
Dlaczego dobór bibliotek i narzędzi ma większe znaczenie w chmurze
W środowisku on-prem złe decyzje technologiczne są bolesne, ale czasem da się je „przecierpieć”: serwer jest jeden, administrator ma do niego fizyczny dostęp, logi leżą na dysku. W chmurze każda pomyłka może się przełożyć na realne koszty i problemy z niezawodnością:
- Złe retry – brak polityk ponawiania albo źle dobrane opóźnienia potrafią „zajechać” usługę chmurową lub wygenerować nadmiarowe koszty.
- Brak centralnego monitoringu – bez Application Insights czy innego stacku observability, diagnoza problemów bywa jak zabawa w zgadywanie z logami na 10 różnych maszynach.
- Sekrety w kodzie – connection string w appsettings.json synchronizowany do repo to proszenie się o kłopoty. Key Vault i towarzyszące biblioteki powstały głównie po to, aby takich sytuacji nie było.
- Zbyt mocny vendor lock-in – bez warstwy abstrakcji na SDK można się „przyspawać” do jednego dostawcy do tego stopnia, że migracja będzie niemal nierealna.
Odpowiednio dobrane narzędzia .NET pomagają zapanować nad tym chaosem i przenieść ciężar z problemów infrastrukturalnych na rozwijanie funkcjonalności biznesowych. I tak, da się osiągnąć stan, w którym wdrożenie do produkcji w piątek popołudniu nie przyprawia o dreszcze – choć niekoniecznie polecam taki sport ekstremalny.

Podstawowy zestaw narzędzi .NET do pracy w chmurze
.NET SDK, CLI i wybór IDE do projektów cloud-native
Niezależnie od tego, czy pracujesz z Azure, AWS czy GCP, fundament jest ten sam: .NET SDK i .NET CLI. To one pozwalają tworzyć, kompilować, testować i publikować aplikacje w sposób automatyzowalny, co w chmurze jest absolutną podstawą (bez CI/CD nie ma zabawy).
Kilka elementów bazowych:
- .NET SDK – najlepiej trzymać się aktualnie wspieranych LTS (np. .NET 8 LTS), szczególnie w środowiskach produkcyjnych.
- .NET CLI (
dotnet) – tworzenie projektów (dotnet new), zarządzanie pakietami (dotnet add package), publikacja (dotnet publish), testy (dotnet test). - IDE:
- Visual Studio – najpełniejsza integracja z Azure, bogaty debugging, szablony do App Service, Functions, kontenerów.
- Rider – świetny dla osób, które preferują środowisko JetBrains i multi-platform.
- VS Code – lekki edytor z rozszerzeniami C# Dev Kit i Azure Tools, idealny pod CI/CD i lekkie środowiska.
W praktyce przy projektach chmurowych liczy się to, na ile IDE dobrze współpracuje z:
- debuggowaniem zdalnym (np. Azure App Service Remote Debugging),
- Azure Functions / kontenerami,
- konfiguracją i sekretnymi danymi (user secrets, Key Vault, environment variables).
Azure CLI i Azure Developer CLI jako fundament automatyzacji
Ręczne klikanie w portalu Azure jest wygodne na start, ale w miarę rozwoju projektu staje się problematyczne: trudno odtworzyć środowisko, ciężko trzymać infrastrukturę pod kontrolą wersji, a „kliknięcia” znikają w niepamięci. Dlatego tak mocno promuje się podejście everything as code.
Dwie kluczowe linie komend dla programisty .NET w Azure:
- Azure CLI (
az) – ogólna konsola do zarządzania zasobami w Azure. Możesz z jej pomocą:- tworzyć resource group, App Service, Functions, bazy danych,
- konfigurować ustawienia aplikacji, connection stringi, sloty wdrożeniowe,
- wykonywać skrypty automatyzujące deployment (np. w GitHub Actions lub Azure DevOps).
- Azure Developer CLI (
azd) – narzędzie nastawione na developer experience:- używa szablonów projektów (aplikacja + infrastruktura jako kod),
- pozwala bardzo szybko utworzyć środowisko dev/test/prod jednym poleceniem,
- integruje się z Bicep/Terraform i repozytorium kodu.
Dla projektów .NET cloud-native sensowne jest podejście, w którym:
- infrastrukturę opisujesz Bicepem lub Terraformem, wołanym z
azlubazd, - kod aplikacji budujesz i publikujesz przez
dotnet, - całość sklejają pipeline’y CI/CD (GitHub Actions, Azure DevOps).
Struktura projektów .NET pod chmurę
Nowoczesny projekt chmurowy w .NET rzadko jest jedną aplikacją. Częściej to solution złożone z kilku projektów:
- API (ASP.NET Core Minimal API / Controllers),
- Worker Service – przetwarzanie kolejek, integracje, zadania cykliczne,
- Class Library z logiką domenową, modelami, walidacją,
- osobne biblioteki dla integracji z zewnętrznymi usługami (np. warstwa „Infrastructure”).
Taka struktura ułatwia:
- testowanie logiki biznesowej niezależnie od chmury,
- wymianę konkretnej implementacji (np. Azure Service Bus na AWS SQS),
- dzielenie kodu między API, workerami a Functions.
Przy ASP.NET Core coraz częściej stosuje się Minimal APIs, które w połączeniu z dependency injection i rozszerzeniami mogą tworzyć zaskakująco czytelny kod. W worker services przydaje się szablon Worker Service i interfejs IHostedService, pozwalający budować procesy działające w tle w tym samym modelu hostingu, co API.
Konfiguracja w chmurze – appsettings, environment i sekrety
Konfiguracja w aplikacjach chmurowych musi być elastyczna i bezpieczna. Standardowy mechanizm konfiguracji .NET świetnie się do tego nadaje – o ile jest poprawnie użyty.
Najczęściej spotykane elementy:
- appsettings.json – konfiguracja domyślna, wersjonowana w repozytorium (ale bez sekretów!),
- appsettings.<Environment>.json – np.
appsettings.Development.json,appsettings.Production.json, - zmienne środowiskowe – nadpisują wartości z plików, idealne do konfiguracji różniącej się między środowiskami chmurowymi,
- User secrets – mechanizm Secret Manager do przechowywania sekretów lokalnie w trybie development, bez wrzucania ich do repo,
- Key Vault – docelowe miejsce przechowywania haseł, connection stringów, kluczy API w chmurze.
Typowy wzorzec:
- W kodzie używasz
IConfigurationiOptions pattern(IOptions<T>,IOptionsSnapshot<T>) do wstrzykiwania ustawień. - Na etapie developmentu sekrety trzymasz w user secrets.
Azure.Identity– wspólny mechanizm uwierzytelniania (Managed Identity, Visual Studio, CLI, GitHub OIDC, itp.),Azure.Security.KeyVault.Secrets,Azure.Security.KeyVault.Keys,Azure.Security.KeyVault.Certificates– operacje na sekretach, kluczach i certyfikatach,- provider konfiguracji do Key Vault – integruje Key Vault z
IConfigurationBuilder. Microsoft.ApplicationInsights.AspNetCore– klasyczna integracja z ASP.NET Core,Azure.Monitor.OpenTelemetry.AspNetCore– nowy sposób integracji z Azure Monitor przez OpenTelemetry,OpenTelemetry.*– biblioteki do logów, metryk i trace’ów niezależne od chmury.- wszystko opiera się o klasy klienta (np.
BlobClient,QueueClient), - wspólny mechanizm uwierzytelniania przez
TokenCredential(np.DefaultAzureCredential), - wbudowane retry z politykami konfigurowanymi przez opcje klienta,
- obsługa async/await, strumieni, paginacji (np.
Pageable<T>). - definiujesz interfejsy domenowe, np.
ICloudBlobStorage,IMessageQueue, - po jednej implementacji na dostawcę:
AzureBlobStorage,AwsS3Storage, - wstrzykujesz odpowiednią implementację na etapie konfiguracji (DI, feature flags, konfiguracja środowiska).
Azure.Storage.Blobs– przechowywanie plików, backupów, załączników,Azure.Storage.Queues– proste kolejki komunikatów,Azure.Data.Tables– NoSQL key-value / tabela,Azure.Storage.Files.Shares– współdzielone udziały plikowe.- Entity Framework Core – pełnoprawny ORM, migracje, LINQ,
- Dapper – „mikro ORM” nastawiony na wydajność i prostotę,
- specyficzne biblioteki dostawców, np.
Microsoft.Data.SqlClient,Npgsql(PostgreSQL),MongoDB.Driver. Azure.Data.Tables– proste tabele NoSQL w Azure Storage,Azure.Data.Cosmos– dostęp do Azure Cosmos DB,- drivery do „managed” baz jak Azure Database for PostgreSQL / MySQL (standardowe sterowniki, ale inne connection stringi i opcje).
- Azure Service Bus – kolejki i tematy (topics) z gwarancjami dostarczenia, sesjami, dead-letteringiem,
- Azure Event Hubs – strumienie zdarzeń (telemetria, logi, zdarzenia IoT),
- Storage Queues – prostsza, tańsza kolejka (omówiona wyżej).
Azure.Messaging.ServiceBus– praca z Service Bus,Azure.Messaging.EventHubsiAzure.Messaging.EventHubs.Processor– obsługa Event Hubs.- mapowanie wiadomości na klasy .NET,
- sagi, routing, retry, outbox,
- wsparcie dla wielu transportów (Service Bus, RabbitMQ, Amazon SQS).
- triggery – co wywołuje funkcję (HTTP, Queue Storage, Service Bus, Event Hub, Timer, Blob, Event Grid),
- input bindings – skąd funkcja pobierze dane (np. dokument z Cosmos DB, BLOB, rekord z tabeli),
- output bindings – gdzie funkcja zapisze efekt (np. wiadomość do kolejki, nowy dokument w Cosmos DB, BLOB).
func init– tworzenie projektu funkcji,func new– dodanie nowej funkcji z szablonu (HTTP, Service Bus, Timer itd.),func start– lokalny runtime do debugowania z Visual Studio / VS Code / Rider.- nowoczesną wersję runtime (np. .NET 8, izolowany worker),
- ograniczenie liczby zależności zewnętrznych do faktycznie potrzebnych,
- unikanie kosztownych operacji w konstruktorach (ładowanie konfiguracji, duże grafy obiektów),
- minimalizację statycznych singletonów, które długo się inicjalizują.
- Azure API Management – jako bramka dla funkcji HTTP (autoryzacja, throttling, wersjonowanie),
- Event Grid – wyzwalanie funkcji zdarzeniami z innych usług (np. nowy BLOB, zmiana w subskrypcji),
- Key Vault – przechowywanie sekretów używanych przez funkcje (connection stringi, klucze API),
- Application Insights – monitoring, logi i distributed tracing.
- OpenFaaS – funkcje pisane w C#, pakowane jako kontenery, uruchamiane na K8s,
- Knative – serwowanie kontenerów http scale-to-zero,
- KEDA – autoskalowanie na podstawie zdarzeń (Azure Queue, Kafka, RabbitMQ, Prometheus itp.).
- logika domenowa w osobnych projektach (np.
MyApp.Domain,MyApp.Application), - funkcje jako cienka warstwa adapterów wejścia/wyjścia (HTTP, kolejka, event),
- współdzielone biblioteki kontraktów (DTO, eventy) między funkcjami a microserwisami,
- wspólna biblioteka „platformowa” na cross-cutting (telemetria, obsługa błędów, retry, logowanie korelacji).
- Bicep / ARM – natywny język deklaratywny dla Azure,
- Terraform – przenośny, działający na Azure/AWS/GCP,
- Pulumi – definicja infrastruktury w C#, co bywa atrakcyjne dla zespołów .NET.
- Application Insights (Azure Monitor) – logi, metryki, distributed tracing,
- OpenTelemetry – wspólny standard eksportu danych obserwowalności,
- serwisy docelowe: Azure Monitor, Prometheus, Jaeger, Grafana Cloud itp.
Bezpieczne zarządzanie sekretami – Azure Key Vault i Azure.Identity
Na etapie produkcji sekrety powinny trafić do dedykowanego magazynu. W ekosystemie Azure takim domyślnym miejscem jest Azure Key Vault, a po stronie .NET kluczową rolę odgrywa pakiet Azure.Identity oraz integracja z mechanizmem konfiguracji.
Typowy zestaw bibliotek:
Najważniejszy element układanki to DefaultAzureCredential. W jednym kodzie ogarnia różne sposoby logowania:
var credential = new DefaultAzureCredential();
var client = new SecretClient(
new Uri("https://<twoj-key-vault>.vault.azure.net/"),
credential);
KeyVaultSecret secret = await client.GetSecretAsync("DbConnection");
string connString = secret.Value;
Ten sam kod zadziała lokalnie (VS / Azure CLI / user secrets), w pipeline (np. federacja z Azure AD) oraz w chmurze (Managed Identity przypisana do App Service / Functions / VM). Bez ręcznego wklepywania haseł w konfigurację.
W aplikacjach webowych i workerach często wygodniej wpiąć Key Vault bezpośrednio w pipeline konfiguracji:
builder.Configuration
.AddJsonFile("appsettings.json", optional: false, reloadOnChange: true)
.AddEnvironmentVariables();
if (!builder.Environment.IsDevelopment())
{
var credential = new DefaultAzureCredential();
var keyVaultUrl = new Uri(builder.Configuration["KeyVaultUrl"]!);
builder.Configuration.AddAzureKeyVault(keyVaultUrl, credential);
}
Dzięki temu cały kod korzysta po prostu z IConfiguration, a to skąd pochodzą sekrety (Key Vault, env, pliki) przestaje być problemem aplikacji. Gdy ktoś wpisze connection string w appsettings.Production.json, system kontroli wersji i code review powinny zapalić wszystkie czerwone lampki.
Observability – Application Insights, logging i tracing rozproszony
Bez sensownego monitoringu aplikacja chmurowa działa tylko teoretycznie. W .NET warto zacząć od trzech elementów: logów, metryk i tracingu rozproszonego.
Najpopularniejszy pakiet w ekosystemie Azure to Application Insights. Współpracuje z ASP.NET Core, Azure Functions, Worker Services, a w nowych projektach dobrze integruje się także z OpenTelemetry:
Przykładowa konfiguracja z OpenTelemetry i wysyłką do Azure Monitor może wyglądać tak:
builder.Services.AddOpenTelemetry()
.UseAzureMonitor() // Azure Monitor / App Insights
.WithMetrics(meterProviderBuilder =>
{
meterProviderBuilder.AddAspNetCoreInstrumentation();
meterProviderBuilder.AddHttpClientInstrumentation();
})
.WithTracing(tracerProviderBuilder =>
{
tracerProviderBuilder.AddAspNetCoreInstrumentation();
tracerProviderBuilder.AddHttpClientInstrumentation();
tracerProviderBuilder.AddSource("MyApp.*");
});
Do logowania w .NET sensownym wyborem pozostaje Serilog, który ma bogaty ekosystem sinków (m.in. Application Insights, Seq, Elasticsearch, Event Hub):
Log.Logger = new LoggerConfiguration()
.ReadFrom.Configuration(builder.Configuration)
.Enrich.FromLogContext()
.WriteTo.Console()
.CreateLogger();
builder.Host.UseSerilog();
Raz poprawnie spięty stack observability pozwala spokojniej spać w piątek po deployu – zamiast „domyślać się z ruchu na stronie”, masz wykresy, trace’y i konkretne logi z korelacją między usługami.
Komunikacja z usługami chmurowymi – Azure SDK for .NET i alternatywy
Nowy model Azure SDK – spójne klienty, retry i paginacja
Aktualna generacja bibliotek Azure SDK dla .NET (nazwy w stylu Azure.Storage.Blobs, Azure.Messaging.ServiceBus) ma spójny model:
Konfiguracja klienta zwykle sprowadza się do przekazania poświadczeń i ewentualnej customizacji opcji:
var options = new BlobClientOptions
{
Retry =
{
Mode = RetryMode.Exponential,
MaxRetries = 5,
Delay = TimeSpan.FromSeconds(1),
MaxDelay = TimeSpan.FromSeconds(10)
}
};
var blobServiceClient = new BlobServiceClient(
new Uri("https://<account>.blob.core.windows.net"),
new DefaultAzureCredential(),
options);
Większość SDK ma też wersje *ClientOptions, w których ustawisz polityki retry, logowanie, diagnostykę. Warto to trzymać w jednym miejscu (np. modul DI), aby nie kopiować konfiguracji po całym kodzie.
HttpClient, Refit i inne podejścia do API REST
Nie wszystko w chmurze to „gotowe” SDK. Bardzo często trzeba rozmawiać z zewnętrznymi API REST – czy to usług partnera, czy własnym microserwisem za bramką API. Bazą w .NET jest oczywiście HttpClient oraz fabryka IHttpClientFactory.
Dobrą praktyką jest rejestrowanie klientów jako typed clients:
builder.Services.AddHttpClient<IPaymentGatewayClient, PaymentGatewayClient>(client =>
{
client.BaseAddress = new Uri("https://api.payment-gateway.com");
client.Timeout = TimeSpan.FromSeconds(10);
});
Do samej definicji kontraktów REST wygodnym narzędziem jest Refit – biblioteka generująca klienta na podstawie interfejsu w C#:
public interface IPaymentGatewayApi
{
[Post("/payments")]
Task<PaymentResponse> CreatePaymentAsync([Body] PaymentRequest request);
[Get("/payments/{id}")]
Task<PaymentStatus> GetStatusAsync(string id);
}
// Rejestracja:
builder.Services
.AddRefitClient<IPaymentGatewayApi>()
.ConfigureHttpClient(c => c.BaseAddress = new Uri("https://api.payment-gateway.com"));
Refit sprawdza się tam, gdzie nie ma oficjalnego SDK lub jest ono bardzo ciężkie. Dobrym uzupełnieniem są biblioteki do retry i circuit breakerów, np. Polly, którą łatwo powiązać z IHttpClientFactory:
builder.Services.AddHttpClient<IPaymentGatewayClient, PaymentGatewayClient>()
.AddTransientHttpErrorPolicy(policy =>
policy.WaitAndRetryAsync(3, retryAttempt =>
TimeSpan.FromSeconds(Math.Pow(2, retryAttempt))));
Z takim podejściem logika biznesowa nie musi wiedzieć, czym dokładnie jest „cloud API” – dostaje interfejs, a pod spodem kryje się HTTP, retry, telemetry i wszystkie chmurowe detale.
Abstrakcja nad dostawcą chmury – gdy Azure nie jest jedyną opcją
Czasem projekt musi wspierać więcej niż jednego dostawcę (np. Azure + AWS u dużego klienta). Wtedy rozsądnie jest wprowadzić warstwę abstrakcji nad SDK dostawcy:
Przykładowy interfejs dla prostego storage’u plików:
public interface ICloudFileStorage
{
Task UploadAsync(string path, Stream content, string contentType, CancellationToken ct);
Task<Stream> DownloadAsync(string path, CancellationToken ct);
Task DeleteAsync(string path, CancellationToken ct);
}
W Azure implementacja oprze się na BlobClient, w AWS na AmazonS3Client. Wyższe warstwy nie muszą o tym wiedzieć – dla nich chmura to „miejsce, gdzie leżą pliki”. Zmiana dostawcy boli tylko w warstwie infrastrukturalnej, a nie w całym systemie.

Przechowywanie danych i kolejek – biblioteki do Storage, baz i komunikatów
Azure Storage – Blob, Queue, Table, Files w wydaniu .NET
Usługi klasy storage w Azure to często pierwszy przystanek każdej aplikacji. W .NET reprezentuje je rodzina Azure.Storage.*:
Przykład pracy z BLOB-ami jest dość prosty:
var containerClient = new BlobContainerClient(
new Uri("https://<account>.blob.core.windows.net/invoices"),
new DefaultAzureCredential());
BlobClient blobClient = containerClient.GetBlobClient("2024/04/invoice-1234.pdf");
await blobClient.UploadAsync(fileStream, overwrite: true);
Do kolejek (Queue Storage) masz analogiczną bibliotekę Azure.Storage.Queues, która pozwala wysyłać / pobierać komunikaty w modelu „best effort”. Przy większych wymaganiach co do gwarancji dostarczenia lepiej spojrzeć w kierunku Azure Service Bus.
Bazy danych – EF Core, Dapper i klienci specyficzni dla chmury
Większość systemów i tak kończy z jakąś bazą danych, niezależnie od tego, ile mówi się o event sourcingu. W ekosystemie .NET najpopularniejsze są:
W chmurze dochodzą klienty dla usług zarządzanych:
EF Core dobrze się sprawdza z klasycznymi bazami (SQL Server, PostgreSQL). W przypadku Cosmos DB bywa wygodny, ale często i tak kończy się sięganiem po model dokumentowy przez Azure.Data.Cosmos, aby mieć pełną kontrolę nad partycjonowaniem i zapytaniami.
W aplikacjach chmurowych warto trzymać dostęp do bazy za interfejsami repozytoriów lub warstwą CQRS. Dzięki temu zmiana „SQL w Azure” na „SQL on-prem” albo odwrotnie nie rozleje się po całej aplikacji.
Kolejki i messaging – Azure Service Bus, Event Hubs i alternatywy
Kolejkowanie i publish-subscribe to kręgosłup wielu systemów cloud-native. W Azure główne opcje to:
Po stronie .NET kluczowe biblioteki to:
Przykład wysyłania wiadomości do Azure Service Bus:
var client = new ServiceBusClient("<connection-string>");
ServiceBusSender sender = client.CreateSender("orders");
var message = new ServiceBusMessage(JsonSerializer.Serialize(order))
{
ContentType = "application/json"
};
await sender.SendMessageAsync(message);
W bardziej złożonych systemach dochodzą abstrakcje wyższego poziomu, np. MassTransit czy NServiceBus, które zapewniają:
To rozwiązania cięższe, ale jeśli system żyje głównie z przesyłania komunikatów między usługami, potrafią zaoszczędzić sporo czasu i kilku siwych włosów.
.NET i serverless – Azure Functions oraz alternatywne platformy
Azure Functions – model programistyczny dla C#
W świecie .NET serverless najczęściej oznacza po prostu Azure Functions. Z punktu widzenia programisty to zwykła biblioteka klas w C#, ale z bardzo konkretnym modelem wywołań. Zamiast Main lub kontrolera MVC masz metody oznaczone atrybutami triggerów i bindingów.
Prosty HTTP trigger w izolowanym modelu procesowym (dotnet-isolated, rekomendowany przy .NET 8+):
public class GetOrderFunction
{
[Function("GetOrder")]
public HttpResponseData Run(
[HttpTrigger(AuthorizationLevel.Function, "get", Route = "orders/{id}")]
HttpRequestData req,
string id,
FunctionContext executionContext)
{
var logger = executionContext.GetLogger("GetOrder");
logger.LogInformation("Getting order {OrderId}", id);
// ..logika, np. wywołanie serwisu domenowego..
var response = req.CreateResponse(HttpStatusCode.OK);
response.Headers.Add("Content-Type", "application/json");
response.WriteString(JsonSerializer.Serialize(new { Id = id }));
return response;
}
}
Funkcja nie wie nic o serwerze, skalowaniu czy węzłach. Dostaje zdarzenie (HTTP, kolejka, timer) i ma je obsłużyć jak najszybciej, najlepiej bez side-effectów poza tymi, które naprawdę są potrzebne.
Trigger’y i bindingi – klej między chmurą a funkcją
W Azure Functions nie trzeba wszystkiego konfigurować ręcznie przez SDK. Większość typowych integracji jest dostępna jako bindings:
Klasyczny scenariusz: wiadomość z kolejki → przetworzenie → zapis wyniku do innej kolejki lub Cosmos DB, bez ręcznego klejenia klientów SDK:
public class ProcessInvoiceFunction
{
[Function("ProcessInvoice")]
[ServiceBusOutput("processed-invoices", Connection = "ServiceBusConnection")]
public async Task<ProcessedInvoice> Run(
[ServiceBusTrigger("incoming-invoices", Connection = "ServiceBusConnection")]
InvoiceMessage message,
FunctionContext context)
{
var logger = context.GetLogger("ProcessInvoice");
logger.LogInformation("Processing invoice {InvoiceId}", message.Id);
// ...logika biznesowa...
return new ProcessedInvoice
{
Id = message.Id,
Status = "Processed"
};
}
}
W prostych scenariuszach bindingi ograniczają ilość „plumbing code”. W bardziej złożonych lepiej sięgać po jawne użycie SDK (np. Azure.Messaging.ServiceBus) i pełną kontrolę nad retry, transakcjami czy routingiem.
Rozwój i debugowanie funkcji w .NET
Do pracy lokalnej przydaje się Azure Functions Core Tools. Można nimi odpalać funkcje lokalnie, symulować triggery i testować całość bez dotykania chmury (i budżetu):
Konfiguracja opiera się głównie o local.settings.json (lokalnie) i Application Settings w Azure. Po stronie kodu używa się standardowego IConfiguration oraz DI znanego z ASP.NET Core – w izolowanym modelu host konfiguruje się w Program.cs:
var host = new HostBuilder()
.ConfigureFunctionsWorkerDefaults()
.ConfigureServices(services =>
{
services.AddHttpClient();
services.AddSingleton<IOrderService, OrderService>();
})
.Build();
host.Run();
Dzięki temu większość wzorców z klasycznych aplikacji .NET da się przenieść 1:1 – te same serwisy, te same rejestracje, inne tylko punkty wejścia.
Wydajność i zimny start – co ma znaczenie w C#
Serverless lubi lekkie projekty. W .NET oznacza to głównie:
Dobrym nawykiem jest użycie pre-warmingu (podtrzymywania instancji) i planu Premium/ASE dla krytycznych funkcji, gdzie zimny start jest nieakceptowalny. W wielu przypadkach wystarczy jednak klasyczny Consumption Plan plus rozsądna optymalizacja kodu (startup + zależności).
Integracja z pozostałymi usługami Azure
Funkcje rzadko żyją w izolacji. Najczęściej są wpięte w resztę platformy:
Z poziomu C# większość integracji sprowadza się do użycia odpowiednich SDK (Azure.Messaging.EventGrid, Azure.Security.KeyVault.Secrets) oraz konfiguracji connection stringów w Application Settings z prefiksem @Microsoft.KeyVault(SecretUri=...). Funkcja myśli, że czyta zwykły setting, a runtime dogaduje się z Key Vaultem za kulisami.
Alternatywne platformy serverless dla .NET
C# nie jest zamknięty na jednego dostawcę. Jeśli projekt ląduje poza Azure, da się wykorzystać te same biblioteki domenowe na innych platformach.
AWS Lambda z .NET
AWS od dawna wspiera .NET w Lambda Functions. Aktualne runtime’y obsługują .NET 8, a aplikacje można pisać zarówno w podejściu „plain C#”, jak i jako minimal API hostowane w Lambdzie.
Przykład prostego handlera:
public class Functions
{
public APIGatewayProxyResponse Get(
APIGatewayProxyRequest request, ILambdaContext context)
{
context.Logger.LogLine($"Request: {request.Path}");
return new APIGatewayProxyResponse
{
StatusCode = 200,
Body = "Hello from Lambda + .NET",
Headers = new Dictionary<string, string>
{
{ "Content-Type", "text/plain" }
}
};
}
}
Do wdrażania i zarządzania infrastrukturą można wykorzystać AWS SAM, CloudFormation albo CDK (gdzie część konfiguracji i tak pisze się w C#). Jeśli kod domenowy ma sensowną abstrakcję, różnice sprowadzają się głównie do warstwy wejścia/wyjścia (eventy z API Gateway, SQS, SNS, DynamoDB).
Google Cloud Functions i Cloud Run
W Google Cloud ekosystem .NET jest trochę mniej „pierwszej świeżości” niż w Azure/AWS, ale da się komfortowo żyć. Dla C# ciekawszą opcją niż klasyczne Cloud Functions jest zwykle Cloud Run – kontener serverless odpalany na żądanie.
Aplikacja ASP.NET Core (np. minimal API) pakuje się do obrazu kontenera, publikuje w Artifact Registry, a Cloud Run dba o skalowanie i routing. Z punktu widzenia kodu to nadal „normalny” web host:
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddSingleton<IWeatherService, WeatherService>();
var app = builder.Build();
app.MapGet("/weather", (IWeatherService svc) => svc.GetForecast());
app.Run();
Cała magia dzieje się w konfiguracji uruchomienia kontenera (limit CPU, memory, concurrency, autoscaling). Biblioteki domenowe i klienci HTTP/SDK pozostają bez zmian.
Open source: OpenFaaS, Knative, KEDA
W środowiskach Kubernetes coraz częściej spotyka się platformy serverless oparte na open source. Dla C# najciekawsze są:
Model jest podobny jak przy Cloud Run: piszesz normalną aplikację .NET (np. minimal API, gRPC, background service), a platforma zajmuje się skalowaniem na podstawie ruchu lub metryk. Jeśli architektura jest oparta na DI i kontraktach, migracja z Azure Functions do „funkcji na Kubernetesa” nie musi być dramatem – głównie zmienia się warstwa hostingu.
Współdzielenie kodu między funkcjami a resztą systemu
W większych projektach pojawia się pokusa: „skopiujmy trochę logiki do funkcji, przecież to tylko kilka linii”. Po trzeciej kopii człowiek zaczyna żałować. Przy serverless w .NET dobrze działa kilka prostych zasad:
Dzięki temu ta sama komenda CQRS (CreateOrderCommand) może być wywoływana zarówno z kontrolera HTTP w API, jak i z funkcji reagującej na wiadomość z kolejki. Funkcje nie stają się „drugim backendem”, tylko kolejnym sposobem dostarczenia tych samych use-case’ów.
Infrastruktura jako kod dla środowisk serverless
Serverless nie zwalnia z ogarnięcia infrastruktury, tylko przerzuca ją w inne miejsce. Zamiast plików ARM/Terraform wyklikiwanych w portalu lepiej postawić na Infrastructure as Code:
Przykład bardzo uproszczonej definicji Function App w Bicep:
param functionAppName string
param location string = resourceGroup().location
resource storage 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: 'st${uniqueString(resourceGroup().id)}'
location: location
sku: {
name: 'Standard_LRS'
}
kind: 'StorageV2'
}
resource plan 'Microsoft.Web/serverfarms@2023-01-01' = {
name: 'asp-${functionAppName}'
location: location
sku: {
name: 'Y1'
tier: 'Dynamic'
}
}
resource func 'Microsoft.Web/sites@2023-01-01' = {
name: functionAppName
location: location
kind: 'functionapp'
properties: {
serverFarmId: plan.id
siteConfig: {
appSettings: [
{
name: 'AzureWebJobsStorage'
value: storage.properties.primaryEndpoints.blob
}
]
}
}
}
Po stronie .NET łatwo to zszyć z pipeline’ami CI/CD (GitHub Actions, Azure DevOps, GitLab CI), tak by wdrożenie nowej funkcji automatycznie aktualizowało też infrastrukturę. Mniej ręcznego klikania, mniej „magicznych” różnic między środowiskami.
Obserwowalność serverless w .NET – logi, metryki, ślady
Przy kilkunastu funkcjach można jeszcze „ogarniać na oko”. Przy kilkudziesięciu albo setce – bez telemetrii robi się loteria. W ekosystemie .NET trzon stanowią:
W Azure Functions integracja z Application Insights jest prawie automatyczna – wystarczy dodać connection string w konfiguracji. W izolowanym workerze można dodatkowo dołożyć ILogger<T> i manualne śledzenie:
public class ChargeCustomerFunction
{
private readonly IPaymentService _payments;
private readonly ILogger<ChargeCustomerFunction> _logger;
public ChargeCustomerFunction(IPaymentService payments, ILogger<ChargeCustomerFunction> logger)
{
_payments = payments;
_logger = logger;
}
[Function("ChargeCustomer")]
public async Task Run(
[ServiceBusTrigger("payments", Connection = "ServiceBus")] PaymentCommand cmd)
{
using var scope = _logger.BeginScope(new Dictionary<string, object>
{
["PaymentId"] = cmd.PaymentId
});
_logger.LogInformation("Charging customer for payment {PaymentId}", cmd.PaymentId);
await _payments.ChargeAsync(cmd);
}
}
Jeśli projekt korzysta z OpenTelemetry w innych microserwisach, nic nie stoi na przeszkodzie, żeby dołączyć do tego też funkcje – biblioteki .NET obsługują HTTP klienta, ASP.NET Core, gRPC, a także własne źródła Activity i Meter.
Dobór narzędzi chmurowych w projektach .NET
Najczęściej zadawane pytania (FAQ)
Jak zacząć pracę w chmurze z C# i .NET, jeśli mam klasyczną aplikację on-premise?
Punkt startowy to zrozumienie, w jakim modelu chmury chcesz działać: IaaS (maszyny wirtualne), PaaS (np. Azure App Service) czy serverless/kontenery. Najszybsza, ale najmniej „chmurowa” droga to przeniesienie aplikacji na VM w Azure lub AWS – wymaga najmniej zmian w kodzie, ale niewiele zyskujesz poza elastyczniejszą infrastrukturą.
Docelowo sensowniej jest przepakować aplikację jako ASP.NET Core (jeśli jeszcze nie jest) i wystawić ją jako Web App w Azure App Service lub w kontenerze Docker uruchamianym w Kubernetes (AKS/EKS/GKE). Dodatkowo dobrze jest od razu wynieść konfigurację poza kod (zmienne środowiskowe, Key Vault) i podpiąć centralne logowanie, np. Application Insights.
Jakie biblioteki .NET są najważniejsze przy tworzeniu aplikacji cloud-native w Azure?
W typowym projekcie cloud-native na Azure często pojawia się ten sam „zestaw startowy” bibliotek. Do komunikacji z usługami chmurowymi używa się Azure SDK for .NET, m.in.: Azure.Storage.Blobs, Azure.Messaging.ServiceBus, Azure.Messaging.EventHubs, Azure.Data.Tables. Do uwierzytelniania względem usług przydaje się Azure.Identity.
Do bezpieczeństwa sekretów dobrze sprawdzają się Azure.Security.KeyVault.Secrets oraz integracja Key Vault z konfiguracją ASP.NET Core. Do logowania i monitoringu – Microsoft.ApplicationInsights.AspNetCore oraz wbudowane rozszerzenia loggingu w .NET. Na deser warto dorzucić Polly do retry i circuit breakerów, bo w chmurze sieć psuje dzień częściej, niżby się chciało.
Jaka jest różnica między hostowaniem .NET na VM (IaaS) a w App Service (PaaS)?
Na maszynie wirtualnej (IaaS) zarządzasz wszystkim sam: systemem operacyjnym, aktualizacjami, instalacją .NET, konfiguracją serwera (IIS, Kestrel + reverse proxy). To daje pełną kontrolę, ale też pełną odpowiedzialność – trochę jak własny serwer w szafie, tylko w cudzej serwerowni.
App Service (PaaS) zdejmuje z ciebie większość zadań administratorskich. Wrzucasz paczkę z aplikacją lub kontener, ustawiasz konfigurację, autoskalowanie, sloty wdrożeniowe i skupiasz się na kodzie. Minusem może być mniejsza elastyczność w porównaniu z „gołą” VM, ale w zamian dostajesz szybsze wdrożenia, prostszy scaling i integrację z innymi usługami Azure.
Kiedy lepiej użyć Azure Functions / AWS Lambda zamiast klasycznego API w ASP.NET Core?
Funkcje serverless mają sens, gdy logika jest mocno zdarzeniowa: reagujesz na komunikaty z kolejki, pliki wrzucane do storage, timery, webhooki. Sprawdzają się też przy nieregularnym ruchu – płacisz głównie za wykonania, więc nie trzymasz cały czas „rozgrzanych” serwerów tylko po to, żeby obsłużyć kilka żądań na godzinę.
Klasyczne API w ASP.NET Core lepiej nadaje się do systemów z przewidywalnym lub stałym ruchem, z większym, spójnym kontraktem HTTP (np. rozbudowane API B2B). Łatwiej też utrzymać jedną aplikację API niż 50 funkcji, jeśli zespół dopiero uczy się podejścia event-driven. Częste rozwiązanie hybrydowe: publiczne API w ASP.NET Core, a cięższe lub opóźnione przetwarzanie w Functions/Lambda.
Jak unikać vendor lock-in, używając C# i .NET w chmurze (Azure, AWS, GCP)?
Podstawą jest oddzielenie logiki domenowej od kodu, który mówi „jak dogadać się z konkretną chmurą”. W praktyce oznacza to wprowadzenie interfejsów i własnych adapterów dla SDK chmurowych. Kod domenowy korzysta np. z IFileStorage czy IMessageBus, a dopiero implementacja wie, że pod spodem jest Azure Blob Storage, S3 czy Google Cloud Storage.
Drugi element to trzymanie konfiguracji usług (connection stringi, nazwy kolejek, topiki, endpointy) poza kodem – w konfiguracji środowisk, Key Vault/Secret Managerach. Dzięki temu w razie migracji nie refaktoryzujesz połowy repozytorium, tylko podmieniasz adapter i ustawienia. I, co ważne, nie korzystaj z unikatowych funkcji dostawcy bez świadomości, że za jakiś czas może to „przyspawać” projekt do danej platformy.
Jakie narzędzia do logowania i monitoringu są polecane dla aplikacji .NET w chmurze?
Najpopularniejszy zestaw w świecie Azure to Application Insights + wbudowany logging w ASP.NET Core. Pozwala to śledzić requesty, zależności, wyjątki, metryki wydajności i tworzyć alerty (np. gdy wzrośnie liczba błędów 5xx). Dobrze jest skonfigurować korelację między usługami (operation id), żeby móc prześledzić cały „lot” żądania przez mikroserwisy i funkcje.
Poza tym często stosuje się narzędzia „cloud-agnostic”: Serilog lub NLog z wysyłką logów do Elastic Stack, Seq, Datadoga, czy Prometheusa + Grafanę. W praktyce kończy się tak, że masz jedno centralne miejsce, gdzie trafiają logi i metryki ze wszystkich usług – inne rozwiązania zwykle kończą się nocnym polowaniem na logi na kilku maszynach jednocześnie.
Jak bezpiecznie przechowywać connection stringi i hasła w aplikacjach .NET w chmurze?
Zamiast trzymać sekrety w appsettings.json czy – o zgrozo – w kodzie, lepiej użyć mechanizmów dostarczanych przez chmurę. W Azure będzie to Azure Key Vault + zarządzane tożsamości (Managed Identity), w AWS – Secrets Manager lub Systems Manager Parameter Store, a w GCP – Secret Manager. ASP.NET Core potrafi wczytywać te wartości bezpośrednio do konfiguracji.
Dodatkowo przydaje się zasada: aplikacja uwierzytelnia się do chmury przy pomocy tożsamości (Identity), a nie „wypalonego” klucza. Dzięki temu nie rozsyłasz haseł po repozytorium i plikach konfiguracyjnych, a rotacja sekretów nie kończy się ręcznym przepisywaniem wartości w kilkunastu projektach.






