Skip to content

Commit

Permalink
Introduce FirestoreDbBuilder
Browse files Browse the repository at this point in the history
This is an extensible way of providing more control over FirestoreDb creation, in a manner consistent with other Cloud client libraries.
  • Loading branch information
jskeet committed Aug 27, 2019
1 parent 6036478 commit b917790
Show file tree
Hide file tree
Showing 4 changed files with 197 additions and 17 deletions.
Expand Up @@ -12,6 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Api.Gax;
using Google.Api.Gax.Grpc;
using System;

Expand Down Expand Up @@ -141,4 +142,31 @@ internal static string GetDatabaseResourceName(string resource)
void ThrowInvalidResource() => throw new ArgumentException($"{resource} is not a valid Firestore resource name", nameof(resource));
}
}

// Support for FirestoreDbBuilder.
public sealed partial class FirestoreClientBuilder : ClientBuilderBase<FirestoreClient>
{
/// <summary>
/// Creates a new instance with no settings.
/// </summary>
public FirestoreClientBuilder()
{
}

/// <summary>
/// Creates a <see cref="FirestoreClientBuilder"/> by copying common settings from another builder.
/// This method is intended for use in Google.Cloud.Firestore with FirestoreDbBuilder. It will
/// work in other scenarios, but is usually not necessary.
/// </summary>
/// <typeparam name="T">The client type of the builder.</typeparam>
/// <param name="builder">The builder to copy settings from. Must not be null.</param>
/// <returns>A FirestoreClientBuilder with common settings from <paramref name="builder"/>.</returns>
public static FirestoreClientBuilder FromOtherBuilder<T>(ClientBuilderBase<T> builder)
{
GaxPreconditions.CheckNotNull(builder, nameof(builder));
var ret = new FirestoreClientBuilder();
ret.CopyCommonSettings(builder);
return ret;
}
}
}
@@ -0,0 +1,63 @@
// Copyright 2019, Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Api.Gax.Testing;
using Google.Cloud.Firestore.V1;
using Grpc.Core;
using System;
using Xunit;

namespace Google.Cloud.Firestore.Tests
{
public class FirestoreDbBuilderTest
{
[Fact]
public void MaximalConfiguration()
{
string warning = null;
var builder = new FirestoreDbBuilder
{
ProjectId = "proj",
DatabaseId = "db",
CallInvoker = new FakeCallInvoker(),
WarningLogger = text => warning = text,
Settings = new FirestoreSettings { Clock = new FakeClock() }
};
var db = builder.Build();
Assert.Equal("proj", db.ProjectId);
Assert.Equal("db", db.DatabaseId);
db.LogWarning("Test warning");
Assert.Equal("Test warning", warning);
Assert.IsType<FakeClock>(db.Client.Settings.Clock);
}

private class FakeCallInvoker : CallInvoker
{
public override AsyncClientStreamingCall<TRequest, TResponse> AsyncClientStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) =>
throw new NotImplementedException();

public override AsyncDuplexStreamingCall<TRequest, TResponse> AsyncDuplexStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options) =>
throw new NotImplementedException();

public override AsyncServerStreamingCall<TResponse> AsyncServerStreamingCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) =>
throw new NotImplementedException();

public override AsyncUnaryCall<TResponse> AsyncUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) =>
throw new NotImplementedException();

public override TResponse BlockingUnaryCall<TRequest, TResponse>(Method<TRequest, TResponse> method, string host, CallOptions options, TRequest request) =>
throw new NotImplementedException();
}
}
}
34 changes: 17 additions & 17 deletions apis/Google.Cloud.Firestore/Google.Cloud.Firestore/FirestoreDb.cs
Expand Up @@ -25,10 +25,9 @@

namespace Google.Cloud.Firestore
{
// TODO: Make this abstract with all the trimmings when we have actual RPCs to issue.

/// <summary>
/// A Firestore database
/// A Firestore database. Create instances using the static <see cref="Create(string, FirestoreClient)"/> and <see cref="CreateAsync(string, FirestoreClient)"/>
/// methods, or using a <see cref="FirestoreDbBuilder"/>.
/// </summary>
public sealed class FirestoreDb
{
Expand Down Expand Up @@ -97,7 +96,8 @@ private FirestoreDb(string projectId, string databaseId, FirestoreClient client,
/// the project will be automatically detected if possible.</param>
/// <param name="client">The client to use for RPC operations. May be null, in which case a client will be created with default credentials.</param>
/// <returns>A new instance.</returns>
public static FirestoreDb Create(string projectId = null, FirestoreClient client = null) => Create(projectId, null, client);
public static FirestoreDb Create(string projectId = null, FirestoreClient client = null) =>
Create(projectId ?? Platform.Instance().ProjectId, null, client ?? FirestoreClient.Create(), null);

/// <summary>
/// Asynchronously creates an instance for the specified project, using the specified <see cref="FirestoreClient"/> for RPC operations.
Expand All @@ -107,7 +107,7 @@ private FirestoreDb(string projectId, string databaseId, FirestoreClient client,
/// <param name="client">The client to use for RPC operations. May be null, in which case a client will be created with default credentials.</param>
/// <returns>A task representing the asynchronous operation. When complete, the result of the task is the new instance.</returns>
public static async Task<FirestoreDb> CreateAsync(string projectId = null, FirestoreClient client = null) =>
new FirestoreDb(
Create(
projectId ?? (await Platform.InstanceAsync().ConfigureAwait(false)).ProjectId,
DefaultDatabaseId,
client ?? await FirestoreClient.CreateAsync().ConfigureAwait(false),
Expand All @@ -117,27 +117,27 @@ private FirestoreDb(string projectId, string databaseId, FirestoreClient client,
/// <summary>
/// Creates an instance for the specified project and database, using the specified <see cref="FirestoreClient"/>
/// for RPC operations.
/// Note: this method should never be made public, as it is expected to grow as additional state is required in the client.
/// Additional parameters should be made optional, for source (but not binary) compatibility with tests.
/// This method does not perform any blocking operations, so may be used from async methods.
/// </summary>
/// <param name="projectId">The ID of the Google Cloud Platform project that contains the database. May be null, in which case
/// the project will be automatically detected if possible.</param>
/// <param name="projectId">The ID of the Google Cloud Platform project that contains the database. Must not be null.</param>
/// <param name="databaseId">The ID of the database within the project. May be null, in which case the default database will be used.</param>
/// <param name="client">The client to use for RPC operations. May be null, in which case a client will be created with default credentials.</param>
/// <param name="client">The client to use for RPC operations. Must not be null.</param>
/// <param name="warningLogger">The warning logger to use, if any. May be null.</param>
/// <returns>A new instance.</returns>
internal static FirestoreDb Create(string projectId, string databaseId, FirestoreClient client) =>
internal static FirestoreDb Create(string projectId, string databaseId, FirestoreClient client, Action<string> warningLogger = null) =>
// Validation is performed in the constructor.
new FirestoreDb(
projectId ?? Platform.Instance().ProjectId,
projectId,
databaseId ?? DefaultDatabaseId,
client ?? FirestoreClient.Create(),
null);
client,
warningLogger);

/// <summary>
/// Returns a new <see cref="FirestoreDb"/> with the same project, database and client as this one,
/// but the given writer for warning logs.
/// </summary>
/// <remarks>
/// This method is experimental, in lieu of a more sophisticated logging. It is likely to be removed before
/// this library becomes GA.
/// </remarks>
/// <param name="warningLogger">The logger for warnings. May be null.</param>
/// <returns>A new <see cref="FirestoreDb"/> based on this one, with the given warning logger.</returns>
public FirestoreDb WithWarningLogger(Action<string> warningLogger) =>
Expand Down
@@ -0,0 +1,89 @@
// Copyright 2019, Google LLC
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

using Google.Api.Gax;
using Google.Api.Gax.Grpc;
using Google.Cloud.Firestore.V1;
using System;
using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;

namespace Google.Cloud.Firestore
{
/// <summary>
/// Builder class for <see cref="FirestoreDb"/>, providing simple configuration of credentials,
/// endpoint, project ID etc.
/// </summary>
public sealed class FirestoreDbBuilder : ClientBuilderBase<FirestoreDb>
{
/// <summary>
/// The settings to use for RPCs, or null for the default settings.
/// </summary>
public FirestoreSettings Settings { get; set; }

/// <summary>
/// The ID of the Google Cloud Platform project that contains the database. May be null, in which case
/// the project will be automatically detected if possible.
/// </summary>
public string ProjectId { get; set; }

// Note: currently internal as only the default database is supported.
/// <summary>
/// The ID of the database within the project. May be null, in which case the default database will be used.
/// </summary>
internal string DatabaseId { get; set; }

/// <summary>
/// Action to receive warning messages. May be null, in which case warnings will be ignored.
/// </summary>
public Action<string> WarningLogger { get; set; }

/// <inheritdoc />
public override FirestoreDb Build()
{
var projectId = ProjectId ?? Platform.Instance().ProjectId;
var clientBuilder = FirestoreClientBuilder.FromOtherBuilder(this);
clientBuilder.Settings = Settings;
var client = clientBuilder.Build();
return BuildFromClient(projectId, client);
}

/// <inheritdoc />
public override async Task<FirestoreDb> BuildAsync(CancellationToken cancellationToken = default)
{
var projectId = ProjectId ?? (await Platform.InstanceAsync().ConfigureAwait(false)).ProjectId;
var clientBuilder = FirestoreClientBuilder.FromOtherBuilder(this);
clientBuilder.Settings = Settings;
var client = await clientBuilder.BuildAsync(cancellationToken).ConfigureAwait(false);
return BuildFromClient(projectId, client);
}

// We never end up using these methods, at least with the current implementation
/// <inheritdoc />
protected override ServiceEndpoint GetDefaultEndpoint() =>
throw new InvalidOperationException($"This method should never execute in {nameof(FirestoreDbBuilder)}");

/// <inheritdoc />
protected override IReadOnlyList<string> GetDefaultScopes() =>
throw new InvalidOperationException($"This method should never execute in {nameof(FirestoreDbBuilder)}");

/// <inheritdoc />
protected override ChannelPool GetChannelPool() =>
throw new InvalidOperationException($"This method should never execute in {nameof(FirestoreDbBuilder)}");

private FirestoreDb BuildFromClient(string projectId, FirestoreClient client) =>
FirestoreDb.Create(projectId, DatabaseId, client, WarningLogger);
}
}

0 comments on commit b917790

Please sign in to comment.