-
Notifications
You must be signed in to change notification settings - Fork 720
Add Cosmos playground code. #1697
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
@Pilchie your thoughts about this approach from a Cosmos perspective? |
| @@ -0,0 +1,15 @@ | |||
| <Project Sdk="Microsoft.NET.Sdk.Web"> | |||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think we should use the word "Playgrounds" and this shouldn't be under the src folder - which is for product code.
I'd rather call these "TestProjects", even if they are manual tests.
| /// </summary> | ||
| /// <returns>The connection string to use for this database.</returns> | ||
| public string? GetConnectionString() => ConnectionString; | ||
| public string? GetConnectionString() => ConnectionString ?? $"{Parent.GetConnectionString()}Database={Name};"; // HACK: Will go away when we get rid of Azure Provisioner package. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What exactly will go away? If we are going to get rid of the Azure Provisioner package, do we even need this hack now?
| { | ||
| builder.Services.AddSingleton(_ => ConfigureDb()); | ||
|
|
||
| var csBuilder = new DbConnectionStringBuilder(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
a) why are we only doing this for the non-EF component?
b) why are we only doing this for the non-Keyed DI path?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The PR is incomplete. I was just getting something working to facilitate conversation.
|
|
||
| if (csBuilder.TryGetValue("Database", out var databaseName)) | ||
| { | ||
| builder.Services.AddSingleton<Database>(sp => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This feels really weird to conditionally add a DI service based on the format used in the connection string.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The more I think about this, the more I don't think we should be doing this in the Aspire component. If someone wants to make a separate NuGet package to do these kinds of convenience operations, that would be OK, but Aspire is more geared towards "cloud" concerns like telemetry, config, health checks, etc. Its goal isn't trying to make the underlying libraries easier to use. It registers the root concept in DI and then gets out of the user's way and let's them decide how to structure their app.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this is fair. Ideally Cosmos libraries would do a lot of this for us.
| var csBuilder = new DbConnectionStringBuilder(); | ||
| csBuilder.ConnectionString = settings.ConnectionString; | ||
|
|
||
| if (csBuilder.TryGetValue("Database", out var databaseName)) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is "Database" a valid connection string property? I only see these 2:
Would CosmosDB start throwing if it finds unrecognized properties in the connection string?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I don't think it throws, it will ignore other properties...you can run a quick test to check it out.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not. Cosmos DB Connection String only has endpoint + Key. Users do not specify the DB on the connection string. Also, this would not work if the authentication method is AAD?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm actually going to propose that Cosmos start accepting a connection string with segments even for AAD auth. The reason is being able to encode extra options like this.
|
@eerhardt i should have marked this draft. It's not fully implemented the intention was to trigger a conversation about things we could do to make the Cosmos experience better. EF support is slightly different anyway because database creation can be triggered in EF along with container creation. Database= is not part of the standard Cosmos connection string. |
|
Also tagging @kirankumarkolli and @ealsur for thoughts. |
| builder.Services.AddSingleton<Database>(sp => | ||
| { | ||
| var client = sp.GetRequiredService<CosmosClient>(); | ||
| var database = client.CreateDatabaseIfNotExistsAsync(databaseName.ToString()).Result.Database; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| var database = client.CreateDatabaseIfNotExistsAsync(databaseName.ToString()).Result.Database; | |
| var database = client.GetDatabase(databaseName); |
Doing a blocking metadata async call sounds not ideal? Also, what if the app interacts with multiple databases?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If an app interacts with multiple databases, then the developer using Aspire would probably have wired up their app model a little differently and this code wouldn't kick in.
| builder.Services.AddKeyedSingleton<Container>("entries", (sp, _) => | ||
| { | ||
| var db = sp.GetRequiredService<Database>(); | ||
| var container = db.CreateContainerIfNotExistsAsync("entries", "/sessionId").Result.Container; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is there a way to avoid the blocking call?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is occurring in DI wire up which isn't async. It may be that we just don't do this database injection, and we leave all this to the developer, but my goal was to try and make it as seamless as possible. But perhaps this should be a Cosmos SDK responsibility as @eerhardt mentions above.
|
|
||
| if (csBuilder.TryGetValue("Database", out var databaseName)) | ||
| { | ||
| builder.Services.AddSingleton<Database>(sp => |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What if instead is a keyedSingleton that takes the DB Name? That way the caller can obtain the Database instance for the name they need?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Or a KeyedSingleton that takes dbName + containerName and return a Container from client.GetContainer().
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I like the idea, some thoughts:
- Customers don't interact with the Database, but the Container, we could have a KeyedSingleton that takes dbname + container and returns a Container instance from
return client.GetContainer(dbName, container). - Avoid CreateIfNotExists in registrations:
- They would be blocking calls
- They don't work if AAD is used for auth
- It consumes the account metadata RU
- Creating these resources is normally something that is done outside of the application scope, like creating a Database and Tables in SQL Server. They require cost planning and proper configuration. Auto-magically creating them for the customer might not be ideal.
| await container.CreateItemAsync(new Entry(Guid.NewGuid().ToString(), sessionId)).ConfigureAwait(false); | ||
|
|
||
| var entries = new List<Entry>(); | ||
| var iterator = container.GetItemQueryIterator<Entry>(requestOptions: new QueryRequestOptions() { MaxItemCount = 5 }); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@ealsur - will this use query serialization? If so, it's not going to use the S.T.J serializer until Azure/azure-cosmos-dotnet-v3#4138 ships and your serializer is updated with the new API
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Queries work well with Custom Serializer. What does not work well is LINQ Queries
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This call here is effectively a ReadFeed operation (a query without any query text)
src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/Program.cs
Outdated
Show resolved
Hide resolved
| { | ||
| // Default serializer for Cosmos V3 client is JSON.NET, this changes | ||
| // us to use S.T.J for this playground. | ||
| clientOptions.Serializer = new StjSerializer(new System.Text.Json.JsonSerializerOptions()); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Later follow-up: With next release of SDK there will be a new type for serialization to be implemented to handle LINQ queries.
| if (serviceKey is null) | ||
| { | ||
| builder.Services.AddSingleton(_ => ConfigureDb()); | ||
|
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
In line 104, please remove clientOptions.LimitToEndpoint = true; this should never be set in any general content, even in the case of the emulator, users might copy this and think this is ok.
I know this came from an official doc (I found the source) and I also corrected it there.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If I remove this, it seems to stop the code working. It just hangs.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It is not hanging, it probably is facing an HttpRequestException and retrying. This flag turns off the retries. HttpRequestException is normally related to some connectivity issue with the endpoint being passed. Was it localhost?
We only model down to the database in the Aspire App model, so registering databases is where I'd probably step off. In the playground sample in the PR you can see that i go a little further and register containers. But that is kind of a last mile thing that would be in end-developer code. |
src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/Program.cs
Outdated
Show resolved
Hide resolved
|
@davidfowl @eerhardt this is just playground code sans some of the ideas that I shared above. Want to get this in so I can use it with the AZD team to validate end to end scenarios. |
src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/Program.cs
Outdated
Show resolved
Hide resolved
src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/Program.cs
Outdated
Show resolved
Hide resolved
src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ApiService/Program.cs
Outdated
Show resolved
Hide resolved
src/Playgrounds/CosmosEndToEnd/CosmosEndToEnd.ServiceDefaults/Extensions.cs
Show resolved
Hide resolved
|
Restarting the build job. Looks like the agent got ripped out from under us. |
This PR contains an end to end Cosmos app which can be used to validate Cosmos scenarios whilst working in our repo. It creates databases (when using emulator), creates containers, and inserts and gets data.
@eerhardt wouldn't mind getting your thoughts on this. This PR does:
Database=x;value on the Cosmos connection string, and if it is present inject theDatabaseinstance that matches that value.One of the issues with the end to end scenario for Cosmos is that the CosmosClient isn't a client for a particular database, it is a client for the entire Cosmos account. Which means that even if someone does this in the app model:
The still need to provide some kind of configuration value so that they code knows what database to talk to. The change I made registers the database instance (incomplete, but enough to actually run and work). In the playground sample itself I also register the container via keyed DI. This means that developers only really need to reference the keyed container by name and then they can perform operations.
I think registering containers as part of the Aspire component is a bridge too far, but registering the database, if it is provided on the connection string would be really useful. It might also be useful to make it an option to determine whether or not to create the database dynamically as well.
Microsoft Reviewers: Open in CodeFlow