Azure Cosmos DB .NET Repository and Event Sourcing

repository·main·Indexed 43 days ago

https://github.com/ievangelist/azure-cosmos-dotnet-repository

A wrapper for the Microsoft Azure Cosmos DB .NET SDK providing a dependency-injection-friendly repository pattern for CRUD operations. The library includes tools for implementing event sourcing (ES) architectures, featuring the IEventStore<TEventItem> interface, optimistic concurrency control via the AtomicEvent pattern, and projection support using the Azure Cosmos DB Change Feed.

Tokens
68.2K
Snippets
194
Records
314
Agent score
55%

What's inside Azure Cosmos DB .NET Repository

  1. Overview of Azure Cosmos DB Repository .NET SDK

    main

    The Azure Cosmos DB Repository .NET SDK is a wrapper around the official Microsoft.Azure.Cosmos NuGet package. It provides a simplified, dependency-injection-ready IRepository<T> interface for performing CRUD (Create, Read, Update, Delete) operations on objects in Azure Cosmos DB.

    To use the repository, your data models must inherit from the Item base class. The Item class provides several essential properties:

    • Id: A globally unique identifier (mapped to id in JSON).
    • Type: A property indicating the subclass name, used for implicit filtering.
    • Partition Key: A property used internally to manage partitioning, which can be optionally overridden on an item-by-item basis.
  2. Use Specifications for advanced querying and paging

    main

    The project supports using Specification objects to build complex queries, ordering, and paging logic. This allows you to encapsulate query logic outside of your services.

    Common specification patterns demonstrated in the samples include:

    • Filtering: AllPersonsOlderThanSpecifciation(int age), AllPersonsWithNameSpecification(string name)
    • Ordering: OrderByMultipleFieldsSpecification, OrderByNameSpecification
    • Paging/Scrolling: FullContinuationTokenSpecificationAsync, FullPageNumberSpecificationAsync

    These specifications are typically passed to repository methods to refine the result set.

  3. Use specialized item interfaces for Etag, TTL, and Timestamps

    main

    The repository provides specialized interfaces to support common Cosmos DB features:

    • IItemWithEtag: Adds a string? Etag property for optimistic concurrency.
    • IItemWithTimeToLive: Adds a TimeSpan? TimeToLive property to control item expiration.
    • IItemWithTimeStamps: Adds DateTime? CreatedTimeUtc, DateTime LastUpdatedTimeUtc, and long LastUpdatedTimeRaw for tracking item lifecycles.
  4. Use the IEventStore interface for Azure Cosmos DB

    main

    The library provides the IEventStore<TEventItem> interface to interact with Azure Cosmos DB as an event store. This interface supports the following core operations:

    • Saving Events: Persist individual events.
    • Saving Aggregate Roots: Save aggregate roots that contain a collection of events.
    • Reading Events: Retrieve all events from the store.
    • Streaming Events: Use IAsyncEnumerable<T> to stream events.
    • Querying Events: Apply queries to limit the set of events returned.
  5. How Projections work in the JobsTracker sample

    main

    Projections in this sample transform domain events into read-optimized data models. They implement a HandleAsync method that is triggered when a specific event occurs.

    Key projection types in the sample:

    • UsersJobsListProjection: Handles JobListCreatedEvent to populate JobsListReadItem.
    • JobsListJobAddedProjection: Handles JobAddedEvent to populate JobItem.
    • JobsListJobCompletedProjection: Handles JobCompletedEvent to update JobItem status.
  6. Define multiple distinct key policies

    main

    You can define multiple, independent unique key policies on a single model by using the keyName parameter in the [UniqueKey] attribute. Properties sharing the same keyName are grouped into a single policy.

    For example, you can have one policy for firstName + age and a completely separate policy for favouriteColor. A violation of the color policy will not be blocked by the name/age policy, and vice versa.

    using Microsoft.Azure.CosmosRepository;
    using Microsoft.Azure.CosmosRepository.Attributes;
    
    namespace Sample.Models;
    
    public class Person : Item
    {
        // Policy 1: nameAndAgePolicyKeyName
        [UniqueKey(keyName: "nameAndAgePolicyKeyName", propertyPath: "/firstName")]
        public string FirstName { get; set; }
    
        [UniqueKey(keyName: "nameAndAgePolicyKeyName", propertyPath: "/age")]
        public int Age { get; set; }
    
        public string County { get; set; }
    
        // Policy 2: favouriteColorPolicyKeyName
        [UniqueKey(keyName: "favouriteColorPolicyKeyName", propertyPath: "/favouriteColor")]
        public string FavouriteColor { get; set; }
    
        protected override string GetPartitionKeyValue() => County;
    
        public Person(string firstName, int age, string county, string favouriteColor)
        {
            FirstName = firstName;
            Age = age;
            County = county;
            FavouriteColor = favouriteColor;
        }
    }
  7. Understand the Astro project structure

    main

    A minimal Astro project follows this directory structure:

    • public/: Contains static assets like images that are served directly.
    • src/pages/: Contains .astro or .md files. Each file in this directory is exposed as a route based on its filename (e.g., src/pages/index.astro maps to the root /).
    • src/components/: A conventional location for Astro, React, Vue, Svelte, or Preact components.
    • package.json: Defines project dependencies and scripts.
    /
    ├── public/
    ├── src/
    │   └── pages/
    │       └── index.astro
    └── package.json
  8. Use the Specification pattern for queries

    main

    The Cosmos Repository uses a specification pattern (inspired by ardalis/Specification) to encapsulate filters, ordering, and projections into reusable classes. Instead of writing raw queries, you declare a query as a class inheriting from a specification base type and execute it using IRepository<T>.QueryAsync(spec).

    Core base types in Microsoft.Azure.CosmosRepository.Specification:

    • BaseSpecification<TItem>: The abstract base used to build queries via the Query property (supporting .Where(), .OrderBy(), etc.).
    • DefaultSpecification<TItem>: A concrete subclass suitable for ad-hoc or simple inline specifications.
    • PageNumberPageSizeSpecification<TItem>: A pre-wired specification for page-number based paging.
    • ContinuationTokenSpecification<TItem>: A pre-wired specification for continuation-token based paging.
    // Example of a simple specification
    public class ProductSpecificationExamples
    {
        private class ProductsPriceLowestToHighestInCategory : DefaultSpecification<Product>
        {
            public ProductsPriceLowestToHighestInCategory(string categoryId) =>
                Query
                    .Where(x => x.PartitionKey == categoryId)
                    .OrderBy(x => x.Price);
        }
    
        public async Task<IQueryResult<Product>> RunDemoAsync(IRepository<Product> repository)
        {
            IQueryResult<Product> orderedProducts = await repository
                .QueryAsync(new ProductsPriceLowestToHighestInCategory("Clothing"));
    
            return orderedProducts;
        }
    }
  9. Implement a Repository with IEventStore

    main

    The DefaultJobListRepository demonstrates how to implement a repository that uses an IEventStore<T> to manage state via event sourcing.

    Key methods include:

    • ReadAsync(Guid jobListId): Reconstructs the state of a JobsList by reading from the event store.
    • SaveAsync(JobsList jobList): Persists changes by saving the resulting events to the store.