Skip to content

Commit 339b66f

Browse files
committed
Implement ICouchbaseClientProvider for a cleaner DI approach
1 parent 525660e commit 339b66f

File tree

6 files changed

+83
-48
lines changed

6 files changed

+83
-48
lines changed

src/Couchbase.Aspire.Client/AspireCouchbaseExtensions.cs

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -123,22 +123,14 @@ void ConfigureClusterOptions(ClusterOptions options)
123123
if (serviceKey is null)
124124
{
125125
builder.Services.AddCouchbase(ConfigureClusterOptions);
126-
127-
if (!string.IsNullOrEmpty(settings.BucketName))
128-
{
129-
builder.Services.TryAddSingleton<INamedBucketProvider>(sp =>
130-
new BucketProvider(sp.GetRequiredService<IClusterProvider>(), settings.BucketName));
131-
}
126+
builder.Services.TryAddSingleton<ICouchbaseClientProvider>(sp =>
127+
new CouchbaseClientProvider(sp.GetRequiredService<IClusterProvider>(), settings.BucketName));
132128
}
133129
else
134130
{
135131
builder.Services.AddKeyedCouchbase(serviceKey, ConfigureClusterOptions);
136-
137-
if (!string.IsNullOrEmpty(settings.BucketName))
138-
{
139-
builder.Services.TryAddKeyedSingleton<INamedBucketProvider>(serviceKey, (sp, serviceKey) =>
140-
new BucketProvider(sp.GetRequiredKeyedService<IClusterProvider>(serviceKey), settings.BucketName));
141-
}
132+
builder.Services.TryAddKeyedSingleton<ICouchbaseClientProvider>(serviceKey, (sp, serviceKey) =>
133+
new CouchbaseClientProvider(sp.GetRequiredKeyedService<IClusterProvider>(serviceKey), settings.BucketName));
142134
}
143135

144136
if (!settings.DisableTracing)

src/Couchbase.Aspire.Client/BucketProvider.cs

Lines changed: 0 additions & 31 deletions
This file was deleted.
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
using System.Diagnostics.CodeAnalysis;
2+
using Couchbase.Extensions.DependencyInjection;
3+
4+
namespace Couchbase.Aspire.Client;
5+
6+
/// <summary>
7+
/// Provides access to a Couchbase cluster and named bucket.
8+
/// </summary>
9+
/// <remarks>
10+
/// <see cref="INamedBucketProvider.GetBucketAsync"/> will throw a <see cref="CouchbaseException"/>
11+
/// if a bucket name is not configured for this client.
12+
/// </remarks>
13+
public interface ICouchbaseClientProvider : IClusterProvider, INamedBucketProvider
14+
{
15+
}
16+
17+
internal sealed class CouchbaseClientProvider(IClusterProvider clusterProvider, string? bucketName) : ICouchbaseClientProvider
18+
{
19+
// The default implementation of IClusterProvider also implements IBucketProvider
20+
private readonly IBucketProvider? _bucketProvider = clusterProvider as IBucketProvider;
21+
22+
public string BucketName => bucketName ?? "";
23+
24+
public ValueTask<ICluster> GetClusterAsync() => clusterProvider.GetClusterAsync();
25+
26+
public ValueTask<IBucket> GetBucketAsync()
27+
{
28+
if (string.IsNullOrEmpty(bucketName))
29+
{
30+
ThrowNoBucketNameException();
31+
}
32+
33+
if (_bucketProvider is not null)
34+
{
35+
// Fast path for the default implementation of IClusterProvider
36+
return _bucketProvider.GetBucketAsync(bucketName);
37+
}
38+
else
39+
{
40+
// Fallback
41+
return GetBucketManualAsync(bucketName);
42+
}
43+
}
44+
45+
private async ValueTask<IBucket> GetBucketManualAsync(string bucketName)
46+
{
47+
var cluster = await clusterProvider.GetClusterAsync().ConfigureAwait(false);
48+
return await cluster.BucketAsync(bucketName).ConfigureAwait(false);
49+
}
50+
51+
// Don't dispose of the cluster provider, it's owned by the DI container and will
52+
// be disposed via that path.
53+
54+
void IDisposable.Dispose()
55+
{
56+
}
57+
58+
ValueTask IAsyncDisposable.DisposeAsync() => ValueTask.CompletedTask;
59+
60+
[DoesNotReturn]
61+
private static void ThrowNoBucketNameException()
62+
{
63+
throw new CouchbaseException("No bucket name was configured for this Couchbase client.");
64+
}
65+
}

tests/Aspire.Test.WebApp/Components/Pages/Counter.razor

Lines changed: 3 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
@using Couchbase.Core.Exceptions.KeyValue
33
@using Couchbase.Extensions.DependencyInjection
44
@rendermode InteractiveServer
5-
@inject INamedBucketProvider BucketProvider
5+
@inject ICounterCollectionProvider CollectionProvider
66

77
<PageTitle>Counter</PageTitle>
88

@@ -27,8 +27,7 @@
2727
{
2828
try
2929
{
30-
var bucket = await BucketProvider.GetBucketAsync();
31-
var collection = bucket.Scope("test-scope").Collection("counter");
30+
var collection = await CollectionProvider.GetCollectionAsync();
3231

3332
var result = await collection.GetAsync("counter", new Couchbase.KeyValue.GetOptions()
3433
.Transcoder(new Couchbase.Core.IO.Transcoders.LegacyTranscoder()));
@@ -49,8 +48,7 @@
4948
{
5049
try
5150
{
52-
var bucket = await BucketProvider.GetBucketAsync();
53-
var collection = bucket.Scope("test-scope").Collection("counter");
51+
var collection = await CollectionProvider.GetCollectionAsync();
5452

5553
try
5654
{
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
using Couchbase.Aspire.Client;
2+
using Couchbase.Extensions.DependencyInjection;
3+
4+
namespace Aspire.Test.WebApp;
5+
6+
public interface ICounterCollectionProvider : INamedCollectionProvider;
7+
8+
public class CounterCollectionProvider(ICouchbaseClientProvider clientProvider)
9+
: NamedCollectionProvider(clientProvider, "test-scope", "counter"), ICounterCollectionProvider;

tests/Aspire.Test.WebApp/Program.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
1+
using Aspire.Test.WebApp;
12
using Aspire.Test.WebApp.Components;
23
using Couchbase.Aspire.Client;
34

45
var builder = WebApplication.CreateBuilder(args);
56

67
builder.AddServiceDefaults();
78
builder.AddCouchbaseClient("test-bucket");
9+
builder.Services.AddSingleton<ICounterCollectionProvider, CounterCollectionProvider>();
810

911
// Add services to the container.
1012
builder.Services.AddRazorComponents()

0 commit comments

Comments
 (0)