| name | koan-multi-provider |
| description | Provider 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
var capabilities = Data<Todo, string>.QueryCaps;
if (capabilities.Capabilities.HasFlag(QueryCapabilities.LinqQueries))
{
var filtered = await Todo.Query(t => t.Priority > 3 && !t.Completed);
}
else
{
var all = await Todo.All();
var filtered = all.Where(t => t.Priority > 3 && !t.Completed).ToList();
}
if (Todo.SupportsFastRemove)
{
await Todo.RemoveAll(RemoveStrategy.Fast);
}
Context Routing
Partition (Logical Suffix)
Partitions provide logical data isolation within same provider:
var todo = new Todo { Title = "Active task" };
await todo.Save();
using (EntityContext.Partition("archive"))
{
var archived = new Todo { Title = "Archived task" };
await archived.Save();
}
using (EntityContext.Partition($"tenant-{tenantId}"))
{
var tenantTodos = await Todo.All();
}
Use Cases:
- Multi-tenant isolation
- Archival storage
- Test data separation
- Environment segmentation
Source (Named Configuration)
Sources route to different provider configurations:
{
"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"
}
}
}
}
}
using (EntityContext.Source("analytics"))
{
var stats = await Todo.Count;
}
using (EntityContext.Source("cache"))
{
var frequent = await FrequentQuery.Get(id);
}
Use Cases:
- Read replicas
- Analytics databases
- Cache layers
- Regional data centers
Adapter (Explicit Provider Override)
Adapters explicitly select provider regardless of configuration:
using (EntityContext.Adapter("mongodb"))
{
var todos = await Todo.All();
}
using (EntityContext.Adapter("json"))
{
await todo.Save();
}
Use Cases:
- Provider-specific features
- Migration testing
- Development overrides
- Provider comparison
CRITICAL RULE: Source XOR Adapter
NEVER combine Source and Adapter (ADR DATA-0077):
using (EntityContext.Source("analytics"))
using (EntityContext.Adapter("mongodb"))
{
}
using (EntityContext.Source("analytics"))
{
}
using (EntityContext.Adapter("mongodb"))
{
}
Context Nesting
Contexts nest and replace previous values:
using (EntityContext.Source("analytics"))
{
var count1 = await Todo.Count;
using (EntityContext.Partition("archive"))
{
var count2 = await Todo.Count;
}
var count3 = await Todo.Count;
}
Provider-Specific Configuration
Forcing Specific Provider
Use [DataAdapter] attribute to pin entity to provider:
[DataAdapter("mongodb")]
public class FlexibleDocument : Entity<FlexibleDocument>
{
public Dictionary<string, object> Properties { get; set; } = new();
}
[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))
{
return await Todo.Query($"CONTAINS(Title, '{searchTerm}')");
}
else
{
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