Skip to content

Add details about generic-type parameters in IGrainStorage interface impl #34245

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

Merged
merged 2 commits into from
Feb 28, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 10 additions & 2 deletions docs/orleans/tutorials-and-samples/custom-grain-storage.md
Original file line number Diff line number Diff line change
@@ -1,15 +1,15 @@
---
title: Custom grain storage sample project
description: Explore a custom grain storage sample project written with .NET Orleans.
ms.date: 12/08/2022
ms.date: 02/27/2023
zone_pivot_groups: orleans-version
---

# Custom grain storage

In the tutorial on declarative actor storage, we looked at allowing grains to store their state in an Azure table using one of the built-in storage providers. While Azure is a great place to squirrel away your data, there are many alternatives. There are so many that there was no way to support them all. Instead, Orleans is designed to let you easily add support for your form of storage by writing a grain storage.

In this tutorial, we'll walk through how to write simple file-based grain storage. A file system is not the best place to store grains states as it is local, there can be issues with file locks and the last update date is not sufficient to prevent inconsistency. But it's an easy example to help us illustrate the implementation of a `Grain Storage`.
In this tutorial, we'll walk through how to write simple file-based grain storage. A file system is not the best place to store grains states as it is local, there can be issues with file locks and the last update date is not sufficient to prevent inconsistency. But it's an easy example to help us illustrate the implementation of a _grain storage_.

## Get started

Expand Down Expand Up @@ -72,6 +72,14 @@ public sealed class FileGrainStorage : IGrainStorage, ILifecycleParticipant<ISil
}
```

Each method implements the corresponding method in the [IGrainStorage](xref:Orleans.Storage.IGrainStorage) interface, accepting a generic-type parameter for the underlying state type. The methods are:

- <xref:Orleans.Storage.IGrainStorage.ReadStateAsync%2A?displayProperty=nameWithType>: to read the state of a grain.
- <xref:Orleans.Storage.IGrainStorage.WriteStateAsync%2A?displayProperty=nameWithType>: to write the state of a grain.
- <xref:Orleans.Storage.IGrainStorage.ClearStateAsync%2A?displayProperty=nameWithType>: to clear the state of a grain.

The <xref:Orleans.ILifecycleParticipant%601.Participate%2A?displayProperty=nameWithType> method is used to subscribe to the lifecycle of the silo.

Before starting the implementation, you'll create an options class containing the root directory where the grain state files are persisted. For that you'll create an options file named `FileGrainStorageOptions` containing the following:

:::code source="snippets/custom-grain-storage/FileGrainStorageOptions.cs":::
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,10 @@ public Task ClearStateAsync<T>(
{
if (fileInfo.LastWriteTimeUtc.ToString() != grainState.ETag)
{
throw new InconsistentStateException($$"""
Version conflict (ClearState): ServiceId={{_clusterOptions.ServiceId}}
ProviderName={{_storageName}} GrainType={{typeof(T)}}
GrainReference={{grainId}}.
throw new InconsistentStateException($"""
Version conflict (ClearState): ServiceId={_clusterOptions.ServiceId}
ProviderName={_storageName} GrainType={typeof(T)}
GrainReference={grainId}.
""");
}

Expand All @@ -57,7 +57,7 @@ public async Task ReadStateAsync<T>(
IGrainState<T> grainState)
{
var fName = GetKeyString(stateName, grainId);
var path = Path.Combine(_options.RootDirectory, fName!);
var path = Path.Combine(_options.RootDirectory, fName!);
var fileInfo = new FileInfo(path);
if (fileInfo is { Exists: false })
{
Expand All @@ -84,10 +84,10 @@ public async Task WriteStateAsync<T>(
var fileInfo = new FileInfo(path);
if (fileInfo.Exists && fileInfo.LastWriteTimeUtc.ToString() != grainState.ETag)
{
throw new InconsistentStateException($$"""
Version conflict (WriteState): ServiceId={{_clusterOptions.ServiceId}}
ProviderName={{_storageName}} GrainType={{typeof(T)}}
GrainReference={{grainId}}.
throw new InconsistentStateException($"""
Version conflict (WriteState): ServiceId={_clusterOptions.ServiceId}
ProviderName={_storageName} GrainType={typeof(T)}
GrainReference={grainId}.
""");
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,13 +10,13 @@ internal static class FileGrainStorageFactory
internal static IGrainStorage Create(
IServiceProvider services, string name)
{
var optionsSnapshot =
services.GetRequiredService<IOptionsSnapshot<FileGrainStorageOptions>>();
var optionsMonitor =
services.GetRequiredService<IOptionsMonitor<FileGrainStorageOptions>>();

return ActivatorUtilities.CreateInstance<FileGrainStorage>(
services,
name,
optionsSnapshot.Get(name),
optionsMonitor.Get(name),
services.GetProviderClusterOptions(name));
}
}