# Testing Methodologies Standards for Azure
This document outlines the testing methodologies standards for Azure development, providing guidance for developers and serving as context for AI coding assistants. It focuses on unit, integration, and end-to-end (E2E) testing within the Azure ecosystem, emphasizing modern approaches, patterns, and the latest Azure features.
## 1. General Testing Principles
### 1.1 Importance of Testing
* **Why:** Thorough testing is crucial for ensuring the reliability, security, and performance of Azure applications. It helps identify defects early in the development lifecycle, reducing the cost and effort of fixing them later.
### 1.2 Testing Pyramid
* **Why:** The testing pyramid emphasizes having more unit tests than integration tests, and more integration tests than end-to-end tests. This approach focuses on fast, isolated tests at the base and slower, more comprehensive tests at the top.
* **Do This:** Balance your testing efforts according to the pyramid: broad unit test coverage, targeted integration tests, and critical path E2E tests.
* **Don't Do This:** Rely heavily on end-to-end tests while neglecting unit and integration tests, as this makes debugging and root cause analysis difficult.
## 2. Unit Testing
### 2.1 Focus and Scope
* **Why:** Unit tests verify the behavior of individual components or functions in isolation. They are fast to execute and provide immediate feedback on code changes.
### 2.2 Standards
* **Do This:**
* Write focused unit tests that cover all code paths and edge cases within a component.
* Use mocking frameworks to isolate the component being tested from external dependencies.
* Follow the Arrange-Act-Assert (AAA) pattern for structuring unit tests.
* Aim for high code coverage (80% or higher) with meaningful assertions.
* **Don't Do This:**
* Write unit tests that depend on external resources or databases.
* Test implementation details rather than the intended behavior.
* Skip testing exception handling and error conditions.
### 2.3 Code Examples
#### 2.3.1 Using Azure Functions for Unit Testing
"""csharp
// Function to be tested
public static class MyFunction
{
[FunctionName("MyFunction")]
public static async Task Run(
[HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req,
ILogger log)
{
log.LogInformation("C# HTTP trigger function processed a request.");
string name = req.Query["name"];
string requestBody = await new StreamReader(req.Body).ReadToEndAsync();
dynamic data = JsonConvert.DeserializeObject(requestBody);
name = name ?? data?.name;
string responseMessage = string.IsNullOrEmpty(name)
? "This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response."
: $"Hello, {name}. This HTTP triggered function executed successfully.";
return new OkObjectResult(responseMessage);
}
}
// Unit Test using xUnit and Moq
using Xunit;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Logging;
using Moq;
using System.IO;
using Newtonsoft.Json;
using System.Threading.Tasks;
public class MyFunctionTests
{
[Fact]
public async Task MyFunction_WithNameProvided_ReturnsGreeting()
{
// Arrange
var request = new Mock();
var query = new Mock();
query.Setup(q => q["name"]).Returns("TestUser");
request.Setup(r => r.Query).Returns(query.Object);
var logger = Mock.Of();
// Act
var result = await MyFunction.Run(request.Object, logger);
// Assert
var okResult = Assert.IsType(result);
Assert.Equal("Hello, TestUser. This HTTP triggered function executed successfully.", okResult.Value);
}
[Fact]
public async Task MyFunction_NoNameProvided_ReturnsGenericGreeting()
{
// Arrange
var request = new Mock();
var query = new Mock();
request.Setup(r => r.Query).Returns(query.Object);
var logger = Mock.Of();
// Act
var result = await MyFunction.Run(request.Object, logger);
// Assert
var okResult = Assert.IsType(result);
Assert.Equal("This HTTP triggered function executed successfully. Pass a name in the query string or in the request body for a personalized response.", okResult.Value);
}
[Fact]
public async Task MyFunction_NameInBody_ReturnsGreeting()
{
// Arrange
var request = new Mock();
var query = new Mock();
request.Setup(r => r.Query).Returns(query.Object);
var ms = new MemoryStream();
var sw = new StreamWriter(ms);
string json = JsonConvert.SerializeObject(new { name = "TestUserBody" });
sw.Write(json);
sw.Flush();
ms.Position = 0;
request.Setup(r => r.Body).Returns(ms);
var logger = Mock.Of();
// Act
var result = await MyFunction.Run(request.Object, logger);
// Assert
var okResult = Assert.IsType(result);
Assert.Equal("Hello, TestUserBody. This HTTP triggered function executed successfully.", okResult.Value);
}
}
"""
#### 2.3.2 Mocking Azure Service Dependencies (Example: Cosmos DB)
"""csharp
using Moq;
using Microsoft.Azure.Cosmos;
using Xunit;
using System.Threading;
using System.Threading.Tasks;
public class CosmosDbServiceTests
{
[Fact]
public async Task GetItemAsync_ItemExists_ReturnsItem()
{
// Arrange
var mockCosmosClient = new Mock();
var mockDatabase = new Mock();
var mockContainer = new Mock();
// Setup mock behavior
mockCosmosClient.Setup(client => client.GetDatabase(It.IsAny())).Returns(mockDatabase.Object);
mockDatabase.Setup(db => db.GetContainer(It.IsAny())).Returns(mockContainer.Object);
// Setup a successful item retrieval (using a FeedResponse for simplicity in this example)
var mockItemResponse = new Mock>();
mockItemResponse.Setup(response => response.Resource).Returns(new MyItem { Id = "1", Name = "Test Item" });
mockItemResponse.Setup(response => response.StatusCode).Returns(System.Net.HttpStatusCode.OK);
mockContainer.Setup(container => container.ReadItemAsync(
It.IsAny(),
It.IsAny(),
It.IsAny(), // Include ItemRequestOptions
It.IsAny()
)).ReturnsAsync(mockItemResponse.Object);
var service = new CosmosDbService(mockCosmosClient.Object);
// Act
var result = await service.GetItemAsync("1");
// Assert
Assert.NotNull(result);
Assert.Equal("1", result.Id);
Assert.Equal("Test Item", result.Name);
}
public class MyItem
{
public string Id { get; set; }
public string Name { get; set; }
}
public class CosmosDbService
{
private readonly CosmosClient _cosmosClient;
private readonly string _databaseName = "TestDatabase";
private readonly string _containerName = "TestContainer";
public CosmosDbService(CosmosClient cosmosClient)
{
_cosmosClient = cosmosClient;
}
public async Task GetItemAsync(string id)
{
try
{
var database = _cosmosClient.GetDatabase(_databaseName);
var container = database.GetContainer(_containerName);
var itemResponse = await container.ReadItemAsync(id, new PartitionKey(id)); // Provide partition key here when required.
return itemResponse.Resource;
}
catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound)
{
return null;
}
}
}
}
"""
### 2.4 Common Anti-Patterns
* **Overspecified Tests:** Writing tests that are too tightly coupled to the implementation details. This often leads to tests that break with minor code changes.
* **Ignoring Edge Cases:** Only testing happy paths and neglecting error scenarios, boundary conditions, and invalid inputs.
* **Insufficient Mocking:** Failing to properly mock dependencies, leading to slow and unreliable tests that behave more like integration tests.
* **Testing Private Methods:** Unit tests should test the public interface (API) of a class, focusing on its behavior, not its internal implementation.
## 3. Integration Testing
### 3.1 Focus and Scope
* **Why:** Integration tests verify the interactions between different components or services within the application. This helps ensure that they work together correctly.
### 3.2 Standards
* **Do This:**
* Focus on testing the interactions between components, not the individual components themselves.
* Use real dependencies or test doubles that closely mimic the behavior of real dependencies.
* Use a dedicated test environment or sandbox to avoid impacting production systems.
* Clean up any test data or resources after the test execution.
* **Don't Do This:**
* Test every possible combination of interactions between components.
* Rely on external systems that are not under your control.
* Run integration tests against production environments.
* Leave behind test data or resources that could interfere with other tests or applications.
### 3.3 Code Examples
#### 3.3.1 Integration Testing with Azure Service Bus
"""csharp
using Xunit;
using Azure.Messaging.ServiceBus;
using System;
using System.Threading.Tasks;
public class ServiceBusIntegrationTests : IAsyncLifetime
{
private const string ServiceBusConnectionString = "YOUR_SERVICE_BUS_CONNECTION_STRING"; // Replace with your connection string
private const string QueueName = "myqueue";
private ServiceBusClient _client;
private ServiceBusSender _sender;
private ServiceBusProcessor _processor;
private readonly string _messageBody = "Test Message";
public async Task InitializeAsync()
{
_client = new ServiceBusClient(ServiceBusConnectionString);
_sender = _client.CreateSender(QueueName);
//Configuring the processor. Use either SessionsProcessorOptions or ProcessorOptions, but not both
ServiceBusProcessorOptions serviceBusProcessorOptions = new ServiceBusProcessorOptions
{
ReceiveMode = ServiceBusReceiveMode.PeekLock,
AutoCompleteMessages = true,
MaxConcurrentCalls = 10,
PrefetchCount = 20,
MaxAutoLockRenewDuration = TimeSpan.FromSeconds(60),
};
_processor = _client.CreateProcessor(QueueName, serviceBusProcessorOptions);
_processor.ProcessMessageAsync += MessageHandler;
_processor.ProcessErrorAsync += ErrorHandler;
await _processor.StartProcessingAsync();
}
public async Task DisposeAsync()
{
await _processor.StopProcessingAsync();
await _processor.DisposeAsync();
await _sender.DisposeAsync();
await _client.DisposeAsync();
}
[Fact]
public async Task SendAndReceiveMessage_Success()
{
// Arrange
bool messageReceived = false;
string receivedMessageBody = null;
// Set up a handler that asserts and marks the message as received
Task MessageHandler(ProcessMessageEventArgs args)
{
receivedMessageBody = args.Message.Body.ToString();
messageReceived = true;
return Task.CompletedTask;
}
Task ErrorHandler(ProcessErrorEventArgs args)
{
Console.WriteLine(args.Exception.ToString()); // Log and inspect the error
return Task.CompletedTask;
}
ServiceBusClient receiverClient = new ServiceBusClient(ServiceBusConnectionString);
ServiceBusReceiver receiver = receiverClient.CreateReceiver(QueueName);
// Act
await _sender.SendMessageAsync(new ServiceBusMessage(_messageBody));
ServiceBusReceivedMessage receivedMessage = await receiver.ReceiveMessageAsync(TimeSpan.FromSeconds(10));
if(receivedMessage != null)
{
Assert.Equal(_messageBody, receivedMessage.Body.ToString());
}
else
{
Assert.Fail("No message was received in the allotted time.");
}
await receiver.DisposeAsync();
await receiverClient.DisposeAsync();
// Assert
// Give some time for the message to be processed
// Assert.True(messageReceived, "Message was not received.");
// Assert.Equal(_messageBody, receivedMessageBody); //verify the content of message received
}
private async Task MessageHandler(ProcessMessageEventArgs args)
{
Console.WriteLine($"Received: {args.Message.Body.ToString()}");
await args.CompleteMessageAsync(args.Message);
}
private Task ErrorHandler(ProcessErrorEventArgs args)
{
Console.WriteLine(args.Exception.ToString());
return Task.CompletedTask;
}
}
"""
#### 3.3.2 Testing Azure Function Integration with Queue Storage
"""csharp
using Xunit;
using Microsoft.Azure.WebJobs;
using Microsoft.Azure.WebJobs.Extensions.Timers;
using Microsoft.Extensions.Logging;
using Moq;
using System;
using System.Threading.Tasks;
using Microsoft.Azure.Storage.Queue;
using Microsoft.Azure.Storage;
public class QueueIntegrationTests
{
[Fact]
public async Task QueueTriggerFunction_AddsMessageToQueue()
{
// Arrange
string connectionString = "UseDevelopmentStorage=true"; // Or your real connection string
string queueName = "test-queue";
string expectedMessage = "Hello Queue!";
//Create Queue (only if it doesn't exist)
CloudStorageAccount storageAccount = CloudStorageAccount.Parse(connectionString);
CloudQueueClient queueClient = storageAccount.CreateCloudQueueClient();
CloudQueue queue = queueClient.GetQueueReference(queueName);
await queue.CreateIfNotExistsAsync();
// Set up the execution context and logger
var loggerMock = new Mock();
// Mock the ICollector to capture the added message. This does not work due to the inability to Mock Extension Types!
//Create a new JobHost configuration
var config = new JobHostConfiguration();
config.StorageConnectionString = connectionString;
// Act
// await MyQueueFunction.Run(expectedMessage, myCollector, loggerMock.Object); //Cannot mock ICollector
//Create a new JobHost using the configuration
using(var host = new JobHost(config))
{
//Call the function using the JobHost
await host.CallAsync(typeof(MyQueueFunction).GetMethod("Run"), new { myQueueItem = expectedMessage, log=loggerMock.Object});
}
// Assert
// Retrieve the message from the queue
CloudQueueMessage retrievedMessage = await queue.GetMessageAsync();
Assert.NotNull(retrievedMessage);
Assert.Equal(expectedMessage, retrievedMessage.AsString);
// Clean up the queue
await queue.DeleteMessageAsync(retrievedMessage); //Cleanup test message
}
public static class MyQueueFunction
{
[FunctionName("QueueFunction")]
public static async Task Run(
[QueueTrigger("test-queue", Connection = "AzureWebJobsStorage")] string myQueueItem,
ILogger log)
{
log.LogInformation($"C# Queue trigger function processed: {myQueueItem}");
await Task.CompletedTask; // Simulate some processing
}
}
}
"""
### 3.4 Common Anti-Patterns
* **Brittle Tests:** Making tests dependent on specific data or configurations that are likely to change. This leads to tests that frequently fail for unrelated reasons.
* **Ignoring Asynchronous Behavior:** Failing to properly handle asynchronous operations, leading to race conditions and intermittent test failures.
* **Insufficient Setup and Teardown:** Neglecting to properly set up the test environment or clean up after the test execution, leading to inconsistent results and potential data corruption.
* **Testing Too Much:** Trying to test too many interactions or components in a single integration test. This makes it difficult to isolate the cause of failures and reduces the test's effectiveness.
## 4. End-to-End (E2E) Testing
### 4.1 Focus and Scope
* **Why:** E2E tests simulate real user scenarios and verify that the entire application works correctly from start to finish. This helps ensure that all components and services are properly integrated and that the application meets the user's needs.
### 4.2 Standards
* **Do This:**
* Focus on testing critical user flows and business processes.
* Use automation frameworks and tools to simulate user interactions.
* Use a dedicated test environment or staging environment that closely resembles production.
* Monitor application logs and metrics to identify performance issues or errors.
* **Don't Do This:**
* Test every possible user interaction or scenario.
* Rely on manual testing for critical functionality.
* Run E2E tests against production environments.
* Ignore performance issues or errors that are identified during testing.
### 4.3 Code Examples
#### 4.3.1 E2E Testing with Playwright (Simulating User Interactions)
"""csharp
using Microsoft.Playwright;
using Xunit;
using System.Threading.Tasks;
public class PlaywrightE2ETests : IAsyncLifetime
{
private IPlaywright _playwright;
private IBrowser _browser;
public IBrowserContext _context;
public IPage _page;
public async Task InitializeAsync()
{
// Install Playwright if not already installed: pwsh bin/Debug/net8.0/playwright.ps1 install
_playwright = await Playwright.CreateAsync();
_browser = await _playwright.Chromium.LaunchAsync(new BrowserTypeLaunchOptions
{
Headless = false // Set to true for running in CI/CD
});
_context = await _browser.NewContextAsync();
_page = await _context.NewPageAsync();
}
public async Task DisposeAsync()
{
await _page.CloseAsync();
await _context.CloseAsync();
await _browser.CloseAsync();
_playwright.Dispose();
}
[Fact]
public async Task NavigateToHomePage_VerifyTitle()
{
await _page.GotoAsync("https://www.example.com");
string title = await _page.TitleAsync();
Assert.Equal("Example Domain", title);
// Example of taking a screenshot
// await _page.ScreenshotAsync(new PageScreenshotOptions { Path = "screenshot.png" });
}
[Fact]
public async Task NavigateToHomePage_VerifyH1()
{
await _page.GotoAsync("https://www.example.com");
string h1Text = await _page.Locator("h1").InnerTextAsync();
Assert.Equal("Example Domain", h1Text);
// Example of taking a screenshot
// await _page.ScreenshotAsync(new PageScreenshotOptions { Path = "screenshot.png" });
}
}
"""
#### 4.3.2 E2E Testing with Azure DevOps Pipelines
(Configuration in Azure DevOps YAML)
"""yaml
trigger:
- main
pool:
vmImage: 'windows-latest'
steps:
- task: DotNetCoreCLI@2
displayName: 'Build'
inputs:
command: 'build'
projects: '**/*.csproj'
arguments: '--configuration Release'
- task: DotNetCoreCLI@2
displayName: 'Test'
inputs:
command: 'test'
projects: '**/*Tests.csproj'
arguments: '--configuration Release --collect:"XPlat Code Coverage" --logger:"trx;LogFileName=test-results.trx"' # Collect code coverage
- task: PublishTestResults@2 # Publish Test Results
inputs:
testResultsFormat: 'VSTest'
testResultsFiles: '**/test-results.trx'
failTaskOnFailedTests: true
#Optionally Publish code coverage results
- task: PublishCodeCoverageResults@2
inputs:
codeCoverageTool: 'Cobertura'
summaryFileLocation: '$(Agent.TempDirectory)/**/coverage.cobertura.xml'
reportDirectory: '$(Agent.TempDirectory)/**/coveragereport'
condition: succeeded() # Execute this task only if the previous tasks succeeded
# Optionally, if needing to run Playwright:
#- script: pwsh bin/Debug/net8.0/playwright.ps1 install --browser chromium
# displayName: 'Install Playwright Browsers'
#- task: DotNetCoreCLI@2 #E2E (Playwright tests e.g.)
# displayName: 'Run E2E Tests'
# inputs:
# command: 'test'
# projects: '**/E2ETests.csproj' # Update the project path
# arguments: '--configuration Release'
"""
### 4.4 Common Anti-Patterns
* **Unreliable Tests:** Creating tests that are prone to failure due to external factors, such as network issues or service outages.
* **Slow Test Execution:** Designing tests that take a long time to execute, slowing down the development process and reducing the frequency of testing.
* **Lack of Observability:** Failing to properly monitor the application during testing, making it difficult to diagnose the cause of failures or performance issues.
* **Ignoring Accessibility:** Neglecting to test the application's accessibility features, potentially excluding users with disabilities.
## 5. Performance Testing/Load Testing
### 5.1 Focus and Scope
* **Why:** Performance testing aims to identify potential bottlenecks and ensure the application can handle the expected load under various conditions.
### 5.2 Standards
* **Do This:**
* Define clear performance goals and metrics (e.g., response time, throughput, resource utilization).
* Simulate realistic user scenarios and workloads.
* Use dedicated performance testing tools such as JMeter, LoadView, or Azure Load Testing.
* Monitor resource utilization (CPU, memory, network) on Azure resources.
* **Don't Do This:**
* Run performance tests in production environments.
* Ignore performance degradation or bottlenecks identified during testing.
* Fail to baseline and track performance over time.
### 5.3 Code Example: Azure Load Testing
Azure Load Testing (ALT) is a fully managed load-testing service.
1. **Create an Azure Load Testing Resource**: Provision an instance through the Azure portal.
2. **Create a Test**: Upload a JMeter script or define a simple URL-based test.
3. **Configure**: Specify test parameters (e.g., number of virtual users, duration)
4. **Run Test**: Execute and monitor real-time metrics.
5. **Analyze Results**: Review performance insights and identify bottlenecks.
"""Azure CLI
#Create an Azure load testing resource
az load create --name --location --resource-group --description "Testing some APIs."
#Upload and run the jmx
az load test create --test-id --resource-group --load-testing-resource --display-name --description --test-plan
"""
### 5.4 Common Anti-Patterns
- **Insufficient Load**: Using too few virtual users, missing peak load moments resulting in unrealistic insights.
- **Ignoring External Dependencies**: Neglecting the impact of external services that can impact results.
- **Testing Too Late**: Post-deployment efforts may be too late and expensive to address issues, hence performance awareness must be inculcated earlier in stages.
## 6. Security Testing
### 6.1 Focus and Scope
* **Why:** Security testing aims to identify vulnerabilities in the application that could be exploited by attackers.
### 6.2 Standards
* **Do This:**
* Perform regular vulnerability scans and penetration testing.
* Follow security best practices, such as the OWASP Top Ten.
* Use static analysis tools to identify potential security flaws in the code.
* Implement security measures at all levels of the application, including authentication, authorization, and data encryption.
* **Don't Do This:**
* Ignore security vulnerabilities or potential risks.
* Rely solely on perimeter security measures.
* Store sensitive data in plain text.
* Use weak or default passwords.
### 6.3 Code Example: Static Code Analysis with SonarCloud
1. **Setup SonarCloud Integration**: Connect your Azure DevOps project to SonarCloud.
2. **Add SonarCloud Task**: Include the SonarCloud Analyze task in your Azure DevOps pipeline.
3. **Configure Quality Gate**: Define quality criteria such as vulnerability rates, bug counts and coverage %.
"""yaml
# Add SonarCloud prepare analysis configuration task
- task: SonarCloudPrepare@1
inputs:
SonarCloud: 'YourSonarCloudServiceConnection'
organization: 'your-sonarcloud-organization'
scannerMode: 'MSBuild'
projectKey: 'your-project-key'
projectName: 'Your Project Name'
# Add MSBuild task to build the project
- task: MSBuild@1
inputs:
solution: '**\*.sln'
msbuildArguments: '/t:Rebuild'
# Add SonarCloud analysis task
- task: SonarCloudAnalyze@1
# Add SonarCloud publish quality gate result task
- task: SonarCloudPublish@1
inputs:
pollingTimeoutSec: '300'
"""
### 6.4 Common Anti-Patterns
- **Lack of Regular Assessments**: Infrequent security testing and assessments can leave systems vulnerable for long periods.
- **Ignoring Third-Party Components**: Failing to assess the security of libraries, dependencies, and other external components.
- **Poor Secrets Management**: Embedding sensitive keys, tokens, and passwords directly into code or configuration files.
By adhering to these testing methodology standards, Azure developers can ensure that their applications are reliable, secure, and performant. This document provides a foundation for building high-quality Azure applications.
danielsogl
Created Mar 6, 2025
This guide explains how to effectively use .clinerules
with Cline, the AI-powered coding assistant.
The .clinerules
file is a powerful configuration file that helps Cline understand your project's requirements, coding standards, and constraints. When placed in your project's root directory, it automatically guides Cline's behavior and ensures consistency across your codebase.
Place the .clinerules
file in your project's root directory. Cline automatically detects and follows these rules for all files within the project.
# Project Overview project: name: 'Your Project Name' description: 'Brief project description' stack: - technology: 'Framework/Language' version: 'X.Y.Z' - technology: 'Database' version: 'X.Y.Z'
# Code Standards standards: style: - 'Use consistent indentation (2 spaces)' - 'Follow language-specific naming conventions' documentation: - 'Include JSDoc comments for all functions' - 'Maintain up-to-date README files' testing: - 'Write unit tests for all new features' - 'Maintain minimum 80% code coverage'
# Security Guidelines security: authentication: - 'Implement proper token validation' - 'Use environment variables for secrets' dataProtection: - 'Sanitize all user inputs' - 'Implement proper error handling'
Be Specific
Maintain Organization
Regular Updates
# Common Patterns Example patterns: components: - pattern: 'Use functional components by default' - pattern: 'Implement error boundaries for component trees' stateManagement: - pattern: 'Use React Query for server state' - pattern: 'Implement proper loading states'
Commit the Rules
.clinerules
in version controlTeam Collaboration
Rules Not Being Applied
Conflicting Rules
Performance Considerations
# Basic .clinerules Example project: name: 'Web Application' type: 'Next.js Frontend' standards: - 'Use TypeScript for all new code' - 'Follow React best practices' - 'Implement proper error handling' testing: unit: - 'Jest for unit tests' - 'React Testing Library for components' e2e: - 'Cypress for end-to-end testing' documentation: required: - 'README.md in each major directory' - 'JSDoc comments for public APIs' - 'Changelog updates for all changes'
# Advanced .clinerules Example project: name: 'Enterprise Application' compliance: - 'GDPR requirements' - 'WCAG 2.1 AA accessibility' architecture: patterns: - 'Clean Architecture principles' - 'Domain-Driven Design concepts' security: requirements: - 'OAuth 2.0 authentication' - 'Rate limiting on all APIs' - 'Input validation with Zod'
# Component Design Standards for Azure This document outlines component design standards for developing applications on Azure. It focuses on creating reusable, maintainable, and performant components within the Azure ecosystem. These standards will help improve code quality, reduce development time, and ensure long-term application health. ## 1. General Principles ### 1.1. Reusability * **Do This:** Design components to be reusable across different parts of the application or even across different applications. * **Don't Do This:** Create monolithic, tightly-coupled components that are difficult to reuse or modify. **Why:** Reusable components reduce code duplication, making applications easier to maintain and update. They also promote consistency across different parts of the system. **Example (Azure Functions):** """csharp // Reusable function to log events to Azure Table Storage public static class LoggingHelper { public static async Task LogEvent(string partitionKey, string rowKey, string message, ILogger log) { string storageAccountName = "yourstorageaccountname"; string storageAccountKey = "yourstorageaccountkey"; string tableName = "Logs"; string storageConnectionString = $"DefaultEndpointsProtocol=https;AccountName={storageAccountName};AccountKey={storageAccountKey};EndpointSuffix=core.windows.net"; CloudStorageAccount storageAccount = CloudStorageAccount.Parse(storageConnectionString); CloudTableClient tableClient = storageAccount.CreateCloudTableClient(); CloudTable table = tableClient.GetTableReference(tableName); await table.CreateIfNotExistsAsync(); var logEntity = new DynamicTableEntity(partitionKey, rowKey); logEntity.Properties.Add("Message", new EntityProperty(message)); TableOperation insertOperation = TableOperation.Insert(logEntity); try { await table.ExecuteAsync(insertOperation); log.LogInformation($"Logged event to Table Storage: PartitionKey={partitionKey}, RowKey={rowKey}"); } catch (StorageException ex) { log.LogError($"Error logging event: {ex.Message}"); } } } // Usage in an Azure Function public static class MyFunction { [FunctionName("MyFunction")] public static async Task Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log) { log.LogInformation("Function executed."); await LoggingHelper.LogEvent("FunctionLogs", Guid.NewGuid().ToString(), "MyFunction ran successfully.", log); } } """ ### 1.2. Single Responsibility Principle (SRP) * **Do This:** Ensure each component has a single, well-defined responsibility. * **Don't Do This:** Create components that perform multiple unrelated tasks. **Why:** SRP makes components easier to understand, test, and modify. Changes to one responsibility don't affect unrelated parts of the component. **Example (Azure Logic Apps):** Instead of having one Logic App perform multiple complex tasks, break it down into smaller, more focused Logic Apps. One Logic App might be responsible for receiving a message from a queue, another for processing the message, and a third for sending the result to storage. ### 1.3. Abstraction and Encapsulation * **Do This:** Use interfaces and abstract classes to define contracts for component behavior. Hide implementation details behind well-defined interfaces. * **Don't Do This:** Directly expose internal implementation details of a component. **Why:** Abstraction reduces dependencies between components and allows for easier swapping of implementations without affecting other parts of the system. Encapsulation protects the internal state of a component and prevents unintended modifications. **Example (Azure Cosmos DB data access):** """csharp // Interface for Cosmos DB data access public interface ICosmosDbService { Task<T> GetItemAsync(string id); Task<IEnumerable<T>> GetItemsAsync(string query); Task AddItemAsync(T item); Task UpdateItemAsync(string id, T item); Task DeleteItemAsync(string id); } // Implementation of the interface (hides Cosmos DB specific details) public class CosmosDbService : ICosmosDbService { private readonly CosmosClient _cosmosClient; private readonly string _databaseName; private readonly string _containerName; public CosmosDbService(CosmosClient cosmosClient, string databaseName, string containerName) { _cosmosClient = cosmosClient ?? throw new ArgumentNullException(nameof(cosmosClient)); _databaseName = databaseName ?? throw new ArgumentNullException(nameof(databaseName)); _containerName = containerName ?? throw new ArgumentNullException(nameof(containerName)); } public async Task<T> GetItemAsync(string id) { //Implementation details using _cosmosClient // using the new .NET 8 SDK is highly recommended for performance + features try { var container = _cosmosClient.GetContainer(_databaseName, _containerName); ItemResponse<T> response = await container.ReadItemAsync<T>(id, new PartitionKey(id)); // Partition Key MUST be provided return response.Resource; } catch (CosmosException ex) when (ex.StatusCode == System.Net.HttpStatusCode.NotFound) { return default; } } public async Task<IEnumerable<T>> GetItemsAsync(string query) { //Implementation details using _cosmosClient var container = _cosmosClient.GetContainer(_databaseName, _containerName); var queryDefinition = new QueryDefinition(query); FeedIterator<T> queryResultSetIterator = container.GetItemQueryIterator<T>(queryDefinition); List<T> results = new List<T>(); while (queryResultSetIterator.HasMoreResults) { FeedResponse<T> currentResultSet = await queryResultSetIterator.ReadNextAsync(); foreach (T item in currentResultSet) { results.Add(item); } } return results; } public async Task AddItemAsync(T item) { var container = _cosmosClient.GetContainer(_databaseName, _containerName); await container.CreateItemAsync(item, new PartitionKey(item.Id)); } public async Task UpdateItemAsync(string id, T item) { var container = _cosmosClient.GetContainer(_databaseName, _containerName); await container.ReplaceItemAsync(item, id, new PartitionKey(id)); } public async Task DeleteItemAsync(string id) { var container = _cosmosClient.GetContainer(_databaseName, _containerName); await container.DeleteItemAsync<T>(id, new PartitionKey(id)); } } """ ### 1.4. Loose Coupling * **Do This:** Minimize dependencies between components. Use dependency injection, event-driven architectures, and message queues to decouple components. * **Don't Do This:** Create tightly coupled components that directly depend on each other's internal implementations. **Why:** Loose coupling makes the system more flexible, easier to change, and easier to test. Changes to one component are less likely to have unintended consequences on other components. **Example (Azure Service Bus):** Use Service Bus queues or topics to send messages between components without requiring direct knowledge of each other. """csharp // Sending a message to a Service Bus queue public static async Task SendMessageToQueue(string queueName, string messageBody, ILogger log) { string connectionString = "your_service_bus_connection_string"; //Store in Key Vault ServiceBusClient client = new ServiceBusClient(connectionString); ServiceBusSender sender = client.CreateSender(queueName); try { ServiceBusMessage message = new ServiceBusMessage(messageBody); await sender.SendMessageAsync(message); log.LogInformation($"Sent message to queue: {queueName}"); } catch (Exception ex) { log.LogError($"Error sending message to queue: {ex.Message}"); } finally { await sender.CloseAsync(); await client.DisposeAsync(); } } // Receiving a message from a Service Bus queue public static async Task ReceiveMessageFromQueue(string queueName, ILogger log) { string connectionString = "your_service_bus_connection_string"; //Store in Key Vault ServiceBusClient client = new ServiceBusClient(connectionString); ServiceBusProcessor processor = client.CreateProcessor(queueName, new ServiceBusProcessorOptions()); processor.ProcessMessageAsync += async (ProcessMessageEventArgs args) => { string body = args.Message.Body.ToString(); log.LogInformation($"Received message: {body}"); await args.CompleteMessageAsync(args.Message); // Mark message as processed }; processor.ProcessErrorAsync += (ProcessErrorEventArgs args) => { log.LogError($"Error receiving message: {args.Exception.Message}"); return Task.CompletedTask; }; await processor.StartProcessingAsync(); Console.WriteLine("Press any key to stop processing..."); Console.ReadKey(); await processor.StopProcessingAsync(); } """ ### 1.5. Maintainability * **Do This:** Write clean, well-documented code. Use consistent naming conventions and formatting. * **Don't Do This:** Write complex, undocumented code that is difficult to understand and modify. **Why:** Maintainable code is easier to update, debug, and extend. This reduces the cost of ownership over time. ### 1.6. Performance * **Do This:** Design components with performance in mind. Use efficient algorithms and data structures. Optimize for the specific requirements of the Azure platform. * **Don't Do This:** Ignore performance considerations during the design phase. **Why:** Performance is critical for cloud applications. Poorly designed components can lead to slow response times and higher costs. Consider using tools like Azure Monitor for performance profiling. Caching is also critical to prevent unnecessary repeated operations to backend services. ### 1.7. Security * **Do This:** Design components with security in mind. Use secure coding practices to prevent vulnerabilities such as SQL injection, cross-site scripting (XSS), and cross-site request forgery (CSRF). Protect sensitive data using encryption and access controls. Leverage Azure Key Vault to store secrets. * **Don't Do This:** Ignore security considerations during the design phase. **Why:** Security is paramount for cloud applications. Vulnerabilities can lead to data breaches and other security incidents. Consider using tools like Azure Security Center to identify and mitigate security risks. ## 2. Component Types in Azure ### 2.1. Azure Functions * **Do This:** Use Azure Functions for small, independent units of work that can be triggered by various events. * **Don't Do This:** Use Azure Functions for long-running processes or complex workflows. Use durable functions instead for stateful workflows . **Specific Standards:** * Keep functions short and focused. * Use dependency injection to manage dependencies. * Handle exceptions gracefully. * Use asynchronous programming to avoid blocking threads. * Consider using bindings to simplify integration with Azure services. **Example:** A simple Azure Function triggered by a queue message: """csharp using Microsoft.Azure.WebJobs; using Microsoft.Extensions.Logging; namespace FunctionApp1; public class QueueTriggerCSharp { [FunctionName("QueueTriggerCSharpEx")] public void Run( [QueueTrigger("myqueue-items", Connection = "AzureWebJobsStorage")] string myQueueItem, ILogger log) { log.LogInformation($"C# Queue trigger function processed: {myQueueItem}"); } } """ ### 2.2. Azure Logic Apps * **Do This:** Use Azure Logic Apps to orchestrate complex workflows and integrate different systems. * **Don't Do This:** Use Azure Logic Apps for computationally intensive tasks. **Specific Standards:** * Break down complex workflows into smaller, more manageable Logic Apps. * Use connectors to integrate with different services. * Use error handling to handle failures gracefully. * Use parameters and variables to make Logic Apps more configurable. **Example:** A Logic App that receives a message from a queue, processes it, and sends an email. Use the Azure portal designer for visually creating and managing Logic Apps. ### 2.3. Azure Web Apps * **Do This:** Use Azure Web Apps for hosting web applications and APIs. * **Don't Do This:** Use Azure Web Apps for long-running background processes. Consider Azure Functions or Azure Container Apps for those scenarios. **Specific Standards:** * Use a well-defined architecture, such as Model-View-Controller (MVC) or Representational State Transfer (REST). * Use dependency injection to manage dependencies. * Use logging and monitoring to track application health. * Secure web apps using authentication and authorization. * Optimize web apps for performance using caching and compression, and CDNs. **Example (ASP.NET Core Web API):** A simple REST API endpoint that handles a GET request. """csharp using Microsoft.AspNetCore.Mvc; namespace MyWebApp.Controllers { [ApiController] [Route("[controller]")] public class MyController : ControllerBase { [HttpGet] public ActionResult<string> Get() { return "Hello from my Web API!"; } } } """ ### 2.4. Azure Container Apps * **Do This:** Use Azure Container Apps to deploy and manage containerized applications and microservices. Especially useful for event-driven applications, or processing background tasks. * **Don't Do This:** Use Azure Container Apps for simple web applications, stick with Web Apps for simplicity in this case. **Specific standards** * Use a robust CI/CD pipeline with your preferreed container registry to deploy changes safely and reliably across new versions * Implement health probes (liveness/readiness) to enable ACA to automatically restart unhealthy containers * Consider autoscaling based on CPU, memory, or custom metrics. **Example (Azure Container App with scaling)** """yaml name: my-container-app properties: managedEnvironmentId: /subscriptions/<subscription_id>/resourceGroups/<resource_group>/providers/Microsoft.App/managedEnvironments/<env_name> configuration: ingress: external: true targetPort: 80 secrets: - name: mysecret value: <secret_value> registries: - server: docker.io username: <docker_username> passwordSecretRef: mysecret activeRevisionsMode: Single dapr: enabled: false appId: my-container-app template: containers: - image: docker.io/<docker_username>/my-image:latest name: my-container resources: cpu: 0.5 memory: 1Gi ports: - port: 80 protocol: TCP env: - name: MY_ENV_VAR value: "my_env_value" scale: minReplicas: 1 maxReplicas: 10 rules: - name: http-request-rule http: metadata: concurrentRequests: '50' auth: secretRef: mysecret_apikey revisionSuffix: latest """ ### 2.5. Azure Data Components (Cosmos DB, SQL Database, etc.) * **Do This:** Choose the right data store for the specific requirements of the application. * **Don't Do This:** Use a single data store for all types of data without considering performance, scalability, and cost. **Specific Standards:** * Use connection pooling to improve performance. * Use parameterized queries to prevent SQL injection. * Use indexing to optimize query performance. * Use appropriate data types to minimize storage costs. * Ensure that components interacting with Azure Data components properly handle retries. **Example (Azure SQL Database connection pooling):** Using Entity Framework Core with connection pooling enabled by default. Handle transient errors using Polly or similar retry libraries. """csharp public class MyDbContext : DbContext { public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { } public DbSet<MyEntity> MyEntities { get; set; } } // In Startup.cs or Program.cs services.AddDbContext<MyDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("MyDbConnection"))); """ ## 3. Design Patterns for Azure ### 3.1. Gateway Pattern * **Description:** A gateway provides a single point of entry for all requests to a system or microservice. * **Azure Implementation:** Use Azure API Management (APIM) as a gateway to manage and secure APIs, apply policies, and monitor usage or Azure Front Door as a global ingress point. ### 3.2. Circuit Breaker Pattern * **Description:** Prevents an application from repeatedly trying to access a failing service. * **Azure Implementation:** Implement the Circuit Breaker pattern using libraries like Polly in C# and integrate with monitoring tools like Application Insights to track circuit breaker state. ### 3.3. Retry Pattern * **Description:** Automatically retries failed operations to handle transient errors. * **Azure Implementation:** Use libraries like Polly or the built-in retry policies in Azure SDKs to automatically retry failed operations. ### 3.4. Queue-Based Load Leveling * **Description:** Uses a queue to buffer requests and smooth out load spikes. * **Azure Implementation:** Use Azure Service Bus queues or Azure Storage queues to buffer requests between components. ### 3.5. Event-Driven Architecture * **Description:** Components communicate through asynchronous events. * **Azure Implementation:** Use Azure Event Grid or Azure Event Hubs to build event-driven architectures. ## 4. Naming Conventions * **Do This:** Establish and consistently use naming conventions for all Azure resources and components. * **Don't Do This:** Use inconsistent or unclear names that make it difficult to understand the purpose of a resource or component. **Examples:** * **Azure Functions:** "FunctionApp-ResourceGroup-Environment-Functionality" (e.g., "FunctionApp-MyRG-Dev-ProcessOrders") * **Storage Accounts:** "StorageAccount-ResourceGroup-Environment-Functionality" (e.g., "StorageAccount-MyRG-Prod-OrderData") * **Logic Apps:** "LogicApp-ResourceGroup-Environment-Functionality" (e.g., "LogicApp-MyRG-Test-OrderProcessing") * **Databases:** "Database-ResourceGroup-Environment-Name" (e.g., "Database-MyRG-Prod-OrdersDB") ## 5. Error Handling * **Do This:** Implement comprehensive error handling in all components. * **Don't Do This:** Ignore errors or allow exceptions to propagate without handling them. **Specific Standards:** * Use try-catch blocks to handle exceptions. * Log errors to Azure Monitor or other logging services. * Implement retry logic for transient errors. * Provide meaningful error messages to users. * Use dead-letter queues for messages that cannot be processed. ## 6. Documentation * **Do This:** Document all components and their interfaces. * **Don't Do This:** Neglect documentation, making it difficult for others to understand and use your components. **Specific Standards:** * Use inline comments to explain complex code. * Create API documentation using tools like Swagger/OpenAPI. * Document the purpose, inputs, and outputs of each component. * Document any dependencies on other components or services. ## 7. Testing * **Do This:** Write unit tests, integration tests, and end-to-end tests for all components. * **Don't Do This:** Deploy components without adequate testing. **Specific Standards:** * Use mocking frameworks to isolate components during unit testing. * Use integration tests to verify that components work together correctly. * Use end-to-end tests to verify that the entire application works as expected. * Automate testing using CI/CD pipelines. ## 8. Monitoring and Logging * **Do This:** Implement comprehensive monitoring and logging for all components. * **Don't Do This:** Deploy components without adequate monitoring and logging. **Specific Standards:** * Use Azure Monitor to collect metrics, logs, and traces. * Use Application Insights to monitor application performance and availability. * Use structured logging to make it easier to analyze logs. * Set up alerts to notify you of critical errors or performance issues. **Example (Logging to Application Insights):** """csharp using Microsoft.Extensions.Logging; public class MyComponent { private readonly ILogger<MyComponent> _logger; public MyComponent(ILogger<MyComponent> logger) { _logger = logger; } public void DoSomething() { _logger.LogInformation("Doing something..."); try { // ... some code that might throw an exception } catch (Exception ex) { _logger.LogError(ex, "An error occurred."); } } } """ By adhering to these component design standards, development teams can build robust, scalable, and maintainable applications on Azure. This document should be treated as a living document, and it should be updated regularly to reflect changes in the Azure platform and best practices.
# Deployment and DevOps Standards for Azure This document outlines coding standards for Deployment and DevOps practices within the Azure environment. It provides guidelines, best practices, and actionable advice to ensure efficient, reliable, and secure deployments. These standards are designed to be used by developers and AI coding assistants to promote consistency and quality across Azure projects. ## 1. Infrastructure as Code (IaC) ### 1.1. Standard Use Infrastructure as Code (IaC) for all Azure infrastructure provisioning and configuration. Emphasize declarative IaC languages. * **Why:** IaC enables automation, version control, repeatability, and reduces the risk of manual errors. Declarative IaC tools such as ARM Templates or Bicep help define the desired state so the engine can reconcile that state with the current actual state. * **Do This:** Use Bicep for Azure resource deployments due to its simplified syntax, modularity, and first-class Azure support. * **Don't Do This:** Prefer not to manually create resources in the Azure portal beyond initial project setups or proof-of-concept exercises. Avoid imperative scripting without IaC. ### 1.2. Bicep Best Practices * **Why:** Bicep provides a clean and simplified way to define Azure resources compared to ARM templates. * **Do This:** * Organize Bicep files into logical modules. A module should have a single purpose, like creating a virtual network or a storage account. * Use parameters and variables extensively for configuration. * Implement outputs to expose important resource properties (e.g., resource ID, name). * Leverage Bicep's type safety and validation features. * Store Bicep files in source control with versioning. * Use Bicep format command for standardization. * Use Bicepparams file for a consistent way to pass parameters to Bicep files. * **Don't Do This:** * Avoid hardcoding values directly in Bicep files. * Do not create monolithic Bicep template files that are difficult to understand and maintain. * Do not ignore Bicep's linting and validation warnings. * **Example:** """bicep // main.bicep param location string = resourceGroup().location param storageAccountName string = 'storage${uniqueString(resourceGroup().id)}' module storageAccount 'modules/storageAccount.bicep' = { name: 'storageAccountModule' params: { location: location storageAccountName: storageAccountName } } output storageAccountId string = storageAccount.outputs.storageAccountId """ """bicep // modules/storageAccount.bicep param location string param storageAccountName string resource storageAccount 'Microsoft.Storage/storageAccounts@2023-01-01' = { name: storageAccountName location: location sku: { name: 'Standard_LRS' } kind: 'StorageV2' } output storageAccountId string = storageAccount.id """ """bicep // azuredeploy.parameters.json { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentParameters.json#", "contentVersion": "1.0.0.0", "parameters": { "location": { "value": "West US 2" } } } """ """azurecli az deployment group create --resource-group exampleRG --template-file main.bicep --parameters azuredeploy.parameters.json """ ### 1.3. ARM Template Considerations While Bicep is the preferred choice, ARM Templates might still be present in existing projects. If you must use ARM Templates, adhere to these guidelines: * **Do This:** * Use linked templates to break down complex deployments into smaller, manageable units. * Parameterize ARM templates to make them reusable across different environments. * Validate ARM templates before deployment using "Test-AzResourceGroupDeployment". * **Don't Do This:** * Avoid excessively large ARM templates that are hard to debug. * Do not embed sensitive information, such as passwords or API keys, directly in ARM templates. Use Key Vault or other secure services instead. ### 1.4. Terraform * Terraform is another option for infrastructure as code. It is a third party IaC tool and can be valuable if your requirements dictate that you use Terraform. * **Do This:** * Utilize modules to encapsulate infrastructure components. * Use Terraform state files remotely (e.g., Azure Storage) for collaboration and versioning. * Follow secure coding practices like least privilege and input validation. * **Don't Do This:** * Avoid storing Terraform state files locally, as it introduces risk and limits collaboration. * Do not hardcode credentials, store secrets securely using tools like Azure Key Vault. ## 2. Continuous Integration and Continuous Delivery (CI/CD) ### 2.1. Standard Implement a CI/CD pipeline for automated build, test, and deployment processes. * **Why:** CI/CD automates the software delivery process, increasing velocity, reducing errors, and enabling faster feedback loops. * **Do This:** Choose Azure DevOps or GitHub Actions for CI/CD pipelines due to their tight integration with Azure services. * **Don't Do This:** Avoid manual deployments to production environments. ### 2.2. Azure DevOps Pipelines * **Why:** Azure DevOps provides a comprehensive suite of services for CI/CD, including build pipelines, release pipelines, and test integration. * **Do This:** * Define build and release pipelines using YAML for version control. * Implement automated testing (unit, integration, end-to-end) in the build pipeline. * Use release gates to enforce quality checks and approvals before deployment to different environments. * Implement Infrastructure as Code deployments as part of your release pipelines. * **Don't Do This:** * Avoid creating separate CI/CD pipelines for each environment. Use parameterized pipelines with variables and conditions. * Do not deploy code without proper testing. * **Example:** """yaml # azure-pipelines.yml trigger: - main pool: vmImage: ubuntu-latest variables: buildConfiguration: 'Release' steps: - task: DotNetCoreCLI@2 displayName: 'Build' inputs: command: 'build' arguments: '--configuration $(buildConfiguration)' projects: '**/*.csproj' - task: DotNetCoreCLI@2 displayName: 'Test' inputs: command: 'test' projects: '**/*Tests.csproj' - task: AzureWebApp@1 displayName: 'Deploy to Azure Web App' inputs: azureSubscription: 'YourAzureSubscription' appName: 'YourWebAppName' package: '$(System.DefaultWorkingDirectory)/**/*.zip' """ ### 2.3. GitHub Actions * **Why:** GitHub Actions offer a similar feature set as Azure DevOps, directly integrated into GitHub repositories. They are well-suited for open-source projects or organizations deeply invested in the GitHub ecosystem. * **Do This:** * Define workflows using YAML files in the ".github/workflows" directory. * Use GitHub Secrets to securely store sensitive information. * Leverage the Azure CLI Action or Azure Resource Manager (ARM) Deploy Action to manage Azure resources. * Use environment-specific branches to manage deployments for different stage tiers. * **Don't Do This:** * Avoid committing sensitive information directly to GitHub repositories. * Do not neglect security hardening of GitHub Actions workflows. * **Example:** """yaml # .github/workflows/deploy.yml name: Deploy to Azure on: push: branches: - main env: AZURE_WEBAPP_NAME: your-app-name AZURE_RG: your-resource-group AZURE_SUBSCRIPTION: your-azure-subscription jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 - uses: azure/login@v1 with: creds: ${{ secrets.AZURE_CREDENTIALS }} - name: Set up .NET Core uses: actions/setup-dotnet@v3 with: dotnet-version: '6.0.x' - name: Build with dotnet run: dotnet build --configuration Release - name: Publish to Azure Web App uses: azure/webapps-deploy@v2 with: app-name: ${{ env.AZURE_WEBAPP_NAME }} package: . """ ### 2.4. Deployment Strategies Select appropriate deployment strategies based on application requirements. * **Do This:** * Use Blue/Green deployments for zero-downtime updates. * Implement Canary releases to test new features with a subset of users. * Consider Rolling deployments for applications where gradual updates are acceptable. * **Don't Do This:** * Avoid big-bang deployments for critical applications. * Do not deploy code directly to production without proper testing and validation. ## 3. Monitoring and Logging ### 3.1. Standard Implement comprehensive monitoring and logging for all Azure resources and applications * **Why:** Monitoring and logging provide insight into application performance, identify issues, and facilitate troubleshooting. * **Do This:** * Use Azure Monitor for collecting metrics, logs, and activity logs. * Implement Application Insights for deep application performance monitoring (APM). * Centralize logs using Azure Log Analytics workspace. * Utilize alerts and dashboards to proactively identify and respond to issues. * Use structured logging. * **Don't Do This:** * Do not rely solely on manual log analysis. * Avoid logging sensitive information directly to logs. ### 3.2. Azure Monitor * **Why:** Azure Monitor provides a unified platform for monitoring Azure resources and applications. * **Do This:** * Configure diagnostic settings to collect metrics and logs from Azure services. * Use Kusto Query Language (KQL) to analyze log data in Log Analytics. * Create alerts based on metrics and log search queries. * Build dashboards to visualize key performance indicators (KPIs). * **Don't Do This:** * Do not disable monitoring for production resources. * Avoid creating too many alerts, which can lead to alert fatigue. * **Example:** (KQL Query) """kusto // Query to find requests that took longer than 5 seconds requests | where timestamp > ago(1d) | where duration > 5 | project timestamp, name, url, duration | order by duration desc """ ### 3.3. Application Insights * **Why:** Application Insights provides detailed performance and usage information for web applications. * **Do This:** * Instrument applications with the Application Insights SDK. * Use custom events and metrics to track specific business logic. * Configure exception tracking and dependency monitoring. * Analyze performance bottlenecks using the Performance blade. * **Don't Do This:** * Avoid sending excessive or irrelevant data to Application Insights. * Do not use Application Insights to store sensitive or personal information. * Disable Live Metrics Stream in production - only enable it temporarily for short-term monitoring. ### 3.4 Container Monitoring Ensure containerized applications utilize standard output for logging. Configure Azure Monitor Container Insights to collect logs and metrics from Kubernetes clusters. * **Do This:** * Write logs to stdout/stderr for streamlined collection by container monitoring solutions. * Implement liveness and readiness probes to ensure containers restart automatically on failure. * Monitor resource utilization (CPU, memory) of containers. * **Don't Do This:** * Avoid writing logs to files within the container's filesystem unless absolutely necessary. * Do not neglect to monitor the health and performance of Kubernetes nodes and pods. ## 4. Security ### 4.1. Standard Implement security best practices throughout the deployment and DevOps process. * **Why:** Security should be a top priority in every stage of the software development lifecycle to protect against vulnerabilities and threats. * **Do This:** * Use Azure Key Vault to store secrets, certificates, and connection strings. * Implement Role-Based Access Control (RBAC) to grant least privilege access. * Harden CI/CD pipelines to prevent unauthorized modifications. * Regularly scan code and infrastructure for vulnerabilities using tools like Azure Security Center/Defender for Cloud. * Enable Just-In-Time (JIT) access for administrative tasks. * Utilize Managed Identities wherever possible. * **Don't Do This:** * Avoid storing secrets directly in code or configuration files. * Do not grant excessive permissions to users or service principals. * Avoid using the default "admin" users for routine tasks. ### 4.2. Azure Key Vault * **Why:** Azure Key Vault provides a secure way to store and manage secrets, keys, and certificates. * **Do This:** * Store all sensitive information in Key Vault. * Grant applications access to secrets using Managed Identities or Service Principals. * Enable auditing to track access to secrets. * Implement secret rotation policies. * **Don't Do This:** * Do not hardcode secrets in code or configuration files. * Avoid granting broad access to Key Vault. * **Example:** (Accessing secrets in Azure Functions) """csharp using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; using Microsoft.Extensions.Configuration; // Add this public static class MyFunction { [FunctionName("MyFunction")] public static IActionResult Run( [HttpTrigger(AuthorizationLevel.Function, "get", "post", Route = null)] HttpRequest req, ILogger log, ExecutionContext context) // Add this { log.LogInformation("C# HTTP trigger function processed a request."); var config = new ConfigurationBuilder() .SetBasePath(context.FunctionAppDirectory) .AddJsonFile("local.settings.json", optional: true, reloadOnChange: true) .AddEnvironmentVariables() .Build(); string mySecret = config["MySecret"]; return mySecret != null ? (ActionResult)new OkObjectResult($"The secret is: {mySecret}") : new BadRequestObjectResult("Please configure MySecret in application settings"); } } """ Configure the function to use a managed identity and grant that identity access to the secret in Key Vault. Set the app setting "MySecret" to "@Microsoft.KeyVault(SecretUri=https://your-key-vault.vault.azure.net/secrets/your-secret-name/)". ### 4.3. Role-Based Access Control (RBAC) * **Why:** RBAC allows you to manage access to Azure resources based on roles and permissions. * **Do This:** * Assign built-in roles (e.g., Contributor, Reader, Owner) to users, groups, or service principals. * Create custom roles to grant granular permissions. * Use Azure Active Directory groups to manage user access. * **Don't Do This:** * Avoid granting the Owner role unnecessarily. * Do not use shared accounts. ## 5. Configuration Management ### 5.1. Standard Manage application configuration centrally and dynamically. * **Why:** Centralized configuration simplifies management and enables dynamic updates without requiring application restarts. * **Do This:** * Utilize Azure App Configuration for centralized configuration management. * Store environment-specific settings separately. * Use feature flags to enable or disable features without code deployments. * **Don't Do This:** * Avoid hardcoding configuration values in code. * Do not store sensitive settings in plain text configuration files. ### 5.2. Azure App Configuration * Azure App Configuration provides a centralized location for storing and managing application configuration settings. * **Do This:** * Integrate applications with Azure App Configuration using SDKs. * Store configuration settings as key-value pairs or JSON documents. * Implement dynamic configuration updates to refresh settings without restarting applications. * Use feature flags to control the availability of features. * **Don't Do This:** * Do not neglect to encrypt sensitive configuration settings stored in Azure App Configuration. ## 6. Automation ### 6.1. Standard Automate repetitive tasks and processes. * **Why:** Automating routine operations reduces manual effort, minimizes errors, and improves efficiency. * **Do This:** * Use Azure Automation with PowerShell or Python runbooks for tasks like starting/stopping VMs, database maintenance, and scaling resources. * Leverage Azure Logic Apps or Power Automate for multi-step workflows and integrations. * Utilise Azure Event Grid to react to events and trigger automated responses. * **Don't Do This:** * Avoid manual execution of routine tasks. * Do not create overly complex automation scripts that are difficult to maintain. ### 6.2. Azure Automation * **Do This:** * Write PowerShell or Python scripts to automate tasks. * Schedule Automation runbooks to run on a recurring basis. * Store credentials securely using credential assets. * **Don't Do This:** * Do not hardcode credentials in automation scripts. * Avoid granting excessive permissions to Automation runbooks. ## 7. Build Processes, CI/CD and Production Considerations ### 7.1. Build Processes * **Standard:** Provide repeatable builds. * **Why:** Repeatable builds are vital for tracking down bugs, and vulnerabilities. * **Do This:** Use containers when possible. Specifically consider Dockerfiles and Azure Container Registry. * **Don't Do This:** Rely on a computer to have certain software installed in order to build. """dockerfile FROM mcr.microsoft.com/dotnet/sdk:7.0 AS build-env WORKDIR /app # Copy csproj and restore as distinct layers COPY *.csproj ./ RUN dotnet restore # Copy everything else and build COPY . ./ RUN dotnet publish -c Release -o out """ ### 7.2 CI/CD Considerations * **Standard:** Build and test thoroughly before deploying. * **Why:** Build and test strategies reduce errors in production environments. * **Do This:** Integrate unit tests, integration tests and end-to-end testing. * **Don't Do This:** Deploy untested code. """yaml # Sample Azure DevOps Pipeline with Tests trigger: - main pool: vmImage: ubuntu-latest steps: - task: UseDotNet@2 displayName: 'Use .NET SDK 7.x' inputs: packageType: 'sdk' version: '7.x' - task: DotNetCoreCLI@2 displayName: 'Restore dependencies' inputs: command: 'restore' projects: '**/*.csproj' - task: DotNetCoreCLI@2 displayName: 'Build the project - Release configuration' inputs: command: 'build' arguments: '--configuration Release' projects: '**/*.csproj' - task: DotNetCoreCLI@2 displayName: 'Run unit tests - Release configuration' inputs: command: 'test' arguments: '--configuration Release' projects: '**/*Tests/*.csproj' publishTestResults: true """ ### 7.3 Production Considerations * **Standard:** Isolate production resources. * **Why:** Isolation improves security. * **Do This:** Isolate production resources in a separate resource group. * **Don't Do This:** Co-mingle development and production resources.. This document provides a consolidated view of the best practices for Deployment and DevOps within the Azure ecosystem, enabling developers and AI coding assistants to adhere to high coding standards for maintainability, performance, and security when building and deploying on Azure.
# Core Architecture Standards for Azure This document outlines the core architectural standards for developing applications on Microsoft Azure. These standards are designed to promote maintainability, scalability, security, and performance by guiding developers toward best practices and modern approaches. This document will also be used as a reference point for AI coding assistants to provide relevant and accurate suggestions. ## 1. Architectural Principles These overarching principles should guide all architectural decisions on Azure. * **Principle of Least Privilege:** Grant services and users only the permissions they require to function. * **Defense in Depth:** Implement multiple layers of security controls to protect against various threats. * **Scalability and Elasticity:** Design applications to scale automatically based on demand, leveraging Azure's elasticity. * **Resiliency:** Implement fault tolerance and self-healing mechanisms to ensure continuous availability. * **Observability:** Implement comprehensive logging, monitoring, and tracing to gain insights into application behavior and performance. * **Cost Optimization:** Design applications to minimize resource consumption and take advantage of Azure's cost management features. * **Infrastructure as Code (IaC):** Manage infrastructure using code, enabling automation, version control, and repeatability. ## 2. Fundamental Architectural Patterns ### 2.1 Microservices Architecture **Do This:** * Embrace microservices for complex applications that require independent scalability and deployment. * Design microservices around business capabilities, not technical functions. * Use lightweight communication protocols like REST or gRPC for inter-service communication. * Implement API gateways for external access to microservices. * Use Azure Kubernetes Service (AKS) for container orchestration. * Implement service discovery using Azure DNS or a dedicated service registry. **Don't Do This:** * Create monolithic applications that are difficult to scale and maintain. * Introduce tight coupling between microservices. * Expose internal microservice endpoints directly to external users. * Neglect monitoring and logging for each microservice. **Why This Matters:** Microservices enable independent scaling, deployment, and fault isolation, leading to more resilient and maintainable applications. AKS simplifies container orchestration. **Code Example (AKS Deployment):** """yaml apiVersion: apps/v1 kind: Deployment metadata: name: my-microservice spec: replicas: 3 selector: matchLabels: app: my-microservice template: metadata: labels: app: my-microservice spec: containers: - name: my-microservice image: myregistry.azurecr.io/my-microservice:latest ports: - containerPort: 8080 """ ### 2.2 Event-Driven Architecture **Do This:** * Use event-driven architecture for asynchronous communication between services. * Leverage Azure Event Hubs or Azure Service Bus for event ingestion and distribution. * Implement idempotent event handlers to prevent duplicate processing. * Design events to be immutable and contain all necessary context. * Use Azure Functions or Logic Apps to process events. **Don't Do This:** * Rely on synchronous communication for long-running operations. * Create complex event schemas without versioning. * Neglect error handling and dead-letter queues for failed events. **Why This Matters:** Asynchronous communication improves performance, scalability, and resilience. Event Hubs and Service Bus provide reliable and scalable eventing platforms. **Code Example (Azure Function Triggered by Event Hub):** """csharp using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Host; using Microsoft.Extensions.Logging; public static class EventHubTriggerCSharp { [FunctionName("EventHubTriggerCSharp")] public static void Run([EventHubTrigger("myhub", Connection = "EventHubConnectionAppSetting")] string myEventHubMessage, ILogger log) { log.LogInformation($"C# Event Hub trigger function processed a message: {myEventHubMessage}"); } } """ ### 2.3 Serverless Architecture **Do This:** * Utilize Azure Functions and Logic Apps for stateless, event-driven workloads. * Design functions to be small and focused on a single task. * Leverage Azure API Management for managing and securing serverless APIs. * Use Azure Durable Functions for orchestrating complex workflows. * Implement monitoring and logging using Azure Monitor. **Don't Do This:** * Develop long-running or stateful functions. * Overuse serverless functions for tasks that are better suited for virtual machines or containers. * Neglect security considerations when exposing serverless APIs. **Why This Matters:** Serverless architectures reduce operational overhead, scale automatically, and offer a pay-per-use pricing model. **Code Example (Azure Function with HTTP Trigger):** """csharp using System.Net; using Microsoft.AspNetCore.Mvc; using Microsoft.Azure.WebJobs; using Microsoft.Azure.WebJobs.Extensions.Http; using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Logging; public static class HttpExample { [FunctionName("HttpExample")] public static async Task<IActionResult> Run( [HttpTrigger(AuthorizationLevel.Anonymous, "get", "post", Route = null)] HttpRequest req, ILogger log) { log.LogInformation("C# HTTP trigger function processed a request."); string name = req.Query["name"]; string requestBody = await new StreamReader(req.Body).ReadToEndAsync(); dynamic data = JsonConvert.DeserializeObject(requestBody); name = name ?? data?.name; return name != null ? (ActionResult)new OkObjectResult($"Hello, {name}") : new BadRequestObjectResult( "Please pass a name on the query string or in the request body"); } } """ ### 2.4 Data Lake Architecture **Do This:** * Use Azure Data Lake Storage Gen2 as the central repository for all data, structured and unstructured. * Partition data logically based on business needs and query patterns. * Implement access control using Azure Active Directory and role-based access control (RBAC). * Use Azure Data Factory for data ingestion and transformation. * Leverage Azure Synapse Analytics for data warehousing and analytics. **Don't Do This:** * Create data silos that are difficult to access and integrate. * Store sensitive data without proper encryption and access controls. * Neglect data governance and metadata management. **Why This Matters:** A data lake provides a centralized and scalable platform for storing and processing large volumes of data, enabling advanced analytics and machine learning. **Code Example (Azure Data Factory Pipeline):** """json { "name": "my_data_pipeline", "properties": { "activities": [ { "name": "CopyData", "type": "Copy", "inputs": [ { "referenceName": "SourceDataset", "type": "DatasetReference" } ], "outputs": [ { "referenceName": "DestinationDataset", "type": "DatasetReference" } ], "translator": { "type": "TabularTranslator", "typeConversion": true, "typeConversionSettings": { "allowDataTruncation": true, "treatAsEmptyString": "" } }, "enableStaging": false } ], "datasets": [ /* Define Source and Destination Datasets here */ ] } } """ ## 3. Project Structure and Organization ### 3.1 Logical Grouping by Functionality **Do This:** * Organize code into logical modules based on functionality or business domain. * Use namespaces or folders to encapsulate related classes and functions. * Follow a consistent naming convention for modules, classes, and functions. **Don't Do This:** * Create large, monolithic projects with tightly coupled code. * Mix unrelated functionalities within the same module. * Use inconsistent naming conventions. **Why This Matters:** Logical grouping improves code readability, maintainability, and testability. **Code Example (C# Project Structure):** """ MyProject/ ├── MyProject.sln ├── MyProject.Core/ │ ├── Models/ │ │ └── Customer.cs │ ├── Services/ │ │ └── CustomerService.cs │ ├── Interfaces/ │ │ └── ICustomerService.cs ├── MyProject.API/ │ ├── Controllers/ │ │ └── CustomerController.cs │ ├── Startup.cs │ ├── appsettings.json """ ### 3.2 Separation of Concerns (SoC) **Do This:** * Apply the principle of separation of concerns (SoC) by dividing the application into distinct layers, such as presentation, business logic, and data access. * Use dependency injection to decouple components and improve testability. * Define clear interfaces between layers to promote loose coupling. **Don't Do This:** * Mix presentation logic with business logic or data access code. * Create tight dependencies between layers. * Repeat code across different layers. **Why This Matters:** SoC enhances maintainability, testability, and reusability by isolating different responsibilities within the application. **Code Example (Dependency Injection in ASP.NET Core):** """csharp // Startup.cs public void ConfigureServices(IServiceCollection services) { services.AddTransient<ICustomerService, CustomerService>(); services.AddControllers(); } // Controller public class CustomerController : ControllerBase { private readonly ICustomerService _customerService; public CustomerController(ICustomerService customerService) { _customerService = customerService; } [HttpGet] public IActionResult GetCustomers() { var customers = _customerService.GetCustomers(); return Ok(customers); } } """ ### 3.3 Infrastructure as Code (IaC) Organization **Do This:** * Use Azure Resource Manager (ARM) templates, Bicep, or Terraform to define and manage infrastructure as code. * Organize IaC code into logical modules based on the resources being provisioned. * Use parameterization to customize deployments for different environments. * Implement version control for IaC code. **Don't Do This:** * Manually provision resources through the Azure portal. * Store sensitive information (e.g., passwords, API keys) directly in IaC code. * Neglect testing and validation of IaC code. **Why This Matters:** IaC enables automation, repeatability, and version control for infrastructure deployments, reducing errors and improving consistency. **Code Example (Bicep Template
# State Management Standards for Azure This document outlines the coding standards and best practices for state management in Azure applications. These standards are intended to promote maintainability, performance, scalability, and security. By following these guidelines, developers can build robust and efficient Azure solutions. ## 1. Introduction to State Management in Azure State management is critical for building scalable and resilient applications in Azure. It involves handling data persistence, session state, user profiles, and application settings across multiple requests and instances. Choosing the right state management strategy is crucial for achieving optimal performance and reliability. ### 1.1 Key Concepts * **Statelessness:** Applications function independently of prior interactions ("no memory"). Highly scalable but requires passing all necessary context with each request. * **Stateful Applications:** Maintain context between interactions. Can improve user experience but introduces complexity in scaling and fault tolerance. * **Distributed Caching:** A shared cache accessible by multiple application instances. Essential for improving application performance and reducing database load. * **Session State:** Data associated with a specific user's session, often stored in-memory or in a distributed cache. * **Data Persistence:** Storing data permanently in a database or other storage system. ### 1.2 Azure Services for State Management * **Azure Cache for Redis:** A fully managed, in-memory data cache service based on the popular open-source Redis. * **Azure SQL Database:** A fully managed relational database service. * **Azure Cosmos DB:** A globally distributed, multi-model database service. * **Azure Blob Storage:** Object storage for unstructured data, like images, videos, and documents. * **Azure Table Storage:** NoSQL key-attribute data store for rapid development and fast access to data. (Consider Cosmos DB table API for new projects) * **Azure App Configuration:** A centralized service for managing application settings. ## 2. General Principles for Azure State Management These principles apply to all aspects of state management, regardless of the specific Azure service used. ### 2.1 Prefer Stateless Architectures * **Do This:** Design applications to be as stateless as possible, relying on external storage for state. * **Don't Do This:** Store session state or application data directly on individual VM instances. **Why:** Statelessness promotes scalability and resilience. Each application instance can handle any request without relying on local data. **Example:** Instead of storing session data in-memory on the web server: """csharp // Anti-pattern: Storing session in-memory HttpContext.Session.SetString("UserId", userId); """ Use Azure Cache for Redis: """csharp // Correct: Storing session in Redis using Microsoft.Extensions.Caching.Distributed; public class SessionController : ControllerBase { private readonly IDistributedCache _cache; public SessionController(IDistributedCache cache) { _cache = cache; } [HttpPost("set-user-id")] public async Task<IActionResult> SetUserId(string userId) { await _cache.SetStringAsync("UserId", userId); return Ok(); } [HttpGet("get-user-id")] public async Task<IActionResult> GetUserId() { string userId = await _cache.GetStringAsync("UserId"); if(userId == null) { return NotFound(); } return Ok(userId); } } """ ### 2.2 Use Distributed Caching for Read-Heavy Data * **Do This:** Implement a caching strategy using Azure Cache for Redis to reduce database load and improve response times. * **Don't Do This:** Retrieve data directly from the database for every request, especially for frequently accessed information. **Why:** Caching significantly improves performance and reduces the cost of database operations. **Example:** """csharp // Anti-pattern: Retrieving product details from the database on every request public async Task<Product> GetProductById(int productId) { using (var db = new MyDbContext()) { return await db.Products.FindAsync(productId); } } """ Using Azure Cache for Redis: """csharp // Correct: Using Azure Cache for Redis using Microsoft.Extensions.Caching.Distributed; using Newtonsoft.Json; // Ensure you have Newtonsoft.Json NuGet package installed public class ProductsController : ControllerBase { private readonly IDistributedCache _cache; private readonly MyDbContext _dbContext; public ProductsController(IDistributedCache cache, MyDbContext dbContext) { _cache = cache; _dbContext = dbContext; } [HttpGet("products/{id}")] public async Task<IActionResult> GetProduct(int id) { string recordKey = $"product:{id}"; string cachedProduct = await _cache.GetStringAsync(recordKey); if (!string.IsNullOrEmpty(cachedProduct)) { // Product found in cache Product product = JsonConvert.DeserializeObject<Product>(cachedProduct); return Ok(product); } else { // Product not found in cache, retrieve from database var product = await _dbContext.Products.FindAsync(id); if (product == null) { return NotFound(); } // Serialize and store in cache string serializedProduct = JsonConvert.SerializeObject(product); var options = new DistributedCacheEntryOptions().SetAbsoluteExpiration(DateTime.Now.AddMinutes(10)).SetSlidingExpiration(TimeSpan.FromMinutes(2)); // Cache entry options await _cache.SetStringAsync(recordKey, serializedProduct, options); return Ok(product); } } public class Product { public int ProductId { get; set; } public string Name { get; set; } public decimal Price { get; set; } } public class MyDbContext : DbContext { public MyDbContext() { } // Empty constructor required for EF public MyDbContext(DbContextOptions<MyDbContext> options) : base(options) { } // Modified Context for service dependencies // If you intend to setup a SQL Server connection, please provide connection string. protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder) { if (!optionsBuilder.IsConfigured) { optionsBuilder.UseSqlServer("Server=localhost;Database=ProductsDb;Trusted_Connection=True;MultipleActiveResultSets=true"); // Replace connection to desired. } } public DbSet<Product> Products { get; set; } } } """ ### 2.3 Choose the Right Data Store for Your Needs * **Do This:** Select the appropriate Azure data store based on the data type, access patterns, and scalability requirements. Use Azure Cosmos DB for diverse data access scenarios. * **Don't Do This:** Use a single data store for all types of data, especially if it doesn't fit the specific needs. **Why:** Each Azure data store is optimized for different types of data and workloads. **Examples:** * **Azure SQL Database:** Suitable for relational data with complex query requirements. * **Azure Cosmos DB:** Best for NoSQL data with global distribution and high scalability needs. Good when you need flexible schema, or you need multiple APIs (SQL, MongoDB, Cassandra, Gremlin, Table). * **Azure Blob Storage:** Ideal for storing unstructured data like media files. * **Azure Cache for Redis:** Best for caching frequently accessed data. ### 2.4 Apply Appropriate Expiration Policies * **Do This:** Define expiration policies for cached data to ensure data freshness and prevent stale data from being served. * **Don't Do This:** Cache data indefinitely without considering its volatility. **Why:** Stale data can lead to incorrect application behavior and poor user experience. **Example:** """csharp // Setting expiration policies for cached data var options = new DistributedCacheEntryOptions() .SetAbsoluteExpiration(DateTime.Now.AddMinutes(30)) // Expire after 30 minutes .SetSlidingExpiration(TimeSpan.FromMinutes(10)); // Expire if inactive for 10 minutes await _cache.SetStringAsync("ProductList", productListJson, options); """ ### 2.5 Implement Data Partitioning for Scalability * **Do This:** Partition large datasets across multiple nodes or containers to improve scalability and performance. * **Don't Do This:** Store all data in a single partition, which can become a bottleneck. **Why:** Partitioning distributes the load and allows for parallel processing of data. **Example (Azure Cosmos DB):** Choosing a partition key smartly is critical for optimal performance. Choose a key that distributes the load evenly and allows for efficient querying. For frequently queried field(s), use that as your partition key. The ideal candidate doesn't have only a few distinct values. ### 2.6 Secure Sensitive Data at Rest and in Transit * **Do This:** Encrypt sensitive data at rest using Azure Key Vault and in transit using HTTPS. * **Don't Do This:** Store sensitive data in plain text or transmit it over unencrypted channels. **Why:** Protecting sensitive data is crucial for compliance and data security. **Examples:** * **Azure Key Vault:** Store connection strings, API keys, and other secrets securely. * **HTTPS:** Ensure all communication between the client and server is encrypted. ### 2.7 Handle Concurrency Correctly * **Do This:** Implement optimistic or pessimistic concurrency control to prevent data corruption when multiple users access the same data simultaneously. * **Don't Do This:** Ignore concurrency issues, which can lead to data loss or inconsistencies. **Why:** Concurrency control ensures data integrity in multi-user environments. **Example (Optimistic Concurrency with Azure Cosmos DB):** Leverage the "_etag" property for optimistic concurrency. Fetch the "_etag" when reading the document and include it in the "ReplaceItemAsync" method. If the "_etag" has changed since you read the document, the operation will fail, indicating a concurrency conflict. ### 2.8 Monitor and Optimize State Management Performance * **Do This:** Use Azure Monitor to track the performance of your data stores
# Performance Optimization Standards for Azure This document outlines the performance optimization standards for Azure development. It serves as a guide for developers to write efficient, responsive, and resource-optimized applications within the Azure ecosystem. It is designed to be used by developers and as context for AI coding assistants. ## 1. Architectural Considerations ### 1.1 Choosing the Right Azure Services **Do This:** Carefully select Azure services based on performance requirements, scalability needs, and cost considerations. **Don't Do This:** Default to familiar services without evaluating if they are optimal for the workload. **Why:** Selecting the right service upfront can drastically reduce development effort and resource consumption in the long run. **Explanation:** Azure offers a broad range of services, each optimized for particular workloads. Choosing the correct service aligns with the workload's characteristics, leading to better performance and lower TCO. For example, using Azure Cosmos DB for high-throughput, low-latency globally distributed data is better than using Azure SQL Database when those characteristics are core requirements. **Code Example (Service Selection):** """ # Consider Azure Functions for serverless, event-driven scenarios. # Consider Azure Container Apps for microservices and scalable applications. # Consider Azure Kubernetes Service (AKS) for complex container orchestration needs. # Consider Azure App Service for web applications and APIs with simpler deployment requirements. """ ### 1.2 Region Selection **Do This:** Deploy Azure resources to the region closest to your users. **Don't Do This:** Assume all regions provide equal performance or latency. **Why:** Minimizing network latency improves application responsiveness. **Explanation:** The physical distance between your application and its users directly impacts latency. Azure regions offer varying levels of network connectivity. Choosing the closest region reduces round-trip times for data transfer. **Code Example (ARM Template Snippet for Region):** """json { "$schema": "https://schema.management.azure.com/schemas/2019-04-01/deploymentTemplate.json#", "contentVersion": "1.0.0.0", "parameters": { "location": { "type": "string", "defaultValue": "eastus", // Default Region (change according to user base) "metadata": { "description": "The location for all resources." } } }, "resources": [ { "type": "Microsoft.Web/sites", "apiVersion": "2022-09-01", "name": "myWebApp", "location": "[parameters('location')]", "properties": { // Web app settings } } ] } """ ### 1.3 Implementing Caching Strategies **Do This:** Implement caching at multiple layers (client, CDN, application, database). **Don't Do This:** Over-cache and risk serving stale data or under-cache and impact performance. **Why:** Caching reduces the load on backend services and improves response times. **Explanation:** Caching stores frequently accessed data closer to the user or application, reducing the need to repeatedly fetch it from the original source. Effective caching strategies involve selecting appropriate cache expiration policies, cache invalidation mechanisms, and cache tiers. **Code Example (Azure Cache for Redis - .NET):** """csharp using StackExchange.Redis; public class RedisCacheService { private static Lazy<ConnectionMultiplexer> lazyConnection = new Lazy<ConnectionMultiplexer>(() => { string cacheConnection = ConfigurationManager.AppSettings["RedisCacheConnection"].ToString(); return ConnectionMultiplexer.Connect(cacheConnection); }); public static ConnectionMultiplexer Connection => lazyConnection.Value; public string GetData(string key) { IDatabase cache = Connection.GetDatabase(); return cache.StringGet(key); } public void SetData(string key, string value, TimeSpan expiry) { IDatabase cache = Connection.GetDatabase(); cache.StringSet(key, value, expiry); } } // Usage: RedisCacheService cache = new RedisCacheService(); string cachedValue = cache.GetData("myKey"); if (string.IsNullOrEmpty(cachedValue)) { // Fetch data from source string dataFromSource = GetDataFromSource(); cache.SetData("myKey", dataFromSource, TimeSpan.FromMinutes(30)); cachedValue = dataFromSource; } //use cachedValue """ ### 1.4 Asynchronous Operations **Do This:** Use asynchronous operations to avoid blocking the main thread for long-running tasks. **Don't Do This:** Perform synchronous I/O operations on the main thread, especially in UI-intensive applications or API endpoints. **Why:** Asynchronous operations improve the responsiveness and scalability of applications. **Explanation:** Asynchronous programming allows the application to continue processing other tasks while waiting for the completion of a long-running operation (e.g., network request, database query). This prevents the application from becoming unresponsive. **Code Example (Asynchronous Web API Controller):** """csharp using Microsoft.AspNetCore.Mvc; using Microsoft.EntityFrameworkCore; [ApiController] [Route("[controller]")] public class ProductsController : ControllerBase { private readonly AppDbContext _context; public ProductsController(AppDbContext context) { _context = context; } [HttpGet] public async Task<ActionResult<IEnumerable<Product>>> GetProducts() { return await _context.Products.ToListAsync(); // Asynchronous database query } } """ ### 1.5 Autoscaling **Do This:** Configure autoscaling for compute resources (e.g., VMs, App Service plans, Azure Container Apps) to handle varying workloads. **Don't Do This:** Rely on fixed capacity, which can lead to resource bottlenecks or underutilization. **Why:** Autoscaling dynamically adjusts resources based on demand, ensuring optimal performance and cost-effectiveness. **Explanation:** Autoscaling automatically increases or decreases the number of compute instances based on predefined metrics (e.g., CPU utilization, memory consumption, request queue length). This ensures that the application can handle sudden spikes in traffic without performance degradation. **Code Example (ARM Template for App Service Autoscaling):** """json { "type": "Microsoft.Insights/autoscalesettings", "apiVersion": "2015-04-01", "name": "myAutoscaleSettings", "location": "[resourceGroup().location]", "properties": { "name": "myAutoscaleSettings", "targetResourceUri": "[resourceId('Microsoft.Web/sites', 'myWebApp')]", "profiles": [ { "name": "AutoScaleProfile", "capacity": { "minimum": "1", "maximum": "10", "default": "1" }, "rules": [ { "metricTrigger": { "metricName": "CpuPercentage", "metricResourceUri": "[resourceId('Microsoft.Web/sites', 'myWebApp')]", "timeGrain": "PT1M", "statistic": "Average", "timeWindow": "PT5M", "timeAggregation": "Average", "operator": "GreaterThan", "threshold": 70 }, "operation": { "operationType": "ChangeCount", "parameters": { "value": "1", "cooldown": "PT5M" } } } ] } ] } } """ ## 2. Database Optimization ### 2.1 Indexing Strategies **Do This:** Create appropriate indexes to speed up query execution. **Don't Do This:** Over-index, which can slow down write operations and increase storage costs. **Why:** Indexes allow the database to quickly locate data without scanning the entire table. **Explanation:** Indexes are data structures that improve the speed of data retrieval operations on a database table. However, excessive indexing can negatively impact write performance and increase storage requirements. It's crucial to analyze query patterns and create indexes selectively on frequently queried columns. **Code Example (SQL Index Creation):** """sql -- Create a non-clustered index on the 'LastName' column of the 'Customers' table CREATE NONCLUSTERED INDEX IX_Customers_LastName ON Customers (LastName); """ ### 2.2 Query Optimization **Do This:** Write efficient queries that minimize resource consumption. **Don't Do This:** Use wildcard characters at the beginning of search strings ("%string"), causing full table scans. Select all columns ("SELECT *") unnecessarily. **Why:** Efficient queries reduce database load and improve application performance. **Explanation:** Poorly written queries can lead to performance bottlenecks and excessive resource consumption. To optimize queries, avoid using wildcard characters at the beginning of search strings, select only the necessary columns, use appropriate JOIN clauses, and leverage parameterized queries. **Code Example (Optimized SQL Query):** """sql -- Instead of: SELECT * FROM Orders WHERE CustomerID LIKE '%123%'; -- Use: SELECT OrderID, OrderDate, ShippingAddress FROM Orders WHERE CustomerID = @CustomerID; --Parameterized query """ ### 2.3 Connection Pooling **Do This:** Use connection pooling to reuse database connections and reduce overhead. **Don't Do This:** Open and close database connections frequently, which can be resource-intensive. **Why:** Connection pooling improves database performance by reducing the overhead of establishing new connections. **Explanation:** Connection pooling maintains a pool of active database connections that can be reused by the application. This avoids the overhead of repeatedly creating and destroying connections, which can be a significant performance bottleneck. Most database drivers and frameworks provide built-in support for connection pooling. **Code Example (.NET Core Connection Pooling with Entity Framework Core):** """csharp services.AddDbContext<AppDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"))); """ EF Core automatically manages connection pooling. Configure connection string effectively ("Min Pool Size", "Max Pool Size"). ### 2.4 Database Sharding & Partitioning **Do This:** Consider database sharding or partitioning for very large datasets or high-throughput requirements. **Don't Do This:** Apply sharding prematurely without analyzing data volume and access patterns. **Why:** Distributing data across multiple databases or partitions improves scalability and performance. **Explanation:** Database sharding involves splitting a large database into smaller, independent databases (shards) and distributing them across multiple servers. Partitioning involves dividing a table into multiple smaller tables (partitions) within the same database. Both techniques can improve query performance and scalability by reducing the amount of data that needs to be processed. **Note:** Cosmos DB offers built-in sharding/partitioning capabilities. Choose a good partition key. ## 3. Code-Level Optimizations ### 3.1 Efficient Data Structures and Algorithms **Do This:** Choose appropriate data structures and algorithms for specific tasks. **Don't Do This:** Use inefficient data structures that lead to quadratic or exponential time complexity. **Why:** Efficient algorithms and data structures minimize resource consumption and improve performance. **Explanation:** The choice of data structures and algorithms can have a significant impact on the performance of an application. For example, using a hash table for lookups results in near constant time complexity (O(1)), while searching an unsorted array can take linear time (O(n)). **Code Example (Efficient Lookup with Dictionary):** """csharp // Instead of: List<string> names = new List<string> { "Alice", "Bob", "Charlie" }; bool found = false; foreach (string name in names) { if (name == "Bob") { found = true; break; } } // Use: Dictionary<string, bool> nameLookup = new Dictionary<string, bool> { { "Alice", true }, { "Bob", true }, { "Charlie", true } }; bool found = nameLookup.ContainsKey("Bob"); // O(1) lookup """ ### 3.2 Minimizing Object Allocation **Do This:** Minimize object allocation and garbage collection overhead. Use object pooling or caching to reuse objects. **Don't Do This:** Create excessive temporary objects, especially in performance-critical sections of code. **Why:** Frequent object allocation and garbage collection can lead to performance bottlenecks. **Explanation:** Object allocation and garbage collection are resource-intensive operations. Reducing the number of objects created and collected can improve application performance. Object pooling involves maintaining a pool of pre-allocated objects that can be reused, while caching stores frequently used objects in memory. Using "struct" instead of "class" when appropriate can also reduce memory allocation (value type vs. reference type). **Code Example (Object Pooling):** """csharp using System.Collections.Concurrent; public class StringBuilderPool { private static ConcurrentBag<StringBuilder> _objectPool = new ConcurrentBag<StringBuilder>(); public static StringBuilder Get() { if (_objectPool.TryTake(out var item)) { return item; } else { return new StringBuilder(); } } public static void Return(StringBuilder obj) { obj.Clear(); _objectPool.Add(obj); } } // Usage: StringBuilder sb = StringBuilderPool.Get(); sb.Append("Hello, world!"); string result = sb.ToString(); StringBuilderPool.Return(sb); """ ### 3.3 String Manipulation **Do This:** Use "StringBuilder" for efficient string concatenation. **Don't Do This:** Use the "+" operator repeatedly for string concatenation, which creates multiple temporary string objects. **Why:** "StringBuilder" avoids the overhead of creating new string objects for each concatenation. **Explanation:** Strings are immutable in .NET, meaning that each string concatenation operation creates a new string object. Using the "+" operator repeatedly for string concatenation can lead to excessive object allocation and garbage collection. The "StringBuilder" class provides an efficient way to build strings by concatenating multiple strings into a mutable buffer. **Code Example (Efficient String Concatenation):** """csharp // Instead of: string result = ""; for (int i = 0; i < 1000; i++) { result += i.ToString(); } // Use: StringBuilder sb = new StringBuilder(); for (int i = 0; i < 1000; i++) { sb.Append(i.ToString()); } string result = sb.ToString(); """ ### 3.4 Avoid Boxing/Unboxing **Do This:** When working with generics or collections, ensure you're not unintentionally boxing value types (like "int", "bool", "structs"). **Don't Do This:** Add value types to non-generic collections like "ArrayList" which store objects and thus require boxing. **Why:** Boxing and unboxing are performance-intensive operations that involve converting value types to reference types and vice versa. **Explanation:** Boxing is the process of converting a value type (e.g., "int", "bool", "struct") to a corresponding object reference. Unboxing is the reverse process. When value types are added to non-generic collections (e.g., "ArrayList"), they are implicitly boxed, leading to performance overhead. Using generic collections (e.g., "List<int>", "Dictionary<string, MyStruct>") avoids boxing and unboxing. **Code Example (Avoid Boxing):** """csharp // Instead of: ArrayList numbers = new ArrayList(); for (int i = 0; i < 1000; i++) { numbers.Add(i); // Boxing occurs here } // Use: List<int> numbers = new List<int>(); for (int i = 0; i < 1000; i++) { numbers.Add(i); } """ ## 4. Network Optimization ### 4.1 Minimize Data Transfer **Do This:** Only transfer the necessary data over the network. Use data compression and pagination to reduce the amount of data transferred. **Don't Do This:** Fetch large amounts of data from the server and then filter it on the client. **Why:** Reducing data transfer improves network bandwidth utilization and reduces latency. **Explanation:** Transferring large amounts of data over the network can be a significant performance bottleneck. To minimize data transfer: compress data before sending it over the network (e.g., using GZIP compression), paginate large datasets to retrieve only the data needed for the current view, and avoid fetching unnecessary data from the server. Apply filters on the server-side if possible. **Code Example (GZIP Compression in ASP.NET Core):** """csharp using Microsoft.AspNetCore.Builder; using Microsoft.AspNetCore.ResponseCompression; using Microsoft.Extensions.DependencyInjection; public class Startup { public void ConfigureServices(IServiceCollection services) { services.AddResponseCompression(options => { options.EnableForHttps = true; options.Providers.Add<GzipCompressionProvider>(); }); } public void Configure(IApplicationBuilder app) { app.UseResponseCompression(); } } """ ### 4.2 Connection Multiplexing **Do This:** Use HTTP/2 or HTTP/3 to enable connection multiplexing. **Don't Do This:** Rely on HTTP/1.1, which can lead to connection overhead due to head-of-line blocking. **Why:** Connection multiplexing allows multiple requests to be sent over a single TCP connection, reducing connection overhead. **Explanation:** HTTP/2 and HTTP/3 support connection multiplexing, which allows multiple requests to be sent over a single TCP connection. This eliminates the need to establish a new connection for each request, reducing connection overhead and improving performance. Most modern web servers and browsers support HTTP/2 and HTTP/3. Enable it in your Azure App Service configuration. ### 4.3 Content Delivery Network (CDN) **Do This:** Serve static content (e.g., images, CSS, JavaScript) from a CDN. **Don't Do This:** Serve static content directly from your application server, which can increase load and latency. **Why:** CDNs distribute content across multiple servers closer to users, reducing latency and improving performance. **Explanation:** A CDN is a distributed network of servers that delivers content to users based on their geographic location. By serving static content from a CDN, you can reduce the load on your application server and improve response times for users who are geographically dispersed. Azure CDN is a popular choice. **Code Example (Azure CDN Configuration - ARM Template - Example with Storage Account):** """json { "type": "Microsoft.Cdn/profiles", "apiVersion": "2021-06-01", "name": "myCdnProfile", "location": "[resourceGroup().location]", "sku": { "name": "Standard_Microsoft", "tier": "Standard" }, "properties": { "originHostHeader": "mystorageaccount.blob.core.windows.net" }, "resources": [ { "type": "endpoints", "apiVersion": "2021-06-01", "name": "myCdnEndpoint", "dependsOn": [ "[resourceId('Microsoft.Cdn/profiles', 'myCdnProfile')]" ], "location": "[resourceGroup().location]", "properties": { "originHostName": "mystorageaccount.blob.core.windows.net", "origins": [ { "name": "myOrigin", "properties": { "hostName": "mystorageaccount.blob.core.windows.net", "httpPort": 80, "httpsPort": 443 } } ] } } ] } """ ## 5. Monitoring and Profiling ### 5.1 Application Insights **Do This:** Use Azure Application Insights to monitor application performance, detect anomalies, and diagnose issues. **Don't Do This:** Deploy applications without proper monitoring and logging, as it hinders troubleshooting and optimization efforts. **Why:** Application Insights provides valuable insights into application behavior and performance. **Explanation:** Application Insights is a powerful monitoring and analytics service that provides insights into application performance, availability, and usage. It can be used to detect anomalies, diagnose issues, and identify areas for optimization. **Code Example (Adding Application Insights to .NET Core App):** """csharp // In Startup.cs: public void ConfigureServices(IServiceCollection services) { services.AddApplicationInsightsTelemetry(); } // To add custom telemetry: using Microsoft.ApplicationInsights; public class MyService { private readonly TelemetryClient _telemetryClient; public MyService(TelemetryClient telemetryClient) { _telemetryClient = telemetryClient; } public void DoSomething() { _telemetryClient.TrackEvent("MyCustomEvent"); _telemetryClient.TrackMetric("MyCustomMetric", 42); } } """ ### 5.2 Profiling Tools **Do This:** Use profiling tools to identify performance bottlenecks in your code. **Don't Do This:** Guess at performance issues; use data-driven analysis to identify the root cause. **Why:** Profiling tools provide detailed information about CPU usage, memory allocation, and other performance metrics. **Explanation:** Profiling tools analyze the execution of your code to identify performance bottlenecks. They provide detailed information about CPU usage, memory allocation, and other performance metrics, allowing you to pinpoint the areas of your code that are consuming the most resources. Visual Studio Profiler and PerfView are commonly used profiling tools. ### 5.3 Azure Monitor **Do This:** Utilize Azure Monitor to monitor the health and performance of Azure resources (VMs, databases, storage accounts). Create alerts for critical metrics. **Don't Do This:** Ignore resource-level metrics, which can provide valuable insights into potential issues. **Why:** Azure Monitor provides a comprehensive view of the performance and health of your Azure resources. **Explanation:** Azure Monitor provides a centralized platform for collecting and analyzing telemetry data from your Azure resources. It can be used to monitor the health and performance of VMs, databases, storage accounts, and other Azure services. Create alerts to be notified of critical issues, like high CPU or low available memory. This document provides a comprehensive overview of performance optimization standards for Azure. By following these guidelines, developers can build efficient, responsive, and scalable applications within the Azure ecosystem. Remember to continually monitor and optimize your applications based on real-world usage patterns and performance metrics gathered using Azure Monitor and Application Insights.