Example Requirement using the CARE Framework
Example: “Order Processor” Microservice Requirement
| Section | AI-Ready Specification |
|---|---|
| Context | Create a .NET 8 Isolated Azure Function for order processing. It must support a Dead Letter Pattern to capture invalid orders for manual recovery. |
| Action | 1. Input: HTTP POST JSON (OrderId, CustomerEmail, TotalAmount). |
| 2. Happy Path: Valid orders must be persisted to Azure Table Storage (OrdersTable). | |
| 3. Failure Path: If validation fails (e.g., negative TotalAmount), the function must: | |
| - Return 400 Bad Request. | |
| - Persist the invalid payload to an Azure Storage Queue named order-error-poison. | |
| Result | • Use a Multi-Output Binding class (OrderResponse) to route data to Table and Queue storage. |
| • Implement Identity-based connections (__tableServiceUri and __queueServiceUri). | |
| • Include structured logging for both successful and “dead-lettered” outcomes. | |
| Evaluation Acceptance Criteria: | 1. No hardcoded connection strings. |
| 2. 201 Created only if written to Table Storage. | |
| 3. Verify that the FailedOrder property is null during successful runs to avoid duplicate queue entries | |
| 4. Unit test stubs using xUnit and Moq for both success and failure scenarios. |
.N.B. During the generation process, the AI will surgest new features or improvements. These should be carefully evaluated to ensure they align with the original requirement and do not introduce unnecessary complexity. Once you are happy the requirement should be fully updated to reflect the desired outcome, you can proceed to generate the final code.
Execute Requirements
Fed the requirement into an AI coding assistant (e.g., GPT-4, Claude Code) and received the following code output:
Model : OrderEntity
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
public class OrderResponse
{
// Routes to Table Storage if not null
[TableOutput("OrdersTable", Connection = "OrdersTableService")]
public OrderEntity? NewOrder { get; set; }
// Routes to Queue Storage if not null (Poison Queue)
[QueueOutput("order-error-poison", Connection = "OrderQueueService")]
public OrderRequest? FailedOrder { get; set; }
// Required for the HTTP Trigger's return value
public HttpResponseData HttpResponse { get; set; } = null!;
}
public record OrderRequest(string OrderId, string CustomerEmail, double TotalAmount);
public class OrderEntity
{
public string PartitionKey { get; set; } = "Orders";
public string RowKey { get; set; } = null!;
public string CustomerEmail { get; set; } = null!;
public double TotalAmount { get; set; }
}
Azure Function : OrderProcessor
using System.Net;
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
public class OrderProcessor
{
private readonly ILogger _logger;
public OrderProcessor(ILoggerFactory loggerFactory)
{
_logger = loggerFactory.CreateLogger<OrderProcessor>();
}
[Function("ProcessOrder")]
public async Task<OrderResponse> Run(
[HttpTrigger(AuthorizationLevel.Function, "post")] HttpRequestData req,
FunctionContext executionContext)
{
var _logger = executionContext.GetLogger("ProcessOrder");
var order = await req.ReadFromJsonAsync<OrderRequest>();
// Validation logic (Failure Path)
if (order == null || order.TotalAmount < 0)
{
_logger.LogWarning("Validation failed for OrderId: {OrderId}", order?.OrderId ?? "Unknown");
var badResponse = req.CreateResponse(System.Net.HttpStatusCode.BadRequest);
return new OrderResponse
{
HttpResponse = badResponse,
FailedOrder = order, // Sent to 'order-error-poison' queue
NewOrder = null // Ensures no Table Storage entry
};
}
// Success Path
_logger.LogInformation("Processing successful order: {OrderId}", order.OrderId);
var okResponse = req.CreateResponse(System.Net.HttpStatusCode.Created);
return new OrderResponse
{
HttpResponse = okResponse,
NewOrder = new OrderEntity
{
RowKey = order.OrderId,
CustomerEmail = order.CustomerEmail,
TotalAmount = order.TotalAmount
},
FailedOrder = null // Ensures no Poison Queue entry
};
}
}local.settings.json
{
"IsEncrypted": false,
"Values": {
"AzureWebJobsStorage": "UseDevelopmentStorage=true",
"FUNCTIONS_WORKER_RUNTIME": "dotnet-isolated",
"OrdersTableService__tableServiceUri": "https://<account>.table.core.windows.net/",
"OrderQueueService__queueServiceUri": "https://<account>.queue.core.windows.net/"
}
}Unit Test Stub: OrderProcessorTests
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.Logging;
using Moq;
using System.Net;
using System.Text;
using Xunit;
public class OrderFunctionTests
{
[Fact]
public async Task ProcessOrder_ValidInput_Returns201AndPersistsToTable()
{
// Arrange
var mockContext = new Mock<FunctionContext>();
var request = MockHelpers.CreateHttpRequestData(new OrderRequest("ORD123", "test@me.com", 100.0));
var function = new OrderFunction();
// Act
var result = await function.Run(request, mockContext.Object);
// Assert
Assert.Equal(HttpStatusCode.Created, result.HttpResponse.StatusCode);
Assert.NotNull(result.NewOrder);
Assert.Null(result.FailedOrder);
}
[Fact]
public async Task ProcessOrder_InvalidAmount_Returns400AndQueuesError()
{
// Arrange
var mockContext = new Mock<FunctionContext>();
var request = MockHelpers.CreateHttpRequestData(new OrderRequest("BAD1", "err@me.com", -10.0));
var function = new OrderFunction();
// Act
var result = await function.Run(request, mockContext.Object);
// Assert
Assert.Equal(HttpStatusCode.BadRequest, result.HttpResponse.StatusCode);
Assert.Null(result.NewOrder);
Assert.NotNull(result.FailedOrder);
}
}MockHelpers.cs
using Microsoft.Azure.Functions.Worker;
using Microsoft.Azure.Functions.Worker.Http;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using Moq;
using System.Text;
using System.Text.Json;
public static class MockHelpers
{
public static HttpRequestData CreateHttpRequestData<T>(T body)
{
var json = JsonSerializer.Serialize(body);
var byteArray = Encoding.UTF8.GetBytes(json);
var memoryStream = new MemoryStream(byteArray);
memoryStream.Position = 0;
// 1. Setup Service Provider for Serializer
var serviceCollection = new ServiceCollection();
serviceCollection.AddSingleton(Options.Create(new WorkerOptions()));
var serviceProvider = serviceCollection.BuildServiceProvider();
// 2. Mock FunctionContext to provide InstanceServices
var mockContext = new Mock<FunctionContext>();
mockContext.Setup(c => c.InstanceServices).Returns(serviceProvider);
// 3. Mock HttpRequestData
var mockRequest = new Mock<HttpRequestData>(mockContext.Object);
mockRequest.Setup(r => r.Body).Returns(memoryStream);
// Mock the CreateResponse method to return a valid HttpResponseData
mockRequest.Setup(r => r.CreateResponse()).Returns(() =>
{
var response = new Mock<HttpResponseData>(mockContext.Object);
response.SetupProperty(r => r.StatusCode);
response.SetupProperty(r => r.Headers, new HttpHeadersCollection());
response.SetupProperty(r => r.Body, new MemoryStream());
return response.Object;
});
return mockRequest.Object;
}
}Github Action Yaml for CI/CD Deployment
name: Build and Deploy Order Processor
on:
push:
branches: [ "main" ]
permissions:
id-token: write # Required for OIDC authentication
contents: read
env:
AZURE_FUNCTIONAPP_NAME: 'your-function-app-name'
DOTNET_VERSION: '8.0.x'
jobs:
build-and-deploy:
runs-on: ubuntu-latest
steps:
- name: 'Checkout GitHub Action'
uses: actions/checkout@v4
- name: 'Setup DotNet ${{ env.DOTNET_VERSION }}'
uses: actions/setup-dotnet@v4
with:
dotnet-version: ${{ env.DOTNET_VERSION }}
- name: 'Restore and Build'
run: dotnet build --configuration Release
- name: 'Run Unit Tests'
run: dotnet test --no-build --configuration Release --verbosity normal
- name: 'Publish'
run: dotnet publish -c Release -o ./publish
- name: 'Login to Azure via OIDC'
uses: azure/login@v2
with:
client-id: ${{ secrets.AZURE_CLIENT_ID }}
tenant-id: ${{ secrets.AZURE_TENANT_ID }}
subscription-id: ${{ secrets.AZURE_SUBSCRIPTION_ID }}
- name: 'Deploy to Azure Functions'
uses: Azure/functions-action@v1
with:
app-name: ${{ env.AZURE_FUNCTIONAPP_NAME }}
package: './publish'Bicep Template for Infrastructure
param location string = resourceGroup().location
param appName string = 'order-proc-${uniqueString(resourceGroup().id)}'
param storageName string = 'st${uniqueString(resourceGroup().id)}'
// 1. Storage Account (for Tables, Queues, and Function Host)
resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = {
name: storageName
location: location
sku: { name: 'Standard_LRS' }
kind: 'StorageV2'
}
// 2. Hosting Plan
resource hostingPlan 'Microsoft.Web/serverfarms@2022-09-01' = {
name: '${appName}-plan'
location: location
sku: { name: 'Y1', tier: 'Dynamic' } // Consumption Plan
}
// 3. Function App with System-Assigned Identity
resource functionApp 'Microsoft.Web/sites@2022-09-01' = {
name: appName
location: location
kind: 'functionapp'
identity: { type: 'SystemAssigned' }
properties: {
serverFarmId: hostingPlan.id
siteConfig: {
netFrameworkVersion: 'v8.0'
appSettings: [
{ name: 'AzureWebJobsStorage__accountName', value: storageAccount.name }
{ name: 'FUNCTIONS_EXTENSION_VERSION', value: '~4' }
{ name: 'FUNCTIONS_WORKER_RUNTIME', value: 'dotnet-isolated' }
// Identity-based Connection Mappings
{ name: 'OrdersTableService__tableServiceUri', value: storageAccount.properties.primaryEndpoints.table }
{ name: 'OrderQueueService__queueServiceUri', value: storageAccount.properties.primaryEndpoints.queue }
]
}
}
}
// 4. RBAC Roles (Least Privilege)
var storageTableDataContributor = '0a9a4e1f-409e-4856-9975-1546272b248a'
var storageQueueDataMessageSender = 'c6a89b2d-59bc-44d0-9896-0f6e12d7b80a'
resource tableRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(storageAccount.id, 'TableContributor', functionApp.id)
scope: storageAccount
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', storageTableDataContributor)
principalId: functionApp.identity.principalId
principalType: 'ServicePrincipal'
}
}
resource queueRole 'Microsoft.Authorization/roleAssignments@2022-04-01' = {
name: guid(storageAccount.id, 'QueueSender', functionApp.id)
scope: storageAccount
properties: {
roleDefinitionId: subscriptionResourceId('Microsoft.Authorization/roleDefinitions', storageQueueDataMessageSender)
principalId: functionApp.identity.principalId
principalType: 'ServicePrincipal'
}
}