Excalibur.Data.Firestore
3.0.0-alpha.19
dotnet add package Excalibur.Data.Firestore --version 3.0.0-alpha.19
NuGet\Install-Package Excalibur.Data.Firestore -Version 3.0.0-alpha.19
<PackageReference Include="Excalibur.Data.Firestore" Version="3.0.0-alpha.19" />
<PackageVersion Include="Excalibur.Data.Firestore" Version="3.0.0-alpha.19" />
<PackageReference Include="Excalibur.Data.Firestore" />
paket add Excalibur.Data.Firestore --version 3.0.0-alpha.19
#r "nuget: Excalibur.Data.Firestore, 3.0.0-alpha.19"
#:package Excalibur.Data.Firestore@3.0.0-alpha.19
#addin nuget:?package=Excalibur.Data.Firestore&version=3.0.0-alpha.19&prerelease
#tool nuget:?package=Excalibur.Data.Firestore&version=3.0.0-alpha.19&prerelease
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
- Use Composite Indexes for complex queries (required for multiple equality/range filters)
- Monitor Read/Write Quotas via GCP Console - Firestore has per-second limits
- Use Transactions for atomic operations across documents
- Enable Firestore Emulator for local development and testing
- Configure Security Rules in production - never rely on client-side validation
- Design Document Structure Carefully - Firestore charges per document read
- Use Batch Writes for multiple operations (max 500 per batch)
- Prefer Shallow Hierarchies - deep subcollections increase complexity
- Cache Frequently-Read Data - Firestore charges per read operation
- 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 | Versions 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. |
-
net10.0
- AWSSDK.Core (>= 4.0.3.8)
- AWSSDK.SecurityToken (>= 4.0.5.6)
- Ben.Demystifier (>= 0.4.1)
- CloudNative.CloudEvents (>= 2.8.0)
- CloudNative.CloudEvents.SystemTextJson (>= 2.8.0)
- Cronos (>= 0.11.1)
- Dapper (>= 2.1.66)
- Excalibur.A3.Abstractions (>= 3.0.0-alpha.19)
- Excalibur.Cdc (>= 3.0.0-alpha.19)
- Excalibur.Data.Abstractions (>= 3.0.0-alpha.19)
- Excalibur.Dispatch.Abstractions (>= 3.0.0-alpha.19)
- Excalibur.EventSourcing (>= 3.0.0-alpha.19)
- Google.Api.Gax.Grpc (>= 4.13.0)
- Google.Apis.Auth (>= 1.73.0)
- Google.Cloud.Firestore (>= 3.7.0)
- Google.Protobuf (>= 3.32.1)
- Grpc.Net.Client (>= 2.71.0)
- Medo.Uuid7 (>= 1.4.0)
- MemoryPack (>= 1.21.4)
- Microsoft.ApplicationInsights (>= 2.23.0)
- Microsoft.AspNetCore.Authorization (>= 9.0.9)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Caching.Memory (>= 10.0.0)
- Microsoft.Extensions.Configuration (>= 10.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Logging (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.ObjectPool (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.0)
- MongoDB.Driver (>= 2.25.0)
- NCrontab (>= 3.4.0)
- Npgsql (>= 9.0.4)
- OpenTelemetry (>= 1.13.0)
- OpenTelemetry.Api (>= 1.13.0)
- OpenTelemetry.Extensions.Hosting (>= 1.13.0)
- QuestPDF (>= 2024.12.2)
- Snappier (>= 1.2.0)
- System.Threading.RateLimiting (>= 10.0.0)
-
net8.0
- AWSSDK.Core (>= 4.0.3.8)
- AWSSDK.SecurityToken (>= 4.0.5.6)
- Ben.Demystifier (>= 0.4.1)
- CloudNative.CloudEvents (>= 2.8.0)
- CloudNative.CloudEvents.SystemTextJson (>= 2.8.0)
- Cronos (>= 0.11.1)
- Dapper (>= 2.1.66)
- Excalibur.A3.Abstractions (>= 3.0.0-alpha.19)
- Excalibur.Cdc (>= 3.0.0-alpha.19)
- Excalibur.Data.Abstractions (>= 3.0.0-alpha.19)
- Excalibur.Dispatch.Abstractions (>= 3.0.0-alpha.19)
- Excalibur.EventSourcing (>= 3.0.0-alpha.19)
- Google.Api.Gax.Grpc (>= 4.13.0)
- Google.Apis.Auth (>= 1.73.0)
- Google.Cloud.Firestore (>= 3.7.0)
- Google.Protobuf (>= 3.32.1)
- Grpc.Net.Client (>= 2.71.0)
- Medo.Uuid7 (>= 1.4.0)
- MemoryPack (>= 1.21.4)
- Microsoft.ApplicationInsights (>= 2.23.0)
- Microsoft.AspNetCore.Authorization (>= 9.0.9)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Caching.Memory (>= 10.0.0)
- Microsoft.Extensions.Configuration (>= 10.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Logging (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.ObjectPool (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.0)
- MongoDB.Driver (>= 2.25.0)
- NCrontab (>= 3.4.0)
- Npgsql (>= 9.0.4)
- OpenTelemetry (>= 1.13.0)
- OpenTelemetry.Api (>= 1.13.0)
- OpenTelemetry.Extensions.Hosting (>= 1.13.0)
- QuestPDF (>= 2024.12.2)
- Snappier (>= 1.2.0)
- System.Diagnostics.DiagnosticSource (>= 10.0.0)
- System.IO.Pipelines (>= 10.0.0)
- System.Threading.Channels (>= 10.0.0)
- System.Threading.RateLimiting (>= 10.0.0)
-
net9.0
- AWSSDK.Core (>= 4.0.3.8)
- AWSSDK.SecurityToken (>= 4.0.5.6)
- Ben.Demystifier (>= 0.4.1)
- CloudNative.CloudEvents (>= 2.8.0)
- CloudNative.CloudEvents.SystemTextJson (>= 2.8.0)
- Cronos (>= 0.11.1)
- Dapper (>= 2.1.66)
- Excalibur.A3.Abstractions (>= 3.0.0-alpha.19)
- Excalibur.Cdc (>= 3.0.0-alpha.19)
- Excalibur.Data.Abstractions (>= 3.0.0-alpha.19)
- Excalibur.Dispatch.Abstractions (>= 3.0.0-alpha.19)
- Excalibur.EventSourcing (>= 3.0.0-alpha.19)
- Google.Api.Gax.Grpc (>= 4.13.0)
- Google.Apis.Auth (>= 1.73.0)
- Google.Cloud.Firestore (>= 3.7.0)
- Google.Protobuf (>= 3.32.1)
- Grpc.Net.Client (>= 2.71.0)
- Medo.Uuid7 (>= 1.4.0)
- MemoryPack (>= 1.21.4)
- Microsoft.ApplicationInsights (>= 2.23.0)
- Microsoft.AspNetCore.Authorization (>= 9.0.9)
- Microsoft.Extensions.Caching.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Caching.Memory (>= 10.0.0)
- Microsoft.Extensions.Configuration (>= 10.0.0)
- Microsoft.Extensions.Configuration.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Configuration.Binder (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection (>= 10.0.0)
- Microsoft.Extensions.DependencyInjection.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks (>= 10.0.0)
- Microsoft.Extensions.Diagnostics.HealthChecks.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Hosting.Abstractions (>= 10.0.0)
- Microsoft.Extensions.Http (>= 10.0.0)
- Microsoft.Extensions.Logging (>= 10.0.0)
- Microsoft.Extensions.Logging.Abstractions (>= 10.0.0)
- Microsoft.Extensions.ObjectPool (>= 10.0.0)
- Microsoft.Extensions.Options (>= 10.0.0)
- Microsoft.Extensions.Options.ConfigurationExtensions (>= 10.0.0)
- Microsoft.Extensions.Options.DataAnnotations (>= 10.0.0)
- MongoDB.Driver (>= 2.25.0)
- NCrontab (>= 3.4.0)
- Npgsql (>= 9.0.4)
- OpenTelemetry (>= 1.13.0)
- OpenTelemetry.Api (>= 1.13.0)
- OpenTelemetry.Extensions.Hosting (>= 1.13.0)
- QuestPDF (>= 2024.12.2)
- Snappier (>= 1.2.0)
- System.Diagnostics.DiagnosticSource (>= 10.0.0)
- System.Threading.Channels (>= 10.0.0)
- System.Threading.RateLimiting (>= 10.0.0)
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 |