CloudStructures - ローカルとクラウドのデータ構造を透過的に表現するC# + Redisライブラリ

というものを作りました。インストールはNuGetから。

何を言ってるのかヨクワカラナイので、まずはコード例を。

// こんなクラスがあるとして
public class Person
{
    public string Name { get; private set; }
    public List<Person> Friends { get; private set; }

    public Person(string name)
    {
        Name = name;
        Friends = new List<Person>();
    }
}

// こんなのがいるとして
var sato = new Person("さとう");

// 人を足す
sato.Friends.Add(new Person("やまだ"));
sato.Friends.Add(new Person("いとう"));

// 件数数える
var friendCount = sato.Friends.Count;

これは普通にローカルで表現する場合です。実に普通です。では、次。

// RedisServerの設定の表現
public static class RedisServer
{
    public static readonly RedisSettings Default = new RedisSettings("127.0.0.1");
}

// こんなクラスがあるとして
public class Person
{
    public string Name { get; private set; }
    public RedisList<Person> Friends { get; private set; }

    public Person(string name)
    {
        Name = name;
        Friends = new RedisList<Person>(RedisServer.Default, "Person-" + Name);
    }
}

// こんなのがいるとして
var sato = new Person("さとう");

// 人を足す
await sato.Friends.AddLast(new Person("やまだ"));
await sato.Friends.AddLast(new Person("いとう"));

// 件数数える
var friendCount = await sato.Friends.GetLength();

この場合、Redisを通してサーバー上にデータは保存されています。ですが、操作感覚はローカルにあるものとほぼほぼ同じです。違いは全ての操作が非同期なので、awaitするぐらい。

IAsyncList

これは、Actor Framework for Windows AzureのDistributed Collectionsに影響を受けています。ActorFxのそれは、SOURCE CODEを落としてdocsフォルダの Distributed Collections using the ActorFx.docx に色々書いてあって面白いので必読です。

そして、ActorFxではSystem.Cloud.Collectionsとして(System名前空間!)、現状、以下のようなインターフェイスが定義されています(まだ変更の可能性大いにあり)。

namespace System.Cloud.Collections
{
    public interface IAsyncCollection<T> : IObservable<T>
    {
        Task<int> CountAsync { get; }
        Task<bool> IsReadOnlyAsync { get; }

        Task AddAsync(T item);
        Task ClearAsync();
        Task<bool> ContainsAsync(T item);
        Task CopyToAsync(T[] array, int arrayIndex);
        Task<bool> RemoveAsync(T item);
    }

    public interface IAsyncList<T> : IAsyncCollection<T>
    {
        Task<T> GetItemAsync(int index);
        Task SetItemAsync(int index, T value);
        Task<int> IndexOfAsync(T item);
        Task InsertAsync(int index, T item);
        Task RemoveAtAsync(int index);

        // Less chatty versions
        Task AddAsync(IEnumerable<T> items);
        Task RemoveRangeAsync(int index, int count);
    }

    public interface IAsyncDictionary<TKey, TValue> : IAsyncCollection<KeyValuePair<TKey, TValue>>
    {
        Task<TValue> GetValueAsync(TKey key);
        Task SetValueAsync(TKey key, TValue value);
        Task<Tuple<bool, TValue>> TryGetValueAsync(TKey key);

        // No AddAsync - use SetValueAsync instead.  We have no atomic operation to add iff a value is not in the dictionary.
        Task<bool> ContainsKeyAsync(TKey key);
        Task<bool> RemoveAsync(TKey key);

        // Bulk operations
        Task<ICollection<TValue>> GetValuesAsync(IEnumerable<TKey> keys);
        Task SetValuesAsync(IEnumerable<TKey> keys, IEnumerable<TValue> values);
        Task RemoveAsync(IEnumerable<TKey> keys);

        ICollection<TKey> Keys { get; }
        ICollection<TValue> Values { get; }
    }
}

わくわくしてきません?私はこの定義を見た瞬間に衝撃を受けました。RxのIObservable<T>を見た時と同程度の衝撃かもわからない。Ax(ActorFx)の実装としてはCloudList, CloudDictionary, CloudStringDictionaryがありますが(基盤としてAzure Table)、見てすぐにRedisと結びついた。Redisの持つデータ構造、List, Hash, Set, SortedSetってこれじゃないか!って。こういう風に表現されたらどれだけ素敵な見た目になるか……!

Strings, Set, SortedSet, List, Hash, その他

というわけで、最初の例ではRedisListだけ出しましたが、StringsもSetもSortedSetもHashもあります。また、HashClassやMemoizedRedisStringといった特殊なものも幾つか用意してあります。

// フィールドに持たなくても、ふつーにRedisClient的に使ってもいいよ
var client = new RedisString<string>(RedisServer.Default, "toaru-key");
await client.Set("あいうえお!", expirySeconds: TimeSpan.FromMinutes(60).TotalSeconds);

// RedisClassはRedisのHash構造をクラスにマッピングするもの
var hito = new RedisClass<Hito>(RedisServer.Default, "hito-1");
await hito.SetField("Name", "やまもと");
await hito.Increment("Money", 100);

var localHito = await hito.GetValue(); // Cloud -> Localに落とす、的ないめーぢ

実際色々あるので見て回ってください!

ConnectionManagement

基盤的な機能として、BookSleeveの接続管理を兼ねています。

// Redisの設定を表す
var settings = new RedisSettings(host: "127.0.0.1", port: 6379, db: 0);

// BookSleeveはスレッドセーフで単一のコネクションを扱う
// コネクションを一つに保ったり切断されていた場合の再接続などをしてくれる
var conn = settings.GetConnection();


// 複数接続はRedisGroupで管理できる
var group = new RedisGroup(groupName: "Cache", settings: new[]
{
    new RedisSettings(host: "100.0.0.1", port: 6379, db: 0),
    new RedisSettings(host: "105.0.0.1", port: 6379, db: 0),
});

// keyを元に分散先のサーバーを決める(デフォルトはMD5をサーバー台数で割って決めるだけの単純な分散)
var conn = group.GetSettings("hogehoge-100").GetConnection();

// シリアライザはデフォルトではJSONとProtoBufを用意(未指定の場合はJSON)
new RedisSettings("127.0.0.1", converter: new JsonRedisValueConverter());
new RedisSettings("127.0.0.1", converter: new ProtoBufRedisValueConverter());

って、ここまでBookSleeveの説明がなかった!BookSleeveはRedisのライブラリで、非同期の操作のみを提供しています。CloudStructuresのRedis操作はBookSleeveに全部委ねてます。というかぶっちゃけ、かなり単純なラップがほとんどだったりします(!)。見せ方を変えただけ、です、よーするところ。

んで、BookSleeveは斬新で非常に良いライブラリなのですけれど、操作が本当にプリミティブなものしかないので(全てのGetとSetがstringとbyte[]しかない、とかね)、ある程度、自分で作りこんでやらないと全く使えません。なので、この部分だけでも、結構使えるかなって思います。

Next

個人的にはすっごく面白いと思ってます。見せ方の違いでしかないわけですが、しかし、その見せ方の違いというのが非常に大事なのです。直感的、ですが、ある種奇抜なデザインなので、戸惑うとは思います。異色度合いで言ったら、以前に私の作ったReactivePropertyと同程度に異色かな、と。だからこそ、凄く大きな可能性を感じませんか?

ちなみに、これは(いつものように)コンセプト止まりじゃなくて、実際に使う予定アリなので、しっかり育ててく気満々です。是非、試してみてもらえると嬉しいですね。

Profile

Yoshifumi Kawai

Cysharp, Inc
CEO/CTO

Microsoft MVP for Developer Technologies(C#)
April 2011
|
July 2024

Twitter:@neuecc GitHub:neuecc

Archive