koan-multi-provider

sylin-org's avatarfrom sylin-org

Provider transparency, capability detection, context routing (partition/source/adapter)

⭐2stars🔀1forks📁View on GitHub🕐Updated Nov 11, 2025

When & Why to Use This Skill

The Koan Multi-Provider skill provides a robust abstraction layer for database transparency, enabling a 'write once, run anywhere' approach across SQL, NoSQL, Vector, and JSON stores. It optimizes data architecture through automatic capability detection, allowing applications to gracefully handle feature differences between providers while offering advanced context routing for multi-tenancy and performance-oriented data sourcing.

Use Cases

  • Multi-tenant isolation: Implementing logical data separation for different tenants or environments using the Partition routing feature without changing the underlying schema.
  • Environment-agnostic development: Seamlessly switching between local file-based storage (JSON/SQLite) for development and enterprise-grade databases (PostgreSQL/MongoDB) for production via simple configuration.
  • Read-replica and cache routing: Directing high-traffic read queries to analytics replicas or Redis cache layers using Source routing to improve system performance and scalability.
  • Capability-aware application logic: Building resilient systems that detect database features (like Full-Text Search or LINQ support) at runtime and automatically fall back to client-side processing when a feature is unavailable.
  • Database migration and testing: Testing application behavior across different database engines (e.g., switching from SQL Server to MongoDB) to compare performance or validate provider-specific features using Adapter overrides.
namekoan-multi-provider
descriptionProvider transparency, capability detection, context routing (partition/source/adapter)

Koan Multi-Provider Transparency

Core Principle

Same entity code works across SQL, NoSQL, Vector, JSON stores. Koan Framework provides complete provider transparency with automatic capability detection and graceful fallbacks.

Revolutionary Approach

Write code once, run on any data provider:

  • PostgreSQL - Relational with JSON support
  • MongoDB - Document database
  • SQLite - Embedded relational
  • Redis - Key-value cache
  • JSON - File-based development
  • Weaviate/Milvus - Vector databases
  • InMemory - Testing

No code changes needed to switch providers. Just change configuration.

Provider Capability Detection

// Check what current provider supports
var capabilities = Data<Todo, string>.QueryCaps;

if (capabilities.Capabilities.HasFlag(QueryCapabilities.LinqQueries))
{
    // Provider supports server-side LINQ (Postgres, Mongo, SQL Server)
    var filtered = await Todo.Query(t => t.Priority > 3 && !t.Completed);
}
else
{
    // Provider requires client-side filtering (JSON, InMemory)
    var all = await Todo.All();
    var filtered = all.Where(t => t.Priority > 3 && !t.Completed).ToList();
}

// Check fast removal support
if (Todo.SupportsFastRemove)
{
    // Provider supports TRUNCATE/DROP (Postgres, SQL Server, Mongo)
    await Todo.RemoveAll(RemoveStrategy.Fast);
}

Context Routing

Partition (Logical Suffix)

Partitions provide logical data isolation within same provider:

// Default partition
var todo = new Todo { Title = "Active task" };
await todo.Save(); // Stored as Todo

// Archive partition
using (EntityContext.Partition("archive"))
{
    var archived = new Todo { Title = "Archived task" };
    await archived.Save(); // Stored as Todo#archive
}

// Tenant isolation
using (EntityContext.Partition($"tenant-{tenantId}"))
{
    var tenantTodos = await Todo.All(); // Only this tenant's data
}

Use Cases:

  • Multi-tenant isolation
  • Archival storage
  • Test data separation
  • Environment segmentation

Source (Named Configuration)

Sources route to different provider configurations:

// appsettings.json
{
  "Koan": {
    "Data": {
      "Sources": {
        "Default": {
          "Adapter": "postgres",
          "ConnectionString": "Host=localhost;Database=main"
        },
        "Analytics": {
          "Adapter": "postgres",
          "ConnectionString": "Host=readonly-replica;Database=analytics"
        },
        "Cache": {
          "Adapter": "redis",
          "ConnectionString": "localhost:6379"
        }
      }
    }
  }
}

// Route to analytics read-replica
using (EntityContext.Source("analytics"))
{
    var stats = await Todo.Count; // Reads from replica
}

// Route to cache
using (EntityContext.Source("cache"))
{
    var frequent = await FrequentQuery.Get(id); // From Redis
}

Use Cases:

  • Read replicas
  • Analytics databases
  • Cache layers
  • Regional data centers

Adapter (Explicit Provider Override)

Adapters explicitly select provider regardless of configuration:

// Force MongoDB
using (EntityContext.Adapter("mongodb"))
{
    var todos = await Todo.All(); // Always uses MongoDB
}

// Force JSON for testing
using (EntityContext.Adapter("json"))
{
    await todo.Save(); // Writes to JSON file
}

Use Cases:

  • Provider-specific features
  • Migration testing
  • Development overrides
  • Provider comparison

CRITICAL RULE: Source XOR Adapter

NEVER combine Source and Adapter (ADR DATA-0077):

// ❌ WRONG: Conflicting context
using (EntityContext.Source("analytics"))
using (EntityContext.Adapter("mongodb"))
{
    // Which wins? Undefined behavior!
}

// ✅ CORRECT: Use one or the other
using (EntityContext.Source("analytics"))
{
    // Routes via named source configuration
}

// OR

using (EntityContext.Adapter("mongodb"))
{
    // Explicit provider override
}

Context Nesting

Contexts nest and replace previous values:

// Outer context
using (EntityContext.Source("analytics"))
{
    var count1 = await Todo.Count; // analytics source

    // Inner context replaces outer
    using (EntityContext.Partition("archive"))
    {
        var count2 = await Todo.Count; // analytics + archive partition
    }

    var count3 = await Todo.Count; // back to analytics (no partition)
}

Provider-Specific Configuration

Forcing Specific Provider

Use [DataAdapter] attribute to pin entity to provider:

// Always use MongoDB for this entity
[DataAdapter("mongodb")]
public class FlexibleDocument : Entity<FlexibleDocument>
{
    public Dictionary<string, object> Properties { get; set; } = new();
}

// Always use vector database
[DataAdapter("weaviate")]
public class MediaEmbedding : Entity<MediaEmbedding>
{
    [VectorField]
    public float[] Embedding { get; set; } = Array.Empty<float>();
}

Capability Fallback Patterns

public async Task<List<Todo>> SearchWithFallback(string searchTerm)
{
    var caps = Data<Todo, string>.QueryCaps;

    if (caps.Capabilities.HasFlag(QueryCapabilities.FullTextSearch))
    {
        // Provider supports full-text search
        return await Todo.Query($"CONTAINS(Title, '{searchTerm}')");
    }
    else
    {
        // Fallback to client-side filtering
        var all = await Todo.All();
        return all.Where(t => t.Title.Contains(searchTerm,
            StringComparison.OrdinalIgnoreCase)).ToList();
    }
}

Provider Comparison Matrix

Provider LINQ Queries Transactions Fast Remove Vector Search JSON Fields
PostgreSQL ✅ ✅ ✅ ✅ (pgvector) ✅
SQL Server ✅ ✅ ✅ ❌ ✅ (limited)
MongoDB ✅ ✅ ✅ ❌ ✅ (native)
SQLite ✅ ✅ ✅ ❌ ✅ (json1)
Redis ❌ ❌ ✅ ❌ ✅
JSON ❌ ❌ ❌ ❌ ✅ (native)
InMemory ❌ ❌ ❌ ❌ ✅
Weaviate ❌ ❌ ❌ ✅ ✅
Milvus ❌ ❌ ❌ ✅ ✅

When This Skill Applies

Invoke this skill when:

  • ✅ Working with multiple data providers
  • ✅ Switching between providers
  • ✅ Implementing multi-tenant isolation
  • ✅ Routing to read replicas
  • ✅ Writing capability-aware code
  • ✅ Debugging provider-specific issues

Reference Documentation

  • Full Guide: docs/guides/entity-capabilities-howto.md § Context Routing
  • ADR: DATA-0077 (Source XOR Adapter rule)
  • Sample: samples/S10.DevPortal/ (Multi-provider showcase)
  • Sample: samples/S14.AdapterBench/ (Provider performance comparison)

Framework Compliance

Multi-provider patterns are fundamental to Koan Framework:

  • ✅ Write provider-agnostic code
  • ✅ Use capability detection
  • ✅ Implement graceful fallbacks
  • ✅ Follow Source XOR Adapter rule
  • ❌ Never write provider-specific code without capability checks
  • ❌ Never hard-code provider assumptions