# Cosmos DB Persistence Usage with Encryption
This sample shows how to use [Azure Cosmos DB client-side encryption](https://learn.microsoft.com/azure/cosmos-db/how-to-always-encrypted) to encrypt selected saga properties before they leave the process. The sample uses a client/server scenario in which the server stores saga data with Cosmos DB Persistence.
## Prerequisites
Ensure that an instance of the latest [Azure Cosmos DB Emulator](https://learn.microsoft.com/en-us/azure/cosmos-db/local-emulator) is running.
## Sample structure
This sample contains three projects, `SharedMessages`, `Client` and `Server`.
### SharedMessages
The shared message contracts used by all endpoints.
### Client
* Sends the `StartOrder` message to `Server`.
* Receives and handles the `OrderCompleted` event.
### Server
* Receives the `StartOrder` message and initiates an `OrderSaga`.
* `OrderSaga` requests a timeout with an instance of `CompleteOrder` with the saga data.
* `OrderSaga` publishes an `OrderCompleted` event when the `CompleteOrder` timeout fires.
## Running the sample
1. Start the `Server` project and wait for the endpoint to report that it has started.
1. Start the `Client` project.
1. Press S in the client window to start an order.
1. After the saga timeout expires, verify that the server reports the completed saga and the client receives the `OrderCompleted` event.
## Implementation highlights
### Persistence config
In Program.cs of the Server project, the endpoint is configured to use Cosmos DB Persistence:
```cs
var endpointConfiguration = new EndpointConfiguration("Samples.CosmosDB.Encryption.Server");
var persistence = endpointConfiguration.UsePersistence();
persistence.DatabaseName("Samples.CosmosDB.Encryption");
// NServiceBus must use the encryption-enabled client so saga reads and writes apply the encryption policy.
persistence.CosmosClient(cosmosClient);
persistence.DefaultContainer("Server", "/id");
// The container is created explicitly with its encryption policy, so persistence must not create it.
persistence.DisableContainerCreation();
```
In the non-transactional mode, the saga id is used as a partition key, and, therefore, the container needs to use `/id` as the partition key path.
### Encryption setup
The server wraps the regular `CosmosClient` with the Cosmos DB encryption client before passing it to NServiceBus. It then creates a client encryption key and a container with an encryption policy.
```cs
var containerProperties = new ContainerProperties(ContainerName, PartitionKeyPath)
{
DefaultTimeToLive = -1,
ClientEncryptionPolicy = new ClientEncryptionPolicy(
[
// NServiceBus derives the saga ID from the correlation value and performs a point read,
// so the correlated property can use randomized encryption.
Encrypted("/OrderId"),
Encrypted("/OrderDescription"),
// Policy format version 2 allows encrypting id and partition key
// but requires deterministic encryption for these properties.
EncryptedDeterministic("/id"),
EncryptedDeterministic("/PartitionKey")
// Deliberately NOT encrypted:
// /_NServiceBus-Persistence-Metadata - pessimistic locking patches a path INSIDE
// this object (SagaDataContainer-ReservedUntil).
// Only top-level paths can be encrypted, so
// including it would encrypt the whole subtree
// and break the lock.
],
policyFormatVersion: 2)
};
```
The policy uses [encryption policy format version 2](https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-always-encrypted#choosing-a-client-encryption-policy), which allows all saga data properties to be encrypted. `OrderId` and `OrderDescription` use randomized encryption, while `id` and `PartitionKey` use [deterministic encryption](https://learn.microsoft.com/en-us/azure/cosmos-db/how-to-always-encrypted#randomized-vs-deterministic-encryption), because Cosmos DB requires these values for point reads and routing. The NServiceBus persistence metadata remains unencrypted, because pessimistic locking updates a nested property within that object.
The key encryption key resolver stores a generated RSA private key under the server output directory. This is suitable only for demonstrating the encryption flow; production systems should use a secure key store, such as Azure Key Vault, and must retain the key for as long as encrypted data needs to be read.
> [!NOTE]
> The server deletes and recreates the `Samples.CosmosDB.Encryption` database each time it starts so that the sample always uses the expected encryption key and policy. Starting the server removes all existing sample data.
## Order saga data
The data stored by the saga is defined in the `OrderSagaData.cs` file in the `Server` project:
```cs
public class OrderSagaData :
ContainSagaData
{
public Guid OrderId { get; set; }
public string OrderDescription { get; set; }
}
```
## Order saga
The handlers for this data are in the `OrderSaga.cs` file in the `Server` project:
```cs
public class OrderSaga(ILogger logger) :
Saga,
IAmStartedByMessages,
IHandleTimeouts
{
protected override void ConfigureHowToFindSaga(SagaPropertyMapper mapper)
{
mapper.MapSaga(saga => saga.OrderId).ToMessage(msg => msg.OrderId);
}
public Task Handle(StartOrder message, IMessageHandlerContext context)
{
var orderDescription = $"The saga for order {message.OrderId}";
Data.OrderDescription = orderDescription;
logger.LogInformation($"Received StartOrder message {Data.OrderId}. Starting Saga");
var shipOrder = new ShipOrder
{
OrderId = message.OrderId
};
logger.LogInformation("Order will complete in 5 seconds");
var timeoutData = new CompleteOrder
{
OrderDescription = orderDescription,
};
return Task.WhenAll(
context.SendLocal(shipOrder),
RequestTimeout(context, TimeSpan.FromSeconds(5), timeoutData)
);
}
public Task Timeout(CompleteOrder state, IMessageHandlerContext context)
{
logger.LogInformation($"Saga with OrderId {Data.OrderId} completed");
MarkAsComplete();
var orderCompleted = new OrderCompleted
{
OrderId = Data.OrderId
};
return context.Publish(orderCompleted);
}
}
```