Author

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'
  }
}