DotNET client usage
The .NET client resolves a namespace for every event store operation. You can configure namespace resolution by passing an IEventStoreNamespaceResolver implementation to the ChronicleClient constructor.
IEventStoreNamespaceResolver
Section titled “IEventStoreNamespaceResolver”using Cratis.Chronicle;
public class NamespacesDotNetClientSampleResolver : IEventStoreNamespaceResolver{ public EventStoreNamespaceName Resolve() => EventStoreNamespaceName.Default;}import io.cratis.chronicle.EventStoreNamespaceNameimport io.cratis.chronicle.namespaces.IEventStoreNamespaceResolver
class NamespacesDotNetClientSampleResolver : IEventStoreNamespaceResolver { override fun resolve(): String = EventStoreNamespaceName.default.value}import io.cratis.chronicle.namespaces.IEventStoreNamespaceResolver;
class NamespacesDotNetClientSampleResolver implements IEventStoreNamespaceResolver { @Override public String resolve() { return "Default"; }}Elixir does not support this workflow yet.TypeScript does not support this workflow yet.The resolver returns the namespace name to use for the current context.
Built-in resolvers
Section titled “Built-in resolvers”DefaultEventStoreNamespaceResolver
Section titled “DefaultEventStoreNamespaceResolver”Always returns the default namespace. Use this when you do not need tenant isolation. This is the default when no resolver is supplied.
ClaimsBasedNamespaceResolver
Section titled “ClaimsBasedNamespaceResolver”Resolves the namespace from the current principal’s claims. This is useful when you have an authenticated user and want to map the tenant from a claim.
using Cratis.Chronicle;
public static class NamespacesDotNetClientClaimsBased{ public static ChronicleClient CreateWithDefaultClaimType(ChronicleOptions options) => // Uses the default claim type "tenant_id" new(options, namespaceResolver: new ClaimsBasedNamespaceResolver("tenant_id"));
public static ChronicleClient CreateWithCustomClaimType(ChronicleOptions options) => // Uses a custom claim type new(options, namespaceResolver: new ClaimsBasedNamespaceResolver("custom_tenant_claim"));}import io.cratis.chronicle.EventStoreNamespaceNameimport io.cratis.chronicle.namespaces.IEventStoreNamespaceResolver
/** * Takes the namespace from a claim on the currently authenticated principal, falling back to the * default namespace for anonymous requests or work happening outside a request. The Spring Boot * integration ships this exact strategy as * io.cratis.chronicle.spring.namespaces.AuthenticationNamespaceResolver; this version takes the * current claims as a parameter so it has no framework dependency. */class ClaimsBasedNamespaceResolver( private val claim: String, private val currentClaims: () -> Map<String, String>?) : IEventStoreNamespaceResolver { override fun resolve(): String = currentClaims()?.get(claim) ?: EventStoreNamespaceName.default.value}
fun createWithDefaultClaimType(currentClaims: () -> Map<String, String>?): IEventStoreNamespaceResolver = ClaimsBasedNamespaceResolver("tenant_id", currentClaims)
fun createWithCustomClaimType(currentClaims: () -> Map<String, String>?): IEventStoreNamespaceResolver = ClaimsBasedNamespaceResolver("custom_tenant_claim", currentClaims)import io.cratis.chronicle.namespaces.IEventStoreNamespaceResolver;
import java.util.Map;import java.util.function.Supplier;
// Takes the namespace from a claim on the currently authenticated principal, falling back to the// default namespace for anonymous requests or work happening outside a request. The Spring Boot// integration ships this exact strategy as// io.cratis.chronicle.spring.namespaces.AuthenticationNamespaceResolver; this version takes the// current claims as a parameter so it has no framework dependency.class ClaimsBasedNamespaceResolver implements IEventStoreNamespaceResolver { private final String claim; private final Supplier<Map<String, String>> currentClaims;
public ClaimsBasedNamespaceResolver(String claim, Supplier<Map<String, String>> currentClaims) { this.claim = claim; this.currentClaims = currentClaims; }
@Override public String resolve() { Map<String, String> claims = currentClaims.get(); String value = claims == null ? null : claims.get(claim); return value == null ? "Default" : value; }
public static IEventStoreNamespaceResolver createWithDefaultClaimType(Supplier<Map<String, String>> currentClaims) { return new ClaimsBasedNamespaceResolver("tenant_id", currentClaims); }
public static IEventStoreNamespaceResolver createWithCustomClaimType(Supplier<Map<String, String>> currentClaims) { return new ClaimsBasedNamespaceResolver("custom_tenant_claim", currentClaims); }}Elixir does not support this workflow yet.TypeScript does not support this workflow yet.If the claim is not found or the user is not authenticated, the default namespace is used.
Passing a resolver to ChronicleClient
Section titled “Passing a resolver to ChronicleClient”For console or direct-client scenarios, pass the resolver as a constructor parameter:
using Cratis.Chronicle;
public static class NamespacesDotNetClientDefaultResolver{ public static ChronicleClient Create(ChronicleOptions options) { var resolver = new DefaultEventStoreNamespaceResolver(); return new ChronicleClient(options, namespaceResolver: resolver); }}import io.cratis.chronicle.ChronicleClientimport io.cratis.chronicle.IEventStoreimport io.cratis.chronicle.namespaces.DefaultEventStoreNamespaceResolver
fun getDefaultEventStore(client: ChronicleClient, name: String): IEventStore = client.getEventStore(name, DefaultEventStoreNamespaceResolver.resolve())import io.cratis.chronicle.IEventStore;import io.cratis.chronicle.java.BlockingChronicleClient;import io.cratis.chronicle.namespaces.DefaultEventStoreNamespaceResolver;
class NamespacesDotNetClientDefaultResolver { IEventStore create(BlockingChronicleClient client, String name) { String namespace = DefaultEventStoreNamespaceResolver.INSTANCE.resolve(); return client.getEventStore(name, namespace).unwrap(); }}Elixir does not support this workflow yet.TypeScript does not support this workflow yet.Configuring in a hosted application
Section titled “Configuring in a hosted application”When using IHostApplicationBuilder (worker service or web app), configure the resolver through ChronicleClientOptions or directly via IChronicleBuilder:
using Microsoft.Extensions.Hosting;
public static class NamespacesDotNetClientHostedAppConfiguration{ public static void ConfigureViaOptions(IHostApplicationBuilder builder) => // Via options (type is resolved from DI) builder.AddCratisChronicle(options => { options.EventStore = "my-store"; options.EventStoreNamespaceResolverType = typeof(TenantNamespaceResolver); });
public static void ConfigureViaBuilder(IHostApplicationBuilder builder, ITenantContext tenantContext) => // Via builder (instance is used directly) builder.AddCratisChronicle( configureOptions: options => options.EventStore = "my-store", configure: b => b.WithNamespaceResolver(new TenantNamespaceResolver(tenantContext)));}import io.cratis.chronicle.ChronicleClientimport io.cratis.chronicle.IEventStoreimport io.cratis.chronicle.namespaces.IEventStoreNamespaceResolver
/** * Kotlin has no separate hosted-app builder to configure a resolver on structurally - a worker * service resolves the namespace and asks the client for that event store exactly like a console * application would. */fun currentEventStore(client: ChronicleClient, name: String, resolver: IEventStoreNamespaceResolver): IEventStore = client.getEventStore(name, resolver.resolve())import io.cratis.chronicle.IEventStore;import io.cratis.chronicle.java.BlockingChronicleClient;import io.cratis.chronicle.namespaces.IEventStoreNamespaceResolver;
// Java has no separate hosted-app builder to configure a resolver on structurally - a worker// service resolves the namespace and asks the client for that event store exactly like a console// application would.class NamespacesDotNetClientHostedAppConfiguration { IEventStore currentEventStore(BlockingChronicleClient client, String name, IEventStoreNamespaceResolver resolver) { return client.getEventStore(name, resolver.resolve()).unwrap(); }}Elixir does not support this workflow yet.TypeScript does not support this workflow yet.Custom resolvers
Section titled “Custom resolvers”Implement a resolver when your namespace comes from a custom context, such as a tenant provider or a request-scoped service.
If your application uses Arc Tenancy, you can resolve the namespace from its tenant context. See Arc Tenancy.
using Cratis.Chronicle;
public interface ITenantContext{ EventStoreNamespaceName CurrentTenantId { get; }}
public class TenantNamespaceResolver : IEventStoreNamespaceResolver{ readonly ITenantContext _tenantContext;
public TenantNamespaceResolver(ITenantContext tenantContext) { _tenantContext = tenantContext; }
public EventStoreNamespaceName Resolve() => _tenantContext.CurrentTenantId;}import io.cratis.chronicle.namespaces.IEventStoreNamespaceResolver
interface ITenantContext { val currentTenantId: String}
class TenantNamespaceResolver(private val tenantContext: ITenantContext) : IEventStoreNamespaceResolver { override fun resolve(): String = tenantContext.currentTenantId}import io.cratis.chronicle.namespaces.IEventStoreNamespaceResolver;
interface ITenantContext { String getCurrentTenantId();}
class TenantNamespaceResolver implements IEventStoreNamespaceResolver { private final ITenantContext tenantContext;
public TenantNamespaceResolver(ITenantContext tenantContext) { this.tenantContext = tenantContext; }
@Override public String resolve() { return tenantContext.getCurrentTenantId(); }}Elixir does not support this workflow yet.TypeScript does not support this workflow yet.using Cratis.Chronicle;
public static class NamespacesDotNetClientTenantResolverUsage{ public static ChronicleClient Create(ChronicleOptions options, ITenantContext tenantContext) => new(options, namespaceResolver: new TenantNamespaceResolver(tenantContext));}import io.cratis.chronicle.ChronicleClientimport io.cratis.chronicle.IEventStore
fun getTenantEventStore(client: ChronicleClient, name: String, tenantContext: ITenantContext): IEventStore = client.getEventStore(name, TenantNamespaceResolver(tenantContext).resolve())import io.cratis.chronicle.IEventStore;import io.cratis.chronicle.java.BlockingChronicleClient;
class NamespacesDotNetClientTenantResolverUsage { IEventStore create(BlockingChronicleClient client, String name, ITenantContext tenantContext) { String namespace = new TenantNamespaceResolver(tenantContext).resolve(); return client.getEventStore(name, namespace).unwrap(); }}Elixir does not support this workflow yet.TypeScript does not support this workflow yet.Best practices
Section titled “Best practices”- Keep resolvers deterministic within a request or operation
- Provide a safe fallback to the default namespace
- Avoid expensive resolution logic, as it is called frequently
For web applications, see ASP.NET Core namespace resolution.