Excalibur.Data.Firestore 3.0.0-alpha.19

This is a prerelease version of Excalibur.Data.Firestore.
dotnet add package Excalibur.Data.Firestore --version 3.0.0-alpha.19
                    
NuGet\Install-Package Excalibur.Data.Firestore -Version 3.0.0-alpha.19
                    
This command is intended to be used within the Package Manager Console in Visual Studio, as it uses the NuGet module's version of Install-Package.
<PackageReference Include="Excalibur.Data.Firestore" Version="3.0.0-alpha.19" />
                    
For projects that support PackageReference, copy this XML node into the project file to reference the package.
<PackageVersion Include="Excalibur.Data.Firestore" Version="3.0.0-alpha.19" />
                    
Directory.Packages.props
<PackageReference Include="Excalibur.Data.Firestore" />
                    
Project file
For projects that support Central Package Management (CPM), copy this XML node into the solution Directory.Packages.props file to version the package.
paket add Excalibur.Data.Firestore --version 3.0.0-alpha.19
                    
#r "nuget: Excalibur.Data.Firestore, 3.0.0-alpha.19"
                    
#r directive can be used in F# Interactive and Polyglot Notebooks. Copy this into the interactive tool or source code of the script to reference the package.
#:package Excalibur.Data.Firestore@3.0.0-alpha.19
                    
#:package directive can be used in C# file-based apps starting in .NET 10 preview 4. Copy this into a .cs file before any lines of code to reference the package.
#addin nuget:?package=Excalibur.Data.Firestore&version=3.0.0-alpha.19&prerelease
                    
Install as a Cake Addin
#tool nuget:?package=Excalibur.Data.Firestore&version=3.0.0-alpha.19&prerelease
                    
Install as a Cake Tool

Excalibur.Data.Firestore

Google Cloud Firestore data provider implementation for Excalibur cloud-native data access.

Installation

dotnet add package Excalibur.Data.Firestore

Quick Start

Configuration

// In Program.cs or Startup.cs
services.AddFirestore(options =>
{
    options.ProjectId = "your-gcp-project-id";
    options.DatabaseId = "(default)"; // Optional, defaults to (default)
    options.DefaultCollection = "your-collection";
});

// Or using configuration
services.AddFirestore(configuration.GetSection("Firestore"));

appsettings.json

{
  "Firestore": {
    "ProjectId": "your-gcp-project-id",
    "DatabaseId": "(default)",
    "DefaultCollection": "your-collection",
    "UseEmulator": false,
    "EmulatorHost": "localhost:8080"
  }
}

Features

CRUD Operations

public class OrderService
{
    private readonly FirestorePersistenceProvider _provider;

    public OrderService(FirestorePersistenceProvider provider)
    {
        _provider = provider;
    }

    public async Task<Order?> GetOrderAsync(string orderId, string tenantId)
    {
        var partitionKey = new PartitionKey(tenantId, "/tenantId");
        return await _provider.GetByIdAsync<Order>(orderId, partitionKey);
    }

    public async Task<CloudOperationResult<Order>> CreateOrderAsync(Order order)
    {
        var partitionKey = new PartitionKey(order.TenantId, "/tenantId");
        return await _provider.CreateAsync(order, partitionKey);
    }

    public async Task<CloudOperationResult<Order>> UpdateOrderAsync(Order order, string etag)
    {
        var partitionKey = new PartitionKey(order.TenantId, "/tenantId");
        return await _provider.UpdateAsync(order, partitionKey, etag);
    }
}

Queries

public async Task<IReadOnlyList<Order>> GetOrdersByStatusAsync(
    string tenantId,
    string status)
{
    var partitionKey = new PartitionKey(tenantId, "/tenantId");

    // Firestore uses collection path as partition key
    var result = await _provider.QueryAsync<Order>(
        "status = @status",
        partitionKey,
        new Dictionary<string, object> { ["status"] = status });

    return result.Documents;
}

Transactional Batches

public async Task<CloudBatchResult> ProcessOrderBatchAsync(
    string tenantId,
    Order order,
    OrderLine[] lines)
{
    var partitionKey = new PartitionKey(tenantId, "/tenantId");
    var operations = new List<ICloudBatchOperation>
    {
        new CloudBatchCreateOperation(order.Id, order)
    };

    foreach (var line in lines)
    {
        operations.Add(new CloudBatchCreateOperation(line.Id, line));
    }

    return await _provider.ExecuteBatchAsync(partitionKey, operations);
}

Real-time Change Listener

public async Task SubscribeToChangesAsync(CancellationToken cancellationToken)
{
    var subscription = await _provider.CreateChangeFeedSubscriptionAsync<Order>(
        "orders",
        new ChangeFeedOptions
        {
            StartPosition = ChangeFeedStartPosition.Now,
            MaxBatchSize = 100
        },
        cancellationToken);

    await subscription.StartAsync(cancellationToken);

    await foreach (var change in subscription.ReadChangesAsync(cancellationToken))
    {
        Console.WriteLine($"Change detected: {change.DocumentId}, Type: {change.EventType}");
        // Process the change
    }
}

Optimistic Concurrency

public async Task<CloudOperationResult<Order>> SafeUpdateAsync(
    Order order,
    string etag)
{
    var partitionKey = new PartitionKey(order.TenantId, "/tenantId");

    // ETag is derived from Firestore UpdateTime
    var result = await _provider.UpdateAsync(order, partitionKey, etag);

    if (!result.Success && result.StatusCode == 412)
    {
        // Precondition failed - document was modified since etag
        Console.WriteLine("Concurrent modification detected");
    }

    return result;
}

Health Checks

services.AddHealthChecks()
    .AddCheck<FirestoreHealthCheck>(
        name: "firestore",
        tags: new[] { "database", "gcp" });

Configuration Options

Connection Settings

Option Description Default
Name Provider instance name for identification Firestore
ProjectId GCP project ID Required*
DefaultCollection Default collection name for operations -
CredentialsPath Path to service account JSON file -
CredentialsJson Service account JSON content (for containers) -

*Required unless using EmulatorHost.

Performance Settings

Option Description Default
TimeoutInSeconds Operation timeout 30
MaxRetryAttempts Maximum retry attempts for transient failures 3

Emulator Settings

Option Description Default
EmulatorHost Firestore emulator host:port (e.g., "localhost:8080") -

Error Handling

The provider returns CloudOperationResult<T> with status codes for error handling:

var result = await _provider.UpdateAsync(order, partitionKey, etag);

if (!result.Success)
{
    switch (result.StatusCode)
    {
        case 404:
            Console.WriteLine("Document not found");
            break;
        case 409:
            Console.WriteLine("Document already exists");
            break;
        case 412:
            Console.WriteLine("Precondition failed - document was modified (ETag mismatch)");
            break;
        case 429:
            Console.WriteLine("Resource exhausted - too many requests");
            break;
        case 503:
            Console.WriteLine("Service unavailable - retry with backoff");
            break;
    }
}

Firestore-Specific Errors

gRPC Status HTTP Code Description
NOT_FOUND 404 Document or collection not found
ALREADY_EXISTS 409 Document already exists (on create)
FAILED_PRECONDITION 412 Precondition failed (ETag mismatch)
RESOURCE_EXHAUSTED 429 Quota exceeded
ABORTED 409 Transaction aborted (retry)
UNAVAILABLE 503 Service temporarily unavailable

Authentication

Firestore uses Google Cloud authentication. Configure one of:

Application Default Credentials

# Local development
gcloud auth application-default login

# In GCP (GKE, Cloud Run, etc.)
# Automatically uses service account

Service Account Key

services.AddFirestore(options =>
{
    options.ProjectId = "your-project";
    options.CredentialPath = "/path/to/service-account.json";
});

Environment Variable

export GOOGLE_APPLICATION_CREDENTIALS="/path/to/service-account.json"

Container/Kubernetes Deployment

For containerized environments where file paths are impractical:

services.AddFirestore(options =>
{
    options.ProjectId = "your-project";
    // JSON content from Kubernetes secret or environment variable
    options.CredentialsJson = Environment.GetEnvironmentVariable("GCP_CREDENTIALS_JSON");
});

Collection Structure

Firestore uses a hierarchical document model:

// Root collection
var partitionKey = new PartitionKey("orders", "/collection");

// Subcollection
var subPartitionKey = new PartitionKey("orders/order-123/items", "/collection");

Best Practices

  1. Use Composite Indexes for complex queries (required for multiple equality/range filters)
  2. Monitor Read/Write Quotas via GCP Console - Firestore has per-second limits
  3. Use Transactions for atomic operations across documents
  4. Enable Firestore Emulator for local development and testing
  5. Configure Security Rules in production - never rely on client-side validation
  6. Design Document Structure Carefully - Firestore charges per document read
  7. Use Batch Writes for multiple operations (max 500 per batch)
  8. Prefer Shallow Hierarchies - deep subcollections increase complexity
  9. Cache Frequently-Read Data - Firestore charges per read operation
  10. Use Appropriate Data Types - leverage Firestore's native types (timestamps, geo points)

Emulator Support

For local development and testing:

# Start Firestore emulator
gcloud emulators firestore start --host-port=localhost:8080
services.AddFirestore(options =>
{
    options.ProjectId = "test-project";
    options.UseEmulator = true;
    options.EmulatorHost = "localhost:8080";
});

License

See LICENSE files in the repository root.

Product Compatible and additional computed target framework versions.
.NET net8.0 is compatible.  net8.0-android was computed.  net8.0-browser was computed.  net8.0-ios was computed.  net8.0-maccatalyst was computed.  net8.0-macos was computed.  net8.0-tvos was computed.  net8.0-windows was computed.  net9.0 is compatible.  net9.0-android was computed.  net9.0-browser was computed.  net9.0-ios was computed.  net9.0-maccatalyst was computed.  net9.0-macos was computed.  net9.0-tvos was computed.  net9.0-windows was computed.  net10.0 is compatible.  net10.0-android was computed.  net10.0-browser was computed.  net10.0-ios was computed.  net10.0-maccatalyst was computed.  net10.0-macos was computed.  net10.0-tvos was computed.  net10.0-windows was computed. 
Compatible target framework(s)
Included target framework(s) (in package)
Learn more about Target Frameworks and .NET Standard.

NuGet packages (2)

Showing the top 2 NuGet packages that depend on Excalibur.Data.Firestore:

Package Downloads
Excalibur.EventSourcing.Firestore

Google Cloud Firestore event store implementation for Excalibur event sourcing.

Excalibur.Outbox.Firestore

Google Cloud Firestore implementation of the cloud-native outbox pattern for Excalibur event sourcing.

GitHub repositories

This package is not used by any popular GitHub repositories.

Version Downloads Last Updated
3.0.0-alpha.19 33 2/26/2026