This repository has been archived by the owner on Jul 12, 2020. It is now read-only.
-
-
Notifications
You must be signed in to change notification settings - Fork 44
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #52 from Elfocrash/develop
Merge version 2.8.1 to master
- Loading branch information
Showing
31 changed files
with
878 additions
and
302 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
# Collection sharing | ||
|
||
## What is collection sharing? | ||
|
||
When development on Cosmonaut started there was no option to provision RUs on the database level. Later this feature came in and it has a 50k RUs minimum. It was later reduced to 10k and now it's on 400 RUs for the whole database. That's fine and all but having collection level throughput is still to me the best way to go. It limits the scalability to a single collection. | ||
|
||
Collection sharing is the consept of having multiple different types of objects sharing the same collection while Cosmonaut is able to operate on them as if they were in completely different containers. | ||
|
||
The benefit of such a feature is that you don't need a single collection per entity type. You can simply have them sharing. If you are also good with your partitioning strategy you will be able to have multiple shared collections with different partition key definitions that make sense and provide optimal read and write performance. | ||
|
||
## How can I use Collection sharing? | ||
|
||
In order to enable collection sharing you all you have to do is have your POCO implement the `ISharedCosmosEntity` interface and decorate the class with the `SharedCosmosCollectionAttribute`. | ||
|
||
An example of an object that is hosted in a shared collection looks like this: | ||
|
||
```c# | ||
[SharedCosmosCollection("shared", "somebooks")] | ||
public class Book : ISharedCosmosEntity | ||
{ | ||
[JsonProperty("id")] | ||
public string Id { get; set; } | ||
|
||
[CosmosPartitionKey] | ||
public string Name { get; set; } | ||
|
||
public string CosmosEntityName { get; set; } | ||
} | ||
``` | ||
|
||
The first parameter at the `SharedCosmosCollection` attribute with value `shared` represents the shared collection name that this object will use. This is the only mandatory parameter for this attribute. The second one with value `somebooks` represents the value that `CosmosEntityName` will automatically be populated with. If you don't set this value then a lowercase pluralised version of the class name will be used instead. | ||
|
||
> Note: You do NOT need to set the `CosmosEntityName` value yourself. Leave it as it is and Cosmonaut will do the rest for you. | ||
Even though this is convinient I understand that you might need to have a more dynamic way of specifying the collection that this object should use. That's why the `CosmosStore` class has some extra constructors that allow you to specify the `overriddenCollectionName` property. This property will override any collection name specified at the attribute level and will use that one instead. | ||
|
||
> Note: If you have specified a CollectionPrefix at the CosmosStoreSettings level it will still be added. You are only overriding the collection name that the attribute would normally set. | ||
If you want your shared collection to be partitioned then make sure than the partition key definition is the same in all the objects that are hosted in this collection. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
# The CosmonautClient | ||
|
||
### What is it and why do I care? | ||
|
||
The CosmonautClient is a wrapper around the DocumentClient that comes from the CosmosDB SDK. It's main purpose is to abstract some of the things you really don't need to know about when it comes to using Cosmos DB. An example would be the UriFactory class. | ||
|
||
Normally the DocumentClient call requires you to provide a Uri to resources in order to perform operations. You shouldn't care. All you need to care about is that this call needs a `databaseId` and a `collectionId`. This client wrapper does that. | ||
|
||
It also wraps the calls to Cosmos and it profiles them in order to provide performance metrics. This will only happen when you have an active event source. You can learn more about this in the Logging section. | ||
|
||
Something worth noting is that the CosmonautClient won't throw an exception for not found documents on methods that the response is `ResourceResponse` but instead it will return null in order to make response handling easier. | ||
|
||
Any method that returns a `CosmosResponse` will still obay all the rules described on the "CosmosResponse and response handling" section of the "The CosmosStore" page. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
# Dependency Injection | ||
|
||
Cosmonaut also has a separate package that adds extensions on top of the .NET Standard Dependency injection framework. | ||
|
||
Nuget package: [Cosmonaut.Extensions.Microsoft.DependencyInjection](https://www.nuget.org/packages/Cosmonaut.Extensions.Microsoft.DependencyInjection/) | ||
|
||
Installing this package will add a set of methods for `IServiceCollection` called `AddCosmosStore`. | ||
|
||
```c# | ||
var cosmosSettings = new CosmosStoreSettings("<<databaseName>>", "<<cosmosUri>>", "<<authkey>>"); | ||
|
||
serviceCollection.AddCosmosStore<Book>(cosmosSettings); | ||
|
||
// or override the collection name | ||
serviceCollection.AddCosmosStore<Book>(cosmosSettings, "myCollection"); | ||
|
||
//or just by using the Action extension | ||
serviceCollection.AddCosmosStore<Book>("<<databaseName>>", "<<cosmosUri>>", "<<authkey>>", settings => | ||
{ | ||
settings.ConnectionPolicy = connectionPolicy; | ||
settings.DefaultCollectionThroughput = 5000; | ||
settings.IndexingPolicy = new IndexingPolicy(new RangeIndex(DataType.Number, -1), | ||
new RangeIndex(DataType.String, -1)); | ||
}); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
# Working with Entities using Cosmonaut | ||
|
||
|
||
## Adding an entity in the CosmosStore | ||
```csharp | ||
var newUser = new User | ||
{ | ||
Name = "Nick" | ||
}; | ||
var oneAdded = await cosmoStore.AddAsync(newUser); | ||
|
||
var multipleAdded = await cosmoStore.AddRangeAsync(manyManyUsers); | ||
``` | ||
|
||
Using the Range operation allows you to also provide a `Func` that creates a `RequestOptions` for every individual execution that takes place in the operation | ||
|
||
## Updating and Upserting entities | ||
When it comes to updating you have two options. | ||
|
||
Update example | ||
```c# | ||
var response = await cosmoStore.UpdateAsync(entity); | ||
``` | ||
|
||
Upsert example | ||
```c# | ||
var response = await cosmoStore.UpsertAsync(entity); | ||
``` | ||
|
||
The main difference is of course in the functionality. | ||
Update will only update if the item you are updating exists in the database with this id. | ||
Upsert on the other hand will either add the item if there is no item with this id or update it if an item with this id exists. | ||
|
||
There are also `Range` variation of both of these methods. | ||
|
||
Using one of the Range operations allows you to also provide a `Func` that creates a `RequestOptions` for every individual execution that takes place in the operation. This might be something the Etag in order to ensure that you are updatng the latest version of the document. | ||
|
||
Example of an `UpdateRangeAsync` execution that ensures that the latest version of the document is being updated: | ||
|
||
```c# | ||
var updated = await booksStore.UpdateRangeAsync(objectsToUpdate, x => new RequestOptions { AccessCondition = new AccessCondition | ||
{ | ||
Type = AccessConditionType.IfMatch, | ||
Condition = x.Etag | ||
}}); | ||
``` | ||
|
||
## Removing entities | ||
|
||
There are multiple ways to remove an entity in Cosmonaut. | ||
|
||
The simplest one is to use any of the overloads of the `RemoveByIdAsync` methods. | ||
|
||
```c# | ||
var removedWithId = await cosmoStore.RemoveByIdAsync("documentId"); | ||
var removedWithIdAndPartitionKey = await cosmoStore.RemoveByIdAsync("documentId", "partitionKeyValue"); | ||
``` | ||
|
||
There is also the `RemoveAsync` method which uses an entity object to do the removal. However this object needs to have the id property populated and if it's a partitioned, it should also have the partition key value populated. | ||
|
||
```c# | ||
var removedEntity = await cosmosStore.RemoveAsync(entity); | ||
``` | ||
|
||
Last but not least you can use the `RemoveAsync` method that has a predicate in it's signature. This will match all the documents that satistfy the predicate and remove them. You have to keep in mind that this method is doing a cross partition query behind the scenes before it does a direct delete per document. It's not very efficient and it should be used only in rare cases. | ||
|
||
```c# | ||
var deleted = await cosmoStore.RemoveAsync(x => x.Name == "Nick"); | ||
``` | ||
|
||
You can specify the `FeedOptions` for the query that takes place, potentially providing a partition key value to limit the scope of the request. | ||
|
||
You to also provide a `Func` that creates a `RequestOptions` for every individual execution that takes place in the operation. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Logging | ||
|
||
## Event source | ||
|
||
Cosmonaut uses the .NET Standard's `System.Diagnostics` to log it's actions as dependency events. | ||
|
||
By default, this system is deactivated. In order to activated and actually do something with those events you need to create an `EventListener` which will activate the logging and give you the option do something with the logs. | ||
|
||
## Cosmonaut.ApplicationInsights | ||
|
||
By using this package you are able to log the events as dependencies in [Application Insights](https://azure.microsoft.com/en-gb/services/application-insights/) in detail. The logs are batched and send in intervals OR automatically sent when the batch buffer is filled to max. | ||
|
||
Just initialise the AppInsightsTelemetryModule in your Startup or setup pipeline like this. | ||
Example: | ||
|
||
```c# | ||
AppInsightsTelemetryModule.Instance.Initialize(new TelemetryConfiguration("InstrumentationKey")); | ||
``` | ||
|
||
If you already have initialised `TelemetryConfiguration` for your application then use `TelemetryConfiguration.Active` instead of `new TelemetryConfiguration` because if you don't there will be no association between the dependency calls and the parent request. | ||
|
||
|
||
```c# | ||
AppInsightsTelemetryModule.Instance.Initialize(new TelemetryConfiguration(TelemetryConfiguration.Active)); | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
# Pagination | ||
|
||
Cosmonaut supports two types of pagination. | ||
|
||
* Page number + Page size | ||
* ContinuationToken + Page size | ||
|
||
Both of there methods work by adding the `.WithPagination()` method after you used any of the `Query` methods. | ||
|
||
```csharp | ||
var firstPage = await booksStore.Query().WithPagination(1, 10).OrderBy(x=>x.Name).ToListAsync(); | ||
var secondPage = await booksStore.Query().WithPagination(2, 10).OrderBy(x => x.Name).ToPagedListAsync(); | ||
var thirdPage = await booksStore.Query().WithPagination(secondPage.NextPageToken, 10).OrderBy(x => x.Name).ToPagedListAsync(); | ||
var fourthPage = await thirdPage.GetNextPageAsync(); | ||
var fifthPage = await booksStore.Query().WithPagination(5, 10).OrderBy(x => x.Name).ToListAsync(); | ||
``` | ||
|
||
`ToListAsync()` on a paged query will just return the results. `ToPagedListAsync()` on the other hand will return a `CosmosPagedResults` object. This object contains the results but also a boolean indicating whether there are more pages after the one you just got but also the continuation token you need to use to get the next page. | ||
|
||
## Pagination recommendations | ||
|
||
Because page number + page size pagination goes though all the documents until it gets to the requested page, it's potentially slow and expensive. | ||
The recommended approach would be to use the page number + page size approach once for the first page and get the results using the `.ToPagedListAsync()` method. This method will return the next continuation token and it will also tell you if there are more pages for this query. Then use the continuation token alternative of `WithPagination` to continue from your last query. | ||
|
||
Keep in mind that this approach means that you have to keep state on the client for the next query, but that's what you'd do if you where using previous/next buttons anyway. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Preparing your classes for the CosmosStore | ||
|
||
There are some things you need to get familiar with when it comes to using Cosmonaut. You see, Cosmonaut it doing a lot of things for you behind the scenes but it's performing even better if you do just a little bit of configuration. | ||
|
||
## Key attributes | ||
|
||
### [CosmosCollection] | ||
|
||
The `CosmosCollection` attribute is an optional attribute that you can decorate your entity's class with. It has two purposes. | ||
|
||
First it allows you to override the default Cosmonaut collection naming bahaviour, which is to name your collections as a lowercase plularised version of the class name. | ||
|
||
Second it allows you to map your class to a pre existing collection with a different name. | ||
|
||
> You can of course do a further override of the `CosmosStore` target name at the `CosmosStore` constructor level by providing the `overriddenCollectionName` parameter. | ||
### [SharedCosmosCollection] | ||
|
||
In order to enable collection sharing you all you have to do is have your POCO implement the `ISharedCosmosEntity` interface and decorate the class with the `SharedCosmosCollectionAttribute`. | ||
|
||
This attribute has one mandatory and one optional parameter namely the `SharedCollectionName` and the `EntityName`. | ||
|
||
```c# | ||
[SharedCosmosCollection("shared", "somebooks")] | ||
``` | ||
|
||
The first parameter at the `SharedCosmosCollection` attribute with value `shared` represents the shared collection name that this object will use. This is the only mandatory parameter for this attribute. The second one with value `somebooks` represents the value that `CosmosEntityName` will automatically be populated with. If you don't set this value then a lowercase pluralised version of the class name will be used instead. | ||
|
||
> You can of course do a further override of the `CosmosStore` target name at the `CosmosStore` constructor level by providing the `overriddenCollectionName` parameter. | ||
### [CosmosPartitionKey] | ||
|
||
This is a parameterless attribute. It is used to decorate the property that represents the partition key definition of your collection. | ||
|
||
```c# | ||
public class Book | ||
{ | ||
[CosmosPartitionKey] | ||
public string Name { get; set; } | ||
|
||
[JsonProperty("id")] | ||
public string Id { get; set; } | ||
} | ||
``` | ||
|
||
In this case I have a partition key definition in my collection named `/Name`. | ||
|
||
By decorating the `Name` property with the `CosmosPartitionKey` attribute I enable Cosmonaut to do a lot of behind the scenes operation optimisation where it will set the partition key value for you if present, speeding up the operation and making it more cost efficient. | ||
|
||
### [JsonProperty("id")] | ||
|
||
This is not a Cosmonaut attribute and it is coming from JSON.NET which the underlying Cosmos DB SDK is using. | ||
|
||
Even though not required I strongly recommend that you decorate your property which represents the id of the entity with the `[JsonProperty("id")]` attribute. This will prevent any unwanted behaviour with LINQ to SQL conversions and the id property not being mapped propertly. |
Oops, something went wrong.