Introduction: What is Cloud-Native?
Cloud-native is a response to the traditional software development model many of us followed for decades: building large monoliths.
In those systems, all features lived together in a single deployable unit (sometimes split into multiple deployables, but still tightly coupled and large). While this worked for small applications, it became a nightmare as systems grew. We needed long maintenance windows, updates carried high risk, and deployments were often unpredictable. Worse, scaling meant replicating the entire application, even when only one feature was under load, leading to wasteful resource usage and high operational costs.
Cloud-native architecture tackles these problems by breaking systems into independent services, often deployed as containers or serverless functions, connected through APIs or event streams. Each component can scale independently, be deployed more frequently, and evolve on its own lifecycle. So, cloud-native applications are designed to fully leverage the scalability, resilience, and agility of the cloud.
But there’s more to it than just technology. As engineers, we tend to think in bits and bytes, but we must also consider the business value. Cloud-native architectures enable teams to innovate faster, replace individual components with better ones, and adapt quickly to new ideas. Found a faster programming language? Just rewrite the relevant part. Discovered a managed cloud service that does the job better? Replace your code and decommission the old logic. This approach helps businesses reduce costs, increase availability, and respond rapidly to evolving market demands.
If you’re a developer, understanding how to build cloud-native systems using AWS, Azure, or any other cloud (including self-hosting) will help you modernize legacy apps, reduce operational overhead, and scale your solutions more effectively. But how do you choose between containers and serverless? What are the trade-offs?
Containers vs Serverless
In case you don’t know the concepts:
-
A container packages your app and its dependencies so it can run anywhere, consistently. Learn more
-
A serverless function runs a small piece of code on demand, without worrying about servers or infrastructure. Learn more
⚠️ Attention: serverless doesn’t always mean functions. You can run containers using serverless compute, like AWS Fargate or Azure Container Apps, where infrastructure is abstracted but your app still runs as a container.
Feature | Containers | Serverless |
---|---|---|
Startup time | Slow (seconds) | Fast (milliseconds) |
Resource control | Full control (CPU, memory, runtime) | Limited and abstracted |
Scaling | Manual or Auto-scaling | Auto and per-request |
Cold starts | No | Yes, but mitigated with provisioned options |
Best for | Long-running or stateful apps | Event-driven, short-lived logic |
⚠️ Performance matters: Both Azure Functions and AWS Lambda expect your application to start fast and complete quickly.
- On Azure Functions, the default execution timeout is 5 minutes for the Consumption Plan and up to 60 minutes for Premium Plans.
- On AWS Lambda, the maximum timeout is 15 minutes regardless of runtime.
Since serverless apps often cold start, especially on .NET, it’s crucial to minimize initialization time. Use techniques like:
- Minimizing dependencies and startup logic;
- Loading configurations asynchronously;
- Leveraging .NET Native AOT (Ahead-of-Time Compilation) in .NET 7 and .NET 8 to speed up boot time.
These workloads should be designed as small units of work, ideally handling one request, event, or task at a time, and returning quickly.
Microservices with Containers
🟧 AWS: ECS, EKS, Fargate
- ECS (Elastic Container Service): Managed container orchestration using EC2 or Fargate.
- EKS (Elastic Kubernetes Service): Fully managed Kubernetes service for advanced use cases.
- Fargate: Serverless compute engine for containers, compatible with ECS and EKS.
🟦 Azure: AKS, Container Apps, App Service
- AKS (Azure Kubernetes Service): Managed Kubernetes with tight integration into Azure’s ecosystem.
- Azure Container Apps: Serverless containers for microservices with built-in auto-scaling and scale to zero.
- App Service: Simplified hosting for .NET containers and APIs. While it supports other languages, my experience is primarily with .NET, and it works very well in this environment.
Serverless with Functions and Workflows
🟧 AWS: Lambda, Step Functions, EventBridge
- AWS Lambda: Runs C# functions with native support for .NET 6 and .NET 8. Automatically scales and charges per invocation.
- Step Functions: Serverless workflow engine that orchestrates multiple Lambdas or services using state machines.
- EventBridge: Central event bus for routing events between AWS services, custom apps, and SaaS providers.
🟦 Azure: Functions, Durable Functions, Event Grid
- Azure Functions: Fully managed, event-driven compute with deep integration for .NET developers. Supports auto-scaling and multiple pricing plans.
- Durable Functions: Code-first orchestration framework for complex workflows, built on top of Azure Functions.
- Event Grid: Event routing service similar to EventBridge, designed for high throughput and low-latency pub/sub scenarios.
Event-Driven Architecture
🟧 AWS: SNS, SQS, Kinesis
- SNS (Simple Notification Service): A fan-out Pub/Sub messaging system. Useful for broadcasting events to multiple subscribers like Lambda, SQS, or HTTP endpoints.
- SQS (Simple Queue Service): Fully managed message queue with support for dead-letter queues and message visibility control. Ideal for decoupling services.
- Kinesis: Real-time data streaming platform. Commonly used for ingesting telemetry, logs, IoT data, or analytics pipelines.
🟦 Azure: Service Bus, Event Hubs, Storage Queues
- Service Bus: Enterprise-grade messaging system with rich features like message sessions, transactions, and dead-lettering. Comparable to SQS but more powerful.
- Event Hubs: High-throughput, real-time event ingestion service. Equivalent to Kinesis, great for streaming large volumes of data.
- Storage Queues: Basic, cost-effective queueing for simple decoupling scenarios or background job processing.
API Strategy: Lifecycle, Management, and Protocols
APIs are the glue between microservices, clients, and external systems. A solid API strategy should address both lifecycle management and protocol choices.
API Lifecycle Management
Both AWS API Gateway and Azure API Management (APIM) provide:
- Rate limiting and throttling
- API versioning
- OAuth2 and JWT authentication
- Developer portals with documentation
- Caching and usage analytics
- Monetization options (plans, quotas, billing)
These tools allow you to expose internal services safely and control their evolution over time.
Protocols
Protocol | AWS | Azure | .NET Libraries |
---|---|---|---|
REST | API Gateway | APIM, App Service | ASP.NET Core MVC |
gRPC | App Mesh, ALB (TCP) | App Gateway, AKS | Grpc.AspNetCore |
GraphQL | AppSync | Hosted on App Service, AKS, or Functions | HotChocolate, GraphQL.NET |
⚠️ Worth mentioning: Azure Application Gateway supports gRPC over HTTP/2. AKS can expose gRPC workloads via Ingress Controllers like NGINX or Envoy. Also, Azure does not offer a managed GraphQL service like AppSync. Instead, you can self-host GraphQL APIs using .NET libraries like HotChocolate or GraphQL.NET on App Service, AKS, or Azure Functions.
- Use REST when you need simplicity and broad compatibility;
- Use gRPC for high-performance internal services;
- Use GraphQL when clients need flexible querying or real-time subscriptions.
For service-to-service communication, both clouds offer service meshes:
- App Mesh (AWS) and
- Open Service Mesh (OSM) on AKS (Azure).
These enable observability, traffic shaping, retries, and mTLS between microservices, complementing your API strategy on the infrastructure layer.
Service Discovery
In a cloud-native environment, services are dynamic… containers and functions scale up, down, or restart often. Hardcoding IPs or hostnames is not practical. Service Discovery enables services to automatically locate and communicate with each other, even as their network locations change.
Why it matters
- Ensures reliable communication between services;
- Supports horizontal scaling and load balancing;
- Allows systems to remain loosely coupled;
- Enables platform portability and fault tolerance.
🟧 AWS Options
- Cloud Map: Service registry that supports DNS- and API-based discovery. Frequently used with ECS and App Mesh.
- ECS Service Discovery: Native integration with Cloud Map for DNS-based name resolution inside your cluster.
- EKS (Kubernetes): Uses native Kubernetes DNS (
<service>.<namespace>.svc.cluster.local
) for pod-to-service communication. - App Mesh: A full service mesh that adds advanced service discovery with traffic routing, retries, observability, and mTLS for ECS and EKS workloads.
🟦 Azure Options
- AKS: Uses the same Kubernetes-native DNS as EKS.
- Dapr (AKS / Container Apps): Application-level service discovery using logical service names, with built-in retries, tracing, and mTLS.
- App Service + VNet: Enables private DNS resolution and secure internal communication between App Services and other services within the same VNet.
- Open Service Mesh (OSM) enabled on AKS: Azure’s managed service mesh option for secure service-to-service communication, observability, and advanced traffic policies.
You can use DNS, environment variables, or service meshes (like App Mesh, OSM, or Dapr) to abstract away service location and support resilient, scalable inter-service communication, regardless of your platform.
Final Thoughts
Cloud-native is not about using trendy services, but choosing the right abstraction for each scenario. Containers give you flexibility, while serverless reduces your operational burden.
Both AWS and Azure offer strong support for C# and .NET workloads. What matters is how you design your architecture, handle state, and observe your system in production.