.Net Cloud and API Design Patterns — Scalable and Efficient Architectures

Cloud and API design patterns provide proven solutions for building scalablemaintainable, and efficient distributed systems. These patterns help developers tackle challenges like load balancingsecurityfault tolerance, and API management by offering structured approaches to handling data flowauthenticationcaching, and integration. By leveraging patterns such as API GatewayCircuit BreakerRate Limiting, and CQRS, developers can ensure robusthigh-performing cloud-native applications that adapt to dynamic workloads and evolving business needs.

  • Gateway Aggregation Pattern — Optimize API calls in microservices.
  • Rate Limiting Pattern — Prevent abuse in public APIs.
  • Circuit Breaker Pattern — Improve resilience in distributed systems.
  • Saga Pattern — Handling long-running transactions in microservices.
  • BFF (Backend for Frontend) — Custom API layers for different clients.

Gateway Aggregation Pattern

In a microservices architecture, clients often need data from multiple services, which can result in multiple API calls and increased latency. The Gateway Aggregation Pattern optimizes performance by aggregating responses from multiple services into a single API call, reducing network overhead and improving response time. This pattern is typically implemented using an API Gateway that handles aggregation logic.

Common Use Cases

  • E-commerce applications: Aggregates product details, pricing, and inventory from multiple services into a single response.
  • Mobile and frontend applications: Reduces multiple API calls to improve performance, especially in low-bandwidth environments.
  • Dashboard services: Combines data from different microservices (e.g., analytics, user stats, notifications) into a single request.

SOLID Principles

  • Single Responsibility Principle (SRP): The aggregation logic is centralized in the API Gateway, preventing clients from handling multiple API calls.
  • Open/Closed Principle (OCP): New services can be added to the aggregation layer without modifying existing clients.
  • Dependency Inversion Principle (DIP): Clients depend on the aggregated API rather than multiple lower-level microservices.

💡Implementing the Gateway Aggregation Pattern: Aggregating Microservice Data in an API Gateway

In this scenario, an application needs to display user informationrecent orders, and recommendations. Instead of making three separate calls, we use an API Gateway to aggregate responses from different services.

1️⃣Define the API Gateway Controller

This GatewayController is acting as an API Gateway that aggregates data from multiple microservices into a single response.

[ApiController]
[Route("api/gateway")]
public class GatewayController : ControllerBase
{
private readonly HttpClient _httpClient;

public GatewayController(HttpClient httpClient)
{
_httpClient = httpClient;
}

[HttpGet("user-dashboard/{userId}")]
public async Task<IActionResult> GetUserDashboard(string userId)
{
var userTask = _httpClient.GetStringAsync($"http://user-service/api/users/{userId}");
var ordersTask = _httpClient.GetStringAsync($"http://order-service/api/orders/{userId}");
var recommendationsTask = _httpClient.GetStringAsync($"http://recommendation-service/api/recommendations/{userId}");

await Task.WhenAll(userTask, ordersTask, recommendationsTask);

var response = new
{
User = JsonConvert.DeserializeObject<UserDto>(await userTask),
Orders = JsonConvert.DeserializeObject<List<OrderDto>>(await ordersTask),
Recommendations = JsonConvert.DeserializeObject<List<ProductDto>>(await recommendationsTask)
};

return Ok(response);
}
}

Controller Definition

  • It’s an API controller ([ApiController]) with a route prefix of "api/gateway".
  • It inherits from ControllerBase, which provides RESTful API capabilities.
  • The HttpClient is injected via constructor dependency injection to make HTTP calls to other services.

Endpoint: GET api/gateway/user-dashboard/{userId}

  • When a client requests this endpoint with a userId, it triggers a process to fetch data from three different microservices:
  • user-service (fetches user details)
  • order-service (fetches user’s past orders)
  • recommendation-service (fetches product recommendations for the user)

Parallel API Calls

  • Instead of waiting for each API call to complete sequentially, it initiates all three API calls in parallel using Task.WhenAll(), which significantly improves performance by reducing response time.

Data Aggregation

  • Once all tasks are completed, it deserializes the JSON responses into strongly typed objects (UserDtoOrderDtoProductDto).
  • It then constructs an aggregated response object that combines user details, order history, and product recommendations.

Returning the Aggregated Response

  • The final response is returned as an HTTP 200 OK with a JSON object containing user details, orders, and recommendations in a single API call.

2️⃣Define DTOs (Data Transfer Objects) for Aggregated Data

This code defines three Data Transfer Objects (DTOs) used to structure the data returned by the API Gateway. DTOs help ensure consistent, lightweight, and structured data transfer between services.

public class UserDto
{
public string Id { get; set; }
public string Name { get; set; }
}

public class OrderDto
{
public string OrderId { get; set; }
public decimal TotalAmount { get; set; }
}

public class ProductDto
{
public string ProductId { get; set; }
public string Name { get; set; }
}
  • UserDtoRepresents user information retrieved from the user-service.
  • OrderDtoRepresents an order retrieved from the order-service.
  • ProductDto: Represents a recommended product retrieved from the recommendation-service.

3️⃣Configure Dependency Injection for HttpClient

This code is configuring and running an ASP.NET Core Web API application. It sets up dependency injection, registers necessary services, and starts the web server.

var builder = WebApplication.CreateBuilder(args);
builder.Services.AddHttpClient();
builder.Services.AddControllers();
var app = builder.Build();
app.MapControllers();
app.Run();

var builder = WebApplication.CreateBuilder(args);

  • Creates a new instance of WebApplicationBuilder, which sets up the application’s configuration, logging, and dependency injection container.

builder.Services.AddHttpClient();

  • Registers HttpClient for dependency injection, allowing the application to make HTTP requests efficiently.
  • This is essential for the Gateway Aggregation Pattern, as it enables making API calls to microservices.

builder.Services.AddControllers();

  • Registers support for API controllers, enabling the application to handle HTTP requests.
  • Required for routing and API endpoints to work.

var app = builder.Build();

  • Builds the final WebApplication instance with all configured services.

app.MapControllers();

  • Maps controller routes so that API endpoints can be accessed.

app.Run();

  • Starts the web server and begins listening for incoming HTTP requests.

Rate Limiting Pattern — Preventing API Abuse

The Rate Limiting Pattern controls the number of API requests a client can make within a specific time frame. This prevents abuseprotects server resources, and ensures fair usage among all consumers. By implementing rate limiting, you can safeguard APIs from DDoS attacks, bot overload, and excessive resource consumption. The pattern is commonly enforced using tokens, request counters, or sliding window algorithms.

Common Use Cases

  • Public APIs with Tiered Access — Ensures free-tier users don’t overload the system while premium users get higher limits.
  • DDoS Mitigation — Blocks excessive requests from malicious bots to prevent system crashes.
  • Preventing Spam and Abuse — Limits how frequently a user can submit forms, preventing automated spam submissions.
  • Optimizing Resource Usage — Ensures database queries, API responses, and server resources are used efficiently.

SOLID Principles

  • Single Responsibility Principle (SRP): Keeps rate-limiting logic separate from business logic, making it easier to maintain.
  • Open/Closed Principle (OCP): Allows adding new rate-limiting strategies (fixed window, sliding window, token bucket) without modifying existing logic.
  • Dependency Inversion Principle (DIP): Clients depend on an abstraction of rate-limiting, not a concrete implementation, making it flexible.

💡Implementing Rate Limiting in ASP.NET Core

A public API allows users to fetch stock market data. To prevent abuse, we limit requests to 5 per minute per user.

1️⃣Install the Required NuGet Package

In this example, we use AspNetCoreRateLimit, a popular library for implementing rate limiting in ASP.NET Core.

  • This package simplifies rate limiting without requiring custom middleware.
  • It supports different rate-limiting strategies (fixed window, sliding window, token bucket).
dotnet add package AspNetCoreRateLimit

2️⃣Configure Rate Limiting in appsettings.json

Define rate limits based on API keys or IP addresses.

  • "Limit": 5 restricts each client to 5 requests per minute.
  • "Endpoint": "*" applies this rule to all API endpoints.
  • Returns HTTP 429 (Too Many Requests) if the limit is exceeded.
{
"IpRateLimiting": {
"EnableEndpointRateLimiting": true,
"StackBlockedRequests": false,
"HttpStatusCode": 429,
"RealIpHeader": "X-Real-IP",
"GeneralRules": [
{
"Endpoint": "*",
"Period": "1m",
"Limit": 5
}
]
}
}

3️⃣Register Rate Limiting Services in Program.cs

Modify Program.cs to enable rate limiting.

  • AddMemoryCache() stores rate limit counters in memory.
  • UseIpRateLimiting() enforces the rate limits for each API request.
var builder = WebApplication.CreateBuilder(args);

// Add Rate Limiting services
builder.Services.AddMemoryCache();
builder.Services.Configure<IpRateLimitOptions>(builder.Configuration.GetSection("IpRateLimiting"));
builder.Services.AddInMemoryRateLimiting();
builder.Services.AddSingleton<IRateLimitConfiguration, RateLimitConfiguration>();

builder.Services.AddControllers();

var app = builder.Build();

app.UseIpRateLimiting(); // Enable rate limiting middleware

app.MapControllers();
app.Run();

4️⃣Apply Rate Limiting to a Controller

Limit API requests for fetching stock market data.

  • Users can request stock prices by sending GET /api/stocks/AAPL.
  • If a user exceeds 5 requests per minute, they receive an HTTP 429 Too Many Requests error.
[ApiController]
[Route("api/stocks")]
public class StocksController : ControllerBase
{
private static readonly List<string> Stocks = new() { "AAPL", "GOOGL", "AMZN", "TSLA", "MSFT" };

[HttpGet("{symbol}")]
public IActionResult GetStock(string symbol)
{
if (!Stocks.Contains(symbol.ToUpper()))
return NotFound("Stock not found");

return Ok(new { Symbol = symbol.ToUpper(), Price = new Random().Next(100, 500) });
}
}

Circuit Breaker Pattern — Improve Resilience in Distributed Systems

The Circuit Breaker Pattern prevents cascading failures in distributed systems by temporarily blocking requests to a failing service. Instead of continuously making requests to an unavailable or slow service, the circuit breaker detects failures and switches to a fallback mode. Once the service recovers, the circuit breaker allows traffic again. This protects system resources, improves response times, and enhances overall system resilience.

Common Use Cases

  • Microservices Communication — Prevents one failing microservice from overloading others with retries.
  • Third-Party API Calls — Stops excessive calls to an external API that is experiencing downtime.
  • Database Queries — Prevents repeated slow queries from bringing down an application.
  • Cloud-Based Applications — Ensures high availability in AzureAWS, and other cloud platforms.

SOLID Principles

  • Single Responsibility Principle (SRP): Keeps failure detection logic separate from business logic.
  • Open/Closed Principle (OCP): Allows different failure handling strategies without modifying core application logic.
  • Dependency Inversion Principle (DIP): Clients depend on an abstract failure-handling mechanism rather than a specific implementation.

💡Implementing Circuit Breaker in ASP.NET Core

An e-commerce website retrieves product recommendations from a third-party API. If the API fails, the system should return a cached response instead of continuously retrying the failed service.

1️⃣Install the Required NuGet Package

We use Polly, a popular .NET resilience library, to implement the circuit breaker.

dotnet add package Polly
  • Polly provides a circuit breaker that detects failures and prevents unnecessary retries.
  • It helps maintain system stability and improves the user experience.

2️⃣Configure Circuit Breaker Policy in Program.cs

Define the circuit breaker to allow 3 failures before blocking calls for 30 seconds.

using Polly;
using Polly.CircuitBreaker;

var builder = WebApplication.CreateBuilder(args);

var circuitBreakerPolicy = Policy
.Handle<HttpRequestException>()
.CircuitBreakerAsync(3, TimeSpan.FromSeconds(30));

builder.Services.AddSingleton(circuitBreakerPolicy);
builder.Services.AddHttpClient();

var app = builder.Build();
app.MapControllers();
app.Run();
  • If 3 consecutive API calls fail, the circuit opens and blocks further calls for 30 seconds.
  • Prevents excessive retries to a failing API, reducing system load.

3️⃣Implement the Circuit Breaker in an API Service

We wrap external API calls in the circuit breaker.

public class RecommendationService
{
private readonly HttpClient _httpClient;
private readonly AsyncCircuitBreakerPolicy _circuitBreakerPolicy;

public RecommendationService(HttpClient httpClient, AsyncCircuitBreakerPolicy circuitBreakerPolicy)
{
_httpClient = httpClient;
_circuitBreakerPolicy = circuitBreakerPolicy;
}

public async Task<List<string>> GetRecommendationsAsync()
{
return await _circuitBreakerPolicy.ExecuteAsync(async () =>
{
var response = await _httpClient.GetStringAsync("https://external-api.com/recommendations");
return JsonConvert.DeserializeObject<List<string>>(response);
});
}
}
  • The service executes API calls through the circuit breaker.
  • If the external API fails repeatedly, further calls are blocked, reducing system strain.

4️⃣Implement a Fallback for When the Circuit is Open

If the external API is down, return a cached response instead.

public async Task<List<string>> GetRecommendationsAsync()
{
return await _circuitBreakerPolicy.ExecuteAsync(async () =>
{
var response = await _httpClient.GetStringAsync("https://external-api.com/recommendations");
return JsonConvert.DeserializeObject<List<string>>(response);
})
.ContinueWith(task =>
{
if (task.IsFaulted)
{
return new List<string> { "Fallback Item 1", "Fallback Item 2" };
}
return task.Result;
});
}
  • If the API fails, the system returns a cached list of recommendations.
  • Prevents users from seeing an error page due to an unavailable API.

Saga Pattern — Handling Long-Running Transactions in Microservices

In a microservices architecture, transactions often span multiple services, making traditional database transactions impractical. The Saga Pattern ensures data consistency across multiple services by breaking a long-running transaction into smaller, manageable steps, each with a compensating action to undo changes if something goes wrong. This prevents data inconsistencies, reduces failure risks, and enhances the resilience of distributed systems.

Common Use Cases

  • E-commerce Order Processing — Ensures order creation, payment, and inventory updates are consistent across multiple services.
  • Bank Transactions — Manages multi-step fund transfers, ensuring rollback if any step fails.
  • Booking Systems — Coordinates hotel, flight, and car rental reservations without partial failures.
  • Subscription Billing — Ensures recurring payments and service activation stay synchronized.

SOLID Principles

  • Single Responsibility Principle (SRP): Each microservice handles its own transaction steps independently.
  • Open/Closed Principle (OCP): Allows new business rules and services to be added without changing the existing transaction logic.
  • Dependency Inversion Principle (DIP): Services depend on an orchestrator or event-driven model, not on direct service calls.

💡Implementing the Saga Pattern in ASP.NET Core

An e-commerce platform processes an order across multiple services:

  1. Order Service — Creates an order.
  2. Payment Service — Processes payment.
  3. Inventory Service — Reserves stock.
  4. Shipping Service — Schedules delivery.

If any step fails, the system compensates by undoing previous actions.

In this example, we use Orchestration, where a Saga Orchestrator (Order Service) manages the transaction flow.

  • Orchestration: A central orchestrator service (like OrderService) calls other services (like PaymentServiceInventoryService, etc.) and controls the flow of the transaction. It handles the failure recovery process as well.
  • Choreography: Each service involved in the saga is aware of the saga’s flow and can independently decide how to participate and how to compensate for failures without a central controller.

1️⃣Define the Order Service (Saga Orchestrator)

The Order Service manages transaction steps and handles compensations on failure.

[ApiController]
[Route("api/orders")]
public class OrderController : ControllerBase
{
private readonly ISagaOrchestrator _sagaOrchestrator;

public OrderController(ISagaOrchestrator sagaOrchestrator)
{
_sagaOrchestrator = sagaOrchestrator;
}

[HttpPost]
public async Task<IActionResult> PlaceOrder(OrderRequest request)
{
var result = await _sagaOrchestrator.ProcessOrderAsync(request);
return result.Success ? Ok("Order placed successfully") : BadRequest(result.Message);
}
}

Controller Definition:

  • [ApiController]: This attribute indicates that this class is an API controller, making it easier to handle HTTP requests with automatic model validation and response formatting.
  • [Route("api/orders")]: The route template specifies that the controller’s base URL is api/orders. This means any request to api/orders will be handled by this controller.
  • OrderController inherits from ControllerBase: This class is responsible for managing HTTP requests related to orders.

Constructor Injection:

  • private readonly ISagaOrchestrator _sagaOrchestrator;: A dependency on ISagaOrchestrator is injected into the controller via its constructor. This orchestrator is responsible for managing the saga — the coordination of multiple steps (e.g., payment, inventory check, shipment) involved in placing an order.
  • The constructor sets the _sagaOrchestrator to the provided ISagaOrchestrator instance.

PlaceOrder Action Method:

  • [HttpPost]: This attribute marks the method as an HTTP POST endpoint, meaning it will respond to requests like POST api/orders.
  • public async Task<IActionResult> PlaceOrder(OrderRequest request): This method handles the HTTP POST request to place an order. It accepts an OrderRequest object, which contains the data for the order (such as product details, customer info, etc.).

Calling the Saga Orchestrator:

  • The controller calls the ProcessOrderAsync method of the Saga Orchestrator. This orchestrator coordinates the saga, which typically involves calling multiple services (e.g., payment, inventory, shipping) in sequence, ensuring that each step of the transaction is handled correctly and rolling back previous steps if any of them fail.

Return Result:

  • Based on the outcome of the ProcessOrderAsync method, the controller returns an HTTP response:
  • If the result is successful (result.Success is true), it returns an HTTP 200 OK response with the message “Order placed successfully”.
  • If the result indicates failure (result.Success is false), it returns an HTTP 400 Bad Request with the failure message (result.Message).

2️⃣Implement the Saga Orchestrator

Manages the workflow and compensates on failures.

public class SagaOrchestrator : ISagaOrchestrator
{
private readonly IPaymentService _paymentService;
private readonly IInventoryService _inventoryService;
private readonly IShippingService _shippingService;

public SagaOrchestrator(IPaymentService paymentService, IInventoryService inventoryService, IShippingService shippingService)
{
_paymentService = paymentService;
_inventoryService = inventoryService;
_shippingService = shippingService;
}

public async Task<SagaResult> ProcessOrderAsync(OrderRequest request)
{
try
{
await _paymentService.ProcessPayment(request.OrderId, request.Amount);
await _inventoryService.ReserveStock(request.OrderId, request.ProductId);
await _shippingService.ScheduleDelivery(request.OrderId, request.Address);
return new SagaResult(true, "Order completed successfully.");
}
catch (Exception ex)
{
await Compensate(request.OrderId);
return new SagaResult(false, $"Transaction failed: {ex.Message}");
}
}

private async Task Compensate(string orderId)
{
await _shippingService.CancelDelivery(orderId);
await _inventoryService.ReleaseStock(orderId);
await _paymentService.RefundPayment(orderId);
}
}
  • The SagaOrchestrator class coordinates a multi-step process for handling an order, ensuring that steps like payment, inventory reservation, and shipping are performed in sequence.
  • If any step fails, the orchestrator calls the Compensate method to undo the previous successful steps, ensuring that the system remains in a consistent state.
  • The orchestrator is a central controller for the saga, ensuring data consistency and preventing partial updates across services.

3️⃣Implement Payment, Inventory, and Shipping Services

Each service must support compensating actions.

✅ Payment Service

This code implements a PaymentService class that provides the functionality for processing payments and refunding payments in the context of an order processing system.

public class PaymentService : IPaymentService
{
public async Task ProcessPayment(string orderId, decimal amount)
{
Console.WriteLine($"Processing payment of {amount} for order {orderId}");
}

public async Task RefundPayment(string orderId)
{
Console.WriteLine($"Refunding payment for order {orderId}");
}
}

✅ Inventory Service

This code implements an InventoryService class that provides the functionality for reserving and releasing stock in the context of an order processing system.

public class InventoryService : IInventoryService
{
public async Task ReserveStock(string orderId, string productId)
{
Console.WriteLine($"Reserving stock for order {orderId}, product {productId}");
}

public async Task ReleaseStock(string orderId)
{
Console.WriteLine($"Releasing stock for order {orderId}");
}
}

✅ Shipping Service

This code implements a ShippingService class that provides the functionality for scheduling and canceling deliveries in an order processing system.

public class ShippingService : IShippingService
{
public async Task ScheduleDelivery(string orderId, string address)
{
Console.WriteLine($"Scheduling delivery for order {orderId} to {address}");
}

public async Task CancelDelivery(string orderId)
{
Console.WriteLine($"Canceling delivery for order {orderId}");
}
}
  • Each service handles its own responsibilities.
  • Services support rollback for failure recovery.

BFF (Backend for Frontend) — Custom API Layers for Different Clients

The Backend for Frontend (BFF) Pattern provides a tailored API layer for each frontend client (e.g., web, mobile, desktop). Instead of exposing a single general-purpose API, the BFF optimizes responses by handling client-specific data transformations, aggregations, and security concerns. This reduces frontend complexity, improves performance, and enhances security by offloading business logic from the client to the server.

Common Use Cases

  • Web vs. Mobile APIs — Mobile apps may require lighter payloads with compressed images, while web apps need richer data.
  • Multi-Tenant Applications — Different frontend clients may have unique authentication or data access rules.
  • Performance Optimization — Reduces unnecessary data transfers and API over-fetching, improving response times.
  • Security and Authorization — Provides a single point for token validation and client-specific security policies.

SOLID Principles

  • Single Responsibility Principle (SRP): Each BFF is responsible for handling the needs of a specific frontend, keeping concerns separated.
  • Open/Closed Principle (OCP): New frontends can have dedicated BFFs without modifying existing ones.
  • Dependency Inversion Principle (DIP): The frontend depends on an abstraction (BFF) instead of direct backend services.

💡Implementing a BFF for Web and Mobile Clients

An e-commerce platform serves both web and mobile users. Web users need detailed product descriptions, while mobile users need faster responses with minimal data. Instead of exposing the same API to both clients, we create two BFFs:

  • WebBff → Fetches full product details
  • MobileBff → Fetches only essential data

1️⃣Define the Web BFF API

The Web BFF returns complete product details.

[ApiController]
[Route("api/web/products")]
public class WebBffController : ControllerBase
{
private readonly IProductService _productService;

public WebBffController(IProductService productService)
{
_productService = productService;
}

[HttpGet("{id}")]
public async Task<IActionResult> GetProductDetails(int id)
{
var product = await _productService.GetProductAsync(id);
return product != null ? Ok(product) : NotFound();
}
}
  • Fetches full product details for web users.
  • Calls the ProductService which interacts with microservices or databases.

2️⃣Define the Mobile BFF API

The Mobile BFF returns a lightweight response with only necessary product data.

[ApiController]
[Route("api/mobile/products")]
public class MobileBffController : ControllerBase
{
private readonly IProductService _productService;

public MobileBffController(IProductService productService)
{
_productService = productService;
}

[HttpGet("{id}")]
public async Task<IActionResult> GetProductSummary(int id)
{
var product = await _productService.GetProductAsync(id);
if (product == null) return NotFound();

var summary = new
{
product.Id,
product.Name,
product.Price
};

return Ok(summary);
}
}
  • Returns only essential fields (IdNamePrice) to improve mobile performance.
  • Reduces network load and response time.

3️⃣Implement the Product Service (Shared by Both BFFs)

Handles the retrieval of product data.

public class ProductService : IProductService
{
private readonly HttpClient _httpClient;

public ProductService(HttpClient httpClient)
{
_httpClient = httpClient;
}

public async Task<ProductDto> GetProductAsync(int id)
{
var response = await _httpClient.GetStringAsync($"http://product-service/api/products/{id}");
return JsonConvert.DeserializeObject<ProductDto>(response);
}
}
  • Calls the backend product microservice to fetch product data.
  • Used by both WebBff and MobileBff.

4️⃣Define the Shared Product DTO

Standard structure for backend communication.

public class ProductDto
{
public int Id { get; set; }
public string Name { get; set; }
public string Description { get; set; }
public decimal Price { get; set; }
public string ImageUrl { get; set; }
}

Ensures a consistent data structure across BFFs.

Extending BFF with GraphQL for More Flexibility

By integrating GraphQL into the Backend for Frontend (BFF) Pattern, we allow clients (web, mobile) to request exactly the data they need, reducing over-fetching and under-fetching of data. Unlike REST, where endpoints return fixed responses, GraphQL enables clients to specify which fields they require, leading to optimized network performance.

Implementing GraphQL in BFF

An e-commerce platform serves both web and mobile users. Web users need detailed product descriptions, while mobile users only need name, price, and image. Instead of creating separate REST APIs, we implement a GraphQL API that allows each frontend to request exactly what it needs.

1️⃣Install Required NuGet Packages

First, install HotChocolate, a popular GraphQL library for .NET.

dotnet add package HotChocolate.AspNetCore
dotnet add package HotChocolate.AspNetCore.Serialization
  • HotChocolate simplifies GraphQL API implementation in ASP.NET Core.
  • Enables query customization without creating multiple endpoints.

2️⃣Define the GraphQL Query Schema

Create a GraphQL Query Type to expose product data dynamically.

public class ProductQuery
{
private readonly IProductService _productService;

public ProductQuery(IProductService productService)
{
_productService = productService;
}

public async Task<ProductDto> GetProduct(int id)
{
return await _productService.GetProductAsync(id);
}
}
  • Defines a GraphQL query named GetProduct.
  • Calls the shared Product Service to fetch product details.

3️⃣Define the GraphQL Schema (Product Type)

Specifies which fields are queryable.

public class ProductType : ObjectType<ProductDto>
{
protected override void Configure(IObjectTypeDescriptor<ProductDto> descriptor)
{
descriptor.Field(p => p.Id).Type<NonNullType<IntType>>();
descriptor.Field(p => p.Name).Type<NonNullType<StringType>>();
descriptor.Field(p => p.Description).Type<StringType>();
descriptor.Field(p => p.Price).Type<NonNullType<DecimalType>>();
descriptor.Field(p => p.ImageUrl).Type<StringType>();
}
}
  • Defines which fields are exposed in GraphQL queries.
  • Enforces strong typing, ensuring predictable responses.

4️⃣Register GraphQL in Program.cs

Modify Program.cs to enable GraphQL.

var builder = WebApplication.CreateBuilder(args);

builder.Services
.AddGraphQLServer()
.AddQueryType<ProductQuery>();

var app = builder.Build();

app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapGraphQL(); // Exposes GraphQL at "/graphql"
});

app.Run();
  • Registers GraphQL services in the dependency container.
  • Maps the GraphQL endpoint (/graphql).

5️⃣Query the API Using GraphQL

Once the API is running, we can query it using GraphQL Playground (http://localhost:5000/graphql).

Web Client Query (Full Product Details)

A web app fetches all product fields.

query {
getProduct(id: 1) {
id
name
description
price
imageUrl
}
}

Returns:

{
"data": {
"getProduct": {
"id": 1,
"name": "Laptop Pro",
"description": "High-end gaming laptop",
"price": 1299.99,
"imageUrl": "https://cdn.example.com/laptop.jpg"
}
}
}

Mobile Client Query (Minimal Data for Faster Loading)

A mobile app only requests essential fields.

query {
getProduct(id: 1) {
name
price
imageUrl
}
}

Returns:

{
"data": {
"getProduct": {
"name": "Laptop Pro",
"price": 1299.99,
"imageUrl": "https://cdn.example.com/laptop.jpg"
}
}
}
  • One API for All Clients — No need to build separate REST endpoints.
  • Optimized for Performance — Each client fetches only the data it needs.
  • Scalable — Easily extend schema for new frontends like smartwatches, IoT, or voice assistants.

The application of Cloud and API design patterns is essential for creating robustscalable, and high-performance distributed systems. By adopting patterns like API GatewayCircuit BreakerRate LimitingSaga, and BFF, developers can address common challenges in cloud-native applications, from optimizing API calls and enhancing system resilience to ensuring secure and efficient communication between microservices. These patterns provide not only a strategic approach to handling complex workflows and varying client needs but also enable the development of adaptivemaintainable solutions that evolve with growing business demands and dynamic workloads. Leveraging these proven design patterns is key to building reliable and scalable cloud architectures that meet both current and future demands.