Skip to content

Commit e9bde2d

Browse files
authored
Merge pull request #62 from atc-net/feature/delete-partition
Add Preview feature for deleting resources by partition key
2 parents db17960 + fadebf4 commit e9bde2d

File tree

10 files changed

+246
-20
lines changed

10 files changed

+246
-20
lines changed

README.md

Lines changed: 66 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -38,14 +38,6 @@ Once the library is added to your project, you will have access to the following
3838
* [`ICosmosBulkReader<T>`](src/Atc.Cosmos/ICosmosBulkReader.cs)
3939
* [`ICosmosBulkWriter<T>`](src/Atc.Cosmos/ICosmosBulkWriter.cs)
4040

41-
When using the preview version, you will have access to the following interfaces, used for reading and writing Cosmos document resources:
42-
* [`ILowPriorityCosmosReader<T>`](src/Atc.Cosmos/ILowPriorityCosmosReader.cs)
43-
* [`ILowPriorityCosmosWriter<T>`](src/Atc.Cosmos/ILowPriorityCosmosWriter.cs)
44-
* [`ILowPriorityCosmosBulkReader<T>`](src/Atc.Cosmos/ILowPriorityCosmosBulkReader.cs)
45-
* [`ILowPriorityCosmosBulkWriter<T>`](src/Atc.Cosmos/ILowPriorityCosmosBulkWriter.cs)
46-
47-
The interfaces that are prefixed with `ILowPriority` require priority-based execution to be enabled on the CosmosDB account. Priority-based execution is currently is not enabled by default and to get started using it you need to fill out this [nomination form](https://forms.microsoft.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbR_kUn4g8ufhFjXbbwUF1gXFUMUQzUzFZSVkzODRSRkxXM0RKVDNUSDBGNi4u). After submitting, a member of the CosmosDb team will reach out and enable the feature on the accounts you listed and contact you to let you know it’s ready for use.
48-
4941
A document resource is represented by a class deriving from the [`CosmosResource`](src/Atc.Cosmos/CosmosResource.cs) base-class, or by implementing the underlying [`ICosmosResource`](src/Atc.Cosmos/ICosmosResource.cs) interface directly.
5042

5143
To configure where each resource will be stored in Cosmos, the `ConfigureCosmos(builder)` extension method is used on the `IServiceCollection` when setting up dependency injection (usually in a `Startup.cs` file).
@@ -185,15 +177,6 @@ The registered interfaces are:
185177
|[`ICosmosBulkReader<T>`](src/Atc.Cosmos/ICosmosBulkReader.cs)| Represents a reader that can perform bulk reads on Cosmos resources. |
186178
|[`ICosmosBulkWriter<T>`](src/Atc.Cosmos/ICosmosBulkWriter.cs)| Represents a writer that can perform bulk operations on Cosmos resources. |
187179

188-
For the preview version, the registered interfaces also include:
189-
190-
|Name|Description|
191-
|-|-|
192-
|[`ILowPriorityCosmosReader<T>`](src/Atc.Cosmos/ILowPriorityCosmosReader.cs)| Represents a reader that can read Cosmos resources with low priority. |
193-
|[`ILowPriorityCosmosWriter<T>`](src/Atc.Cosmos/ILowPriorityCosmosWriter.cs)| Represents a writer that can write Cosmos resources with low priority. |
194-
|[`ILowPriorityCosmosBulkReader<T>`](src/Atc.Cosmos/ILowPriorityCosmosBulkReader.cs)| Represents a reader that can perform bulk reads on Cosmos resources with low priority. |
195-
|[`ILowPriorityCosmosBulkWriter<T>`](src/Atc.Cosmos/ILowPriorityCosmosBulkWriter.cs)| Represents a writer that can perform bulk operations on Cosmos resources with low priority. |
196-
197180
The bulk reader and writer are for optimizing performance when executing many operations towards Cosmos. It works by creating all the tasks and then use the `Task.WhenAll()` to await them. This will group operations by partition key and send them in batches of 100.
198181

199182
When not operating with bulks, the normal readers are faster as there is no delay waiting for more work.
@@ -235,7 +218,72 @@ To do this you will need to:
235218

236219
*Note: The change feed processor relies on a HostedService, which means that this feature is only available in AspNet Core services.*
237220

238-
### Unit Testing
221+
## Preview Features
222+
223+
The library also has a preview version that exposes some of CosmosDB preview features.
224+
225+
### Priority Based Execution
226+
227+
When using the preview version, you will have access to the following interfaces, used for reading and writing Cosmos document resources:
228+
229+
|Name|Description|
230+
|-|-|
231+
|[`ILowPriorityCosmosReader<T>`](src/Atc.Cosmos/ILowPriorityCosmosReader.cs)| Represents a reader that can read Cosmos resources with low priority. |
232+
|[`ILowPriorityCosmosWriter<T>`](src/Atc.Cosmos/ILowPriorityCosmosWriter.cs)| Represents a writer that can write Cosmos resources with low priority. |
233+
|[`ILowPriorityCosmosBulkReader<T>`](src/Atc.Cosmos/ILowPriorityCosmosBulkReader.cs)| Represents a reader that can perform bulk reads on Cosmos resources with low priority. |
234+
|[`ILowPriorityCosmosBulkWriter<T>`](src/Atc.Cosmos/ILowPriorityCosmosBulkWriter.cs)| Represents a writer that can perform bulk operations on Cosmos resources with low priority. |
235+
236+
In order to use these interfaces the "Priority Based Execution" feature needs to be enabled on the CosmosDB account.
237+
238+
This can be done by either enabling it directly in Azure Portal under Settings -> Features tab on the CosmosDB resource.
239+
240+
Alternatively through Azure CLI:
241+
242+
```bash
243+
# install cosmosdb-preview Azure CLI extension
244+
az extension add --name cosmosdb-preview
245+
246+
# Enable priority-based execution
247+
az cosmosdb update --resource-group $ResourceGroup --name $AccountName --enable-priority-based-execution true
248+
```
249+
250+
See [MS Learn](https://learn.microsoft.com/en-us/azure/cosmos-db/priority-based-execution) for more details.
251+
252+
### Delete resources by partition key
253+
254+
The preview version of the library extends the `ICosmosWriter` and `ILowPriorityCosmosWriter` with and additional method `DeletePartitionAsync` to delete all resources in a container based on a partition key. The deletion will be executed in a CosmosDB background service using a percentage of the RU's available. The effect are available immediatly as all resources in the partition will not be available through reads or queries.
255+
256+
In order to use this new method the "Delete All Items By Partition Key" feature needs to be enabled on the CosmosDB account.
257+
258+
This can be done through Azure CLI:
259+
260+
```bash
261+
# Delete All Items By Partition Key
262+
az cosmosdb update --resource-group $ResourceGroup --name $AccountName --capabilities DeleteAllItemsByPartitionKey
263+
```
264+
265+
or wih bicep:
266+
267+
```bicep
268+
resource cosmosAccount 'Microsoft.DocumentDB/databaseAccounts@2024-05-15' = {
269+
name: cosmosName
270+
properties: {
271+
databaseAccountOfferType: 'Standard'
272+
locations: location
273+
capabilities: [
274+
{
275+
name: 'DeleteAllItemsByPartitionKey'
276+
}
277+
]
278+
}
279+
}
280+
```
281+
282+
If the feature is not enabled when calling this method then a `CosmosException` will be thrown.
283+
284+
See [MS Learn](https://learn.microsoft.com/en-us/azure/cosmos-db/nosql/how-to-delete-by-partition-key) for more details.
285+
286+
## Unit Testing
239287
The reader and writer interfaces can easily be mocked, but in some cases it is nice to have a fake version of a reader or writer to mimic the behavior of the read and write operations. For this purpose the `Atc.Cosmos.Testing` namespace contains the following fakes:
240288

241289
|Name|Description|

src/Atc.Cosmos/Atc.Cosmos.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -13,8 +13,8 @@
1313
<PackageReference Include="Microsoft.Extensions.Hosting.Abstractions" Version="8.0.0" />
1414
<PackageReference Include="Microsoft.Extensions.Options" Version="8.0.2" />
1515
<PackageReference Include="System.Text.Json" Version="8.0.5" />
16-
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.43.0-preview.0" Condition="$(IsPreview)" />
17-
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.43.1" Condition="!$(IsPreview)" />
16+
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.45.0-preview.0" Condition="$(IsPreview)" />
17+
<PackageReference Include="Microsoft.Azure.Cosmos" Version="3.44.1" Condition="!$(IsPreview)" />
1818
</ItemGroup>
1919

2020
<ItemGroup Condition="$(TargetFramework) == 'netstandard2.0'">

src/Atc.Cosmos/ICosmosWriter.cs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,6 +149,26 @@ public Task<bool> TryDeleteAsync(
149149
string documentId,
150150
string partitionKey,
151151
CancellationToken cancellationToken = default);
152+
#if PREVIEW
153+
154+
/// <summary>
155+
/// Preview Feature DeleteAllItemsByPartitionKey.<br/>
156+
/// Deletes all resources in the Container with the specified <see cref="PartitionKey"/>.
157+
/// Starts an asynchronous Cosmos DB background operation which deletes all resources in the Container with the specified value.
158+
/// The asynchronous Cosmos DB background operation runs using a percentage of user RUs.
159+
/// </summary>
160+
/// <remarks>
161+
/// A <see cref="CosmosException"/>
162+
/// with StatusCode <see cref="HttpStatusCode.BadRequest"/>
163+
/// will be thrown if the DeleteAllItemsByPartitionKey feature is not enabled.
164+
/// </remarks>
165+
/// <param name="partitionKey">Partition key of the resource.</param>
166+
/// <param name="cancellationToken">The <see cref="CancellationToken"/> used.</param>
167+
/// <returns>A <see cref="Task"/> representing the asynchronous operation.</returns>
168+
public Task DeletePartitionAsync(
169+
string partitionKey,
170+
CancellationToken cancellationToken = default);
171+
#endif
152172

153173
/// <summary>
154174
/// Updates a <typeparamref name="T"/> resource that is read from the configured

src/Atc.Cosmos/Internal/CosmosWriter.cs

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,21 @@ await container
175175

176176
return true;
177177
}
178+
#if PREVIEW
179+
180+
public Task DeletePartitionAsync(
181+
string partitionKey,
182+
CancellationToken cancellationToken = default)
183+
=> container
184+
.DeleteAllItemsByPartitionKeyStreamAsync(
185+
new PartitionKey(partitionKey),
186+
new ItemRequestOptions
187+
{
188+
PriorityLevel = PriorityLevel,
189+
},
190+
cancellationToken: cancellationToken)
191+
.ProcessResponseMessage();
192+
#endif
178193

179194
public Task<T> UpdateAsync(
180195
string documentId,
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
using System.Threading.Tasks;
2+
using Microsoft.Azure.Cosmos;
3+
4+
namespace Atc.Cosmos.Internal
5+
{
6+
public static class ResponseMessageExtensions
7+
{
8+
public static async Task ProcessResponseMessage(this Task<ResponseMessage> responseMessage)
9+
{
10+
using ResponseMessage message = await responseMessage.ConfigureAwait(false);
11+
message.EnsureSuccessStatusCode();
12+
}
13+
}
14+
}

src/Atc.Cosmos/Testing/FakeCosmos.cs

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -481,6 +481,16 @@ Task<bool> ICosmosWriter<T>.TryDeleteAsync(
481481
documentId,
482482
partitionKey,
483483
cancellationToken);
484+
#if PREVIEW
485+
486+
Task ICosmosWriter<T>.DeletePartitionAsync(
487+
string partitionKey,
488+
CancellationToken cancellationToken)
489+
=> ((ICosmosWriter<T>)Writer)
490+
.DeletePartitionAsync(
491+
partitionKey,
492+
cancellationToken);
493+
#endif
484494

485495
Task<T> ICosmosWriter<T>.UpdateAsync(
486496
string documentId,

src/Atc.Cosmos/Testing/FakeCosmosWriter.cs

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,18 @@ await DeleteAsync(
153153

154154
return true;
155155
}
156+
#if PREVIEW
157+
158+
public virtual Task DeletePartitionAsync(
159+
string partitionKey,
160+
CancellationToken cancellationToken = default)
161+
{
162+
Documents.RemoveAll(d
163+
=> d.PartitionKey == partitionKey);
164+
165+
return Task.CompletedTask;
166+
}
167+
#endif
156168

157169
public virtual Task<T> UpdateAsync(
158170
string documentId,

test/Atc.Cosmos.Tests/CosmosWriterTests.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,14 @@ public CosmosWriterTests()
4949
container
5050
.PatchItemAsync<object>(default, default, default, default)
5151
.ReturnsForAnyArgs(response);
52+
#if PREVIEW
53+
54+
var responseMessage = Substitute.For<ResponseMessage>();
55+
responseMessage.StatusCode.Returns(HttpStatusCode.Accepted);
56+
container
57+
.DeleteAllItemsByPartitionKeyStreamAsync(default, default, default)
58+
.ReturnsForAnyArgs(responseMessage);
59+
#endif
5260

5361
reader = Substitute.For<ICosmosReader<Record>>();
5462
reader
@@ -288,6 +296,34 @@ public async Task Should_Return_False_When_Trying_To_Delete_NonExisting_Resource
288296
#endif
289297
cancellationToken: cancellationToken);
290298
}
299+
#if PREVIEW
300+
301+
[Theory, AutoNSubstituteData]
302+
public async Task DeletePartitionAsync_Calls_DeleteAllItemsByPartitionKeyStreamAsync_On_Container(
303+
CancellationToken cancellationToken)
304+
{
305+
await sut.DeletePartitionAsync(record.Pk, cancellationToken);
306+
_ = container
307+
.Received(1)
308+
.DeleteAllItemsByPartitionKeyStreamAsync(
309+
new PartitionKey(record.Pk),
310+
Arg.Is<ItemRequestOptions>(o => o.PriorityLevel == PriorityLevel.High),
311+
cancellationToken: cancellationToken);
312+
}
313+
314+
[Theory, AutoNSubstituteData]
315+
public Task DeletePartitionAsync_Throws_CosmosException_If_ResponseMessage_Is_Not_Sucessful(
316+
CancellationToken cancellationToken)
317+
{
318+
using var responseMessage = new ResponseMessage(HttpStatusCode.BadRequest);
319+
container
320+
.DeleteAllItemsByPartitionKeyStreamAsync(default, default, default)
321+
.ReturnsForAnyArgs(responseMessage);
322+
323+
Func<Task> act = () => sut.DeletePartitionAsync(record.Pk, cancellationToken);
324+
return act.Should().ThrowAsync<CosmosException>();
325+
}
326+
#endif
291327

292328
[Theory, AutoNSubstituteData]
293329
public async Task UpdateAsync_Reads_The_Resource(

test/Atc.Cosmos.Tests/LowPriorityCosmosWriterTests.cs

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,12 @@ public LowPriorityCosmosWriterTests()
5151
.PatchItemAsync<object>(default, default, default, default)
5252
.ReturnsForAnyArgs(response);
5353

54+
var responseMessage = Substitute.For<ResponseMessage>();
55+
responseMessage.StatusCode.Returns(HttpStatusCode.Accepted);
56+
container
57+
.DeleteAllItemsByPartitionKeyStreamAsync(default, default, default)
58+
.ReturnsForAnyArgs(responseMessage);
59+
5460
reader = Substitute.For<ILowPriorityCosmosReader<Record>>();
5561
reader
5662
.ReadAsync(default, default, default)
@@ -257,6 +263,32 @@ public async Task Should_Return_False_When_Trying_To_Delete_NonExisting_Resource
257263
cancellationToken: cancellationToken);
258264
}
259265

266+
[Theory, AutoNSubstituteData]
267+
public async Task DeletePartitionAsync_Calls_DeleteAllItemsByPartitionKeyStreamAsync_On_Container(
268+
CancellationToken cancellationToken)
269+
{
270+
await sut.DeletePartitionAsync(record.Pk, cancellationToken);
271+
_ = container
272+
.Received(1)
273+
.DeleteAllItemsByPartitionKeyStreamAsync(
274+
new PartitionKey(record.Pk),
275+
Arg.Is<ItemRequestOptions>(o => o.PriorityLevel == PriorityLevel.Low),
276+
cancellationToken: cancellationToken);
277+
}
278+
279+
[Theory, AutoNSubstituteData]
280+
public Task DeletePartitionAsync_Throws_CosmosException_If_ResponseMessage_Is_Not_Sucessful(
281+
CancellationToken cancellationToken)
282+
{
283+
using var responseMessage = new ResponseMessage(HttpStatusCode.BadRequest);
284+
container
285+
.DeleteAllItemsByPartitionKeyStreamAsync(default, default, default)
286+
.ReturnsForAnyArgs(responseMessage);
287+
288+
Func<Task> act = () => sut.DeletePartitionAsync(record.Pk, cancellationToken);
289+
return act.Should().ThrowAsync<CosmosException>();
290+
}
291+
260292
[Theory, AutoNSubstituteData]
261293
public async Task UpdateAsync_Reads_The_Resource(
262294
string documentId,

test/Atc.Cosmos.Tests/Testing/FakeCosmosWriterTests.cs

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,45 @@ public async Task DeleteAsync_Should_Replace_Existing_Document(
211211
.Should()
212212
.NotContain(existingDocument);
213213
}
214+
#if PREVIEW
215+
216+
[Theory, AutoNSubstituteData]
217+
public async Task DeletePartitionAsyncAsync_Should_Delete_Existing_Documents(
218+
FakeCosmosWriter<Record> sut,
219+
Record record1,
220+
Record record2,
221+
Record record3)
222+
{
223+
var existingDocument1 = new Record
224+
{
225+
Id = record1.Id,
226+
Pk = record1.Pk,
227+
};
228+
sut.Documents.Add(existingDocument1);
229+
var existingDocument2 = new Record
230+
{
231+
Id = record2.Id,
232+
Pk = record1.Pk,
233+
};
234+
sut.Documents.Add(existingDocument2);
235+
var existingDocument3 = new Record
236+
{
237+
Id = record3.Id,
238+
Pk = record3.Pk,
239+
};
240+
sut.Documents.Add(existingDocument3);
241+
242+
await sut.DeletePartitionAsync(record1.Pk);
243+
244+
sut.Documents
245+
.Should()
246+
.NotContain(existingDocument1)
247+
.And
248+
.NotContain(existingDocument2)
249+
.And
250+
.Contain(existingDocument3);
251+
}
252+
#endif
214253

215254
[Theory, AutoNSubstituteData]
216255
public void UpdateAsync_Should_Throw_If_Document_Does_Not_Exists(

0 commit comments

Comments
 (0)