Alternative Architecture DOJO

オルターブースのクラウドネイティブ特化型ブログです。

Azure Cosmos DB Repository .NET SDK を試してみたら少し便利だった

こんにちは。MLBお兄さんこと松村です。
MLBは60試合の短縮シーズンが終わり、ポストシーズンに突入しました。我がヤンキースは地区シリーズで敗退してしまい、現在はタンパベイ・レイズの応援に勤しんでいます。


私は .NET Core でデータベースを使ってアプリケーションを実装するときにリポジトリパターンという、データ操作の方式を使うことが多いです。
リポジトリパターンを採用するかしないかというのは賛否両論あるでしょうが、私としてはデータ操作のコードが共通化しやすく、インターフェースを使うことでテストコードが書きやすくなるという利点があるため、リポジトリパターンを使っています。
リポジトリパターンについての説明は省略しますのでこちらをご覧ください。

docs.microsoft.com

リポジトリパターンは RDB 専用のものではなく、データストア全般に適用することができます。
NoSQL である Azure Cosmos DB のコードでもリポジトリパターンを使っています。
これまでは自作していましたが、先日このようなマイクロソフトの技術ブログにこちらの記事が公開されました。

devblogs.microsoft.com

MS非公式のライブラリではありますが、 Azure Cosmos DB .NET SDK v3 をラップした CosmosDB 用のリポジトリライブラリ「Azure Cosmos DB Repository .NET SDK」を紹介した記事になります。
ライブラリの URL はこちら。

どうやらライブラリでは CosmosDB のデータの CRUD の機能が利用できるようです。

public interface IRepository<TItem> where TItem : Item
{
    // Create
    ValueTask<TItem> CreateAsync(TItem value);
    Task<TItem[]> CreateAsync(IEnumerable values);

    // Read
    ValueTask<TItem> GetAsync(string id, string partitionKeyValue = null);
    ValueTask<TItem> GetAsync(string id, PartitionKey partitionKey);
    ValueTask<IEnumerable<TItem>> GetAsync(Expression<Func<TItem, bool>> predicate);

    // Update
    ValueTask<TItem> UpdateAsync(TItem value);

    // Delete
    ValueTask DeleteAsync(TItem value);
    ValueTask DeleteAsync(string id, string partitionKeyValue = null);
    ValueTask DeleteAsync(string id, PartitionKey partitionKey);
}

サンプルアプリに Cosmos DB Repository を適用する

使い方に慣れるために、こちらのサンプルアプリケーションをカスタマイズして Cosmos DB Repository を組み込んでみます。

github.com

このサンプルは CosmosDB の以下の操作が実装されています。
いずれも CosmosClient クラス を使って操作を行っています。

  1. データベースの作成
  2. コンテナーの作成
  3. アイテムの登録
  4. アイテムの検索
  5. アイテムの更新
  6. アイテムの削除
  7. データベースの削除

前準備①).NET Core 3.1 にアップデート

csproj ファイルを編集して .NET Core 2.1 から .NET Core 3.1 にアップデートします。
ついでに Cosmos DB .NET SDK も最新 (3.1.4) にアップデートします。

<Project Sdk="Microsoft.NET.Sdk">

  <PropertyGroup>
    <OutputType>Exe</OutputType>
    <TargetFramework>netcoreapp3.1</TargetFramework> <!-- ここ -->
    <IsPackable>false</IsPackable>
    <GenerateAssemblyInfo>false</GenerateAssemblyInfo>
    <RootNamespace>CosmosGettingStartedTutorial</RootNamespace>
    <LangVersion>latest</LangVersion>
  </PropertyGroup>

  <ItemGroup>
    <PackageReference Include="IEvangelist.Azure.CosmosRepository" Version="2.0.2" />
    <PackageReference Include="Microsoft.Azure.Cosmos" Version="3.14.0" /> <!-- ここ -->
    <PackageReference Include="Microsoft.Extensions.Hosting" Version="3.1.9" />
  </ItemGroup>
  
</Project>

前準備②)汎用ホスト構成に変更

Cosmos DB Repository は DI による実装が案内されているため、素のコンソールアプリであるサンプルを汎用ホスト構成に変更します。
汎用ホスト構成に変更したときの詳細はこちらのコミットをご覧ください。

github.com

Cosmos DB Repository のインストール

汎用ホスト構成でサンプルアプリが動くようになったところで、Cosmos DB Repository の NuGet パッケージをインストールします。

dotnet add package IEvangelist.Azure.CosmosRepository --version 2.0.2

JSON データとなるアイテムクラスの適用

CRUD を行う対象のアイテムクラスに対して、 Cosmos DB Repository としての作法を適用していきます。

変更前 (抜粋)

using Newtonsoft.Json;

namespace CosmosGettingStartedTutorial
{
    public class Family
    {
        [JsonProperty(PropertyName = "id")]
        public string Id { get; set; }
        public string LastName { get; set; }
        public Parent[] Parents { get; set; }
        public Child[] Children { get; set; }
        public Address Address { get; set; }
        public bool IsRegistered { get; set; }
        public override string ToString()
        {
            return JsonConvert.SerializeObject(this);
        }
    }

変更後 (抜粋)

using Microsoft.Azure.CosmosRepository;
using Microsoft.Azure.CosmosRepository.Attributes;
using Newtonsoft.Json;

namespace CosmosGettingStartedTutorial
{
    [PartitionKeyPath("/LastName")]
    public class Family : Item
    {
        public string LastName { get; set; }
        public Parent[] Parents { get; set; }
        public Child[] Children { get; set; }
        public Address Address { get; set; }
        public bool IsRegistered { get; set; }
        public override string ToString()
        {
            return JsonConvert.SerializeObject(this);
        }

        protected override string GetPartitionKeyValue() => LastName;
    }

やったこと

  • Microsoft.Azure.CosmosRepository.Item クラスを継承した
  • クラスに対して Microsoft.Azure.CosmosRepository.Attributes.PartitionKeyPathAttribute 属性を指定した(パーティションキーが /LastName であることを明示)
  • GetPartitionKeyValue() メソッドをオーバーライドし、パーティションキーの値を返すようにする

DI にリポジトリを登録する

IServiceCollection にリポジトリを登録します。このとき接続文字列、データベース名、コンテナー名を定義します。

static IHostBuilder CreateHostBuilder(string[] args) =>
    Host.CreateDefaultBuilder(args)
        .ConfigureServices((hostContext, services) =>
        {
            services.AddHostedService<Worker>();
            
            // ここから
            services.AddCosmosRepository(hostContext.Configuration, options =>
            {
                options.CosmosConnectionString = "(connection string)";
                options.ContainerId = "FamilyContainer";
                options.DatabaseId = "FamilyDatabase";
            });
            // ここまで
        });

リポジトリを通じてデータ CRUD を行う

コンストラクタで DI から IRepository<TItem> のオブジェクトを取り出し、 CRUD の処理に書き換えます。
書き方はシンプルなので Cosmos DB .NET SDK に慣れていれば難しくないと思います。

サンプルアプリでのリポジトリの使い方はこちらのコミットをご覧ください。

github.com

使いにくさもある

ライブラリとしてはシンプルで使いやすいですが、継承が必要な Item クラスや IRepository インターフェースの名前が汎用的すぎて、自分のコードでの名前と被ることが考えられます。
名前が被ると名前空間から指定しないといけないので、多少の面倒さが出ます。

www.alterbooth.com

www.alterbooth.com

cloudpointer.tech