# Data Standard: Azure Cosmos DB

> **Scope:** data-nosql
> **Layer:** 2
> **Keywords:** cosmos-db, nosql, document-database, event-store, partition-key, azure-cosmos
> **Load When:** a NoSQL document store, event store, or globally distributed data is in scope

**Verified against:** .NET 10 + Microsoft.Azure.Cosmos SDK v3. Last-verified: 2026-05-20.

---

## Overview
NoSQL document database — use for high-throughput, globally distributed, or schema-flexible data.

## When to Use Cosmos DB
✅ Event store (append-only, high write throughput)
✅ User activity/session data (variable schema)
✅ Multi-region writes required
✅ JSON documents with flexible schema
❌ Relational data with complex joins (use Azure SQL)
❌ Small datasets (cost inefficient)

## Container Design
```json
{
  "partitionKey": "/tenantId",
  "indexingPolicy": {
    "includedPaths": [{ "path": "/*" }],
    "excludedPaths": [
      { "path": "/largePayload/*" },
      { "path": "/_etag/?" }
    ]
  }
}
```

### Partition Key Strategy
- High cardinality: userId, tenantId, orderId
- Even distribution: avoid hot partitions (e.g., /status = "active" for 90% of docs)
- Query alignment: partition key should appear in most queries

## SDK Setup
```csharp
// Program.cs
builder.Services.AddSingleton(sp =>
    new CosmosClient(
        config["CosmosDb:ConnectionString"],
        new CosmosClientOptions
        {
            SerializerOptions = new CosmosSerializationOptions
            {
                PropertyNamingPolicy = CosmosPropertyNamingPolicy.CamelCase
            },
            ApplicationName = "MyApp"
        }));

builder.Services.AddSingleton<ICosmosRepository<Order>>(sp =>
    new CosmosRepository<Order>(
        sp.GetRequiredService<CosmosClient>(),
        config["CosmosDb:DatabaseId"],
        "orders"));
```

## Repository Pattern
```csharp
public class CosmosRepository<T> where T : CosmosDocument
{
    private readonly Container _container;

    public async Task<T?> GetAsync(string id, string partitionKey)
    {
        try
        {
            var response = await _container.ReadItemAsync<T>(id, new PartitionKey(partitionKey));
            return response.Resource;
        }
        catch (CosmosException ex) when (ex.StatusCode == HttpStatusCode.NotFound)
        {
            return null;
        }
    }

    public async Task<IReadOnlyList<T>> QueryAsync(string sql, Dictionary<string, object>? parameters = null)
    {
        var query = new QueryDefinition(sql);
        if (parameters != null)
            foreach (var (k, v) in parameters) query = query.WithParameter(k, v);

        var results = new List<T>();
        using var feed = _container.GetItemQueryIterator<T>(query);
        while (feed.HasMoreResults)
        {
            var page = await feed.ReadNextAsync();
            results.AddRange(page);
        }
        return results;
    }

    public async Task UpsertAsync(T document)
        => await _container.UpsertItemAsync(document, new PartitionKey(document.PartitionKey));
}
```

## Document Base Class
```csharp
public abstract class CosmosDocument
{
    [JsonPropertyName("id")]
    public string Id { get; set; } = Guid.NewGuid().ToString();

    [JsonPropertyName("_partitionKey")]
    public abstract string PartitionKey { get; }

    [JsonPropertyName("type")]
    public string Type => GetType().Name;

    [JsonPropertyName("createdAt")]
    public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
}
```

## Cost Optimization
- Enable TTL on transient data (session, temp tokens)
- Use serverless mode for dev/test environments
- Index only queried paths (exclude large blobs)
- Batch operations with TransactionalBatch for same partition
- Use bulk execution for large imports
