2017年5月17日水曜日

ASP.NET MVCでCQRSを利用してORMをうまく使う

以前(2013年頃)自分が設計した内容のメモです。
今はASP.NETの世界から離れて久しい。

はじめに

CQRS(コマンドクエリ責務分離)という考え方を使って、参照系と更新系でORMとSQLを使い分けていいとこ取りするという趣旨です。

サンプル

サンプルを作りました。
https://github.com/herara-ofnir3/cqrs-sample

CQRS(コマンドクエリ責務分離)とは

CQRSについて詳しくはぜひこちらをどうぞ。

[Greg Young流CQRS - Mark Nijhof]
http://d.hatena.ne.jp/digitalsoul/20100712/1278886009
※ 上記記事のサンプル
https://github.com/MarkNijhof/Fohjin

ORMを使いたい理由と使いたくない理由

更新処理がとても快適

煩雑なSQLの発行処理を書かずとも、エンティティの状態を変化させ、永続化してやればうまいこと更新してくれます。
特に one-to-many や many-to-many の関係をカスケードしてうまく処理してくれるのは、とても素晴らしいメカニズムです。

バッチ処理的な一括更新や大量データを高速に更新するのには向いていませんが、業務アプリケーションの通常の更新処理なら非常にマッチします。

参照系でしぬ

参照系の処理がやりにくいです。
普通なら簡単なSQLを投げてそのまま結果を表示するだけ、みたいな画面ですら、気にすることが多すぎます。

  • 遅延読み込みされるとまずい(N+1問題)から、これとこれをフェッチして...
  • 一回流してみて実際のSQLをチェックしないと...

みたいな調子です。
どうしてもデータの取得が冗長になりがちです。
特にHQLでSQLを再現していると泣けてきます。

よく言われるORMの問題点はこの参照系の問題に集中している気がします。
どう考えてもシンプルにSQL書いたほうがはやくて確実ですし。

ORMの弱点を補うためのCQRS

ORMの得意な更新系の処理だけORM使って、参照系はSQLを書けばいい、という考えに至りました。
そして、参照系と更新系の分割と一貫性のために、CQRSを適用しました。

CQRSはイベントソーシングとセットで語られることが多いのですが、イベントソーシングはスルーします。
データアクセスがクエリ(参照系)とコマンド(更新系)で違うだけの実装です。

その他利点

実際やってみると色々いいところがありました。

良い作りに縛れる
アプリケーションに必要な参照/更新操作がクエリ/コマンドという形でいい感じにコード上に浮き上がってきます。
いわいる「変な作り」というのがやりにくくなります。
テストがしやすい (疎結合になる)

コマンドの発行は全てコマンドバスに投げるだけになるので、
コントローラが依存するのはコマンドバスといくつかのクエリだけとなります。
そのため、アンチパターンとしてありがちなファットコントローラに陥りにくくなります。

依存関係が絞られますし、トランザクションなどの煩雑な処理が隠蔽されるため、テストが非常に簡潔になります。
自然に結合度が下がり、構造がシンプルになります。
おかげでメンバが積極的にテストを書いてくれました。

更新処理のログが簡単にとれる

全てのコマンド(更新操作)はコマンドバスを通して発行されるので、更新処理を簡単に確実にログできます。(コマンドバスにログ処理を挟むだけ)
このあたりの機構にイベントソーシングを組み合わせると強力ですが、ログをとれるだけでも大きな恩恵を得られました。

運用・保守をする上で、現状のデータに至るまでの経緯が全て記録されているというのは、極めて有効でした。

2014年7月11日金曜日

Dapper で例外 "シーケンスに要素が含まれていません" が起きる

Dapper を使っていたらハマったので。(Dapper っていうより LINQ ?)

現象

以下のように Dapper で Single/First メソッドを使うと例外が起きることがあるようです。

var user = connection.Query<User>("select * from Users where UserName = @userName", new { userName = "hoge" }).Single();
System.InvalidOperationException: シーケンスに要素が含まれていません

クエリの結果が必ず1件あるにも関わらず、起きることがあります。また、常になるというわけではありませんでした。(環境にもよる...?)
私が遭遇したケースでは、数十回から数百回に一度程度再現しました。

解決策

原因と理由はよくわかりませんが SingleOrDefault/FirstOrDefault に変更すると発生しなくなります。

var user = connection.Query<User>("select * from Users where UserName = @userName", new { userName = "hoge" }).SingleOrDefault();

参考

2013年7月20日土曜日

IE10 の互換モードで jquery.validate.unobtrusive を使うとエラー

最近ハマったのでメモ。解決策は以下の記事を参考にしました。

エラーの内容

jquery.validate.unobtrusive を IE10の互換モードで利用するとこんなエラーが出ることがあります。

行: 2699
エラー: メンバーが見つかりません。

どうやらページ内に form があるだけで起こるようで、jQuery その他のスクリプトが全滅して全く動かなくなります。怖い。

なんとなく IE10 の互換モードのバグっぽい雰囲気ですが、時間がなかったためあまり詳しく調べることができませんでした。(いいわけ)

jQuery の以下の set メソッド呼び出しで、novalidate という属性を設定しようとしてエラーが発生しているようです。

nodeHook = jQuery.valHooks.button = {
  get: function( elem, name ) {
   var ret;
   ret = elem.getAttributeNode( name );
   return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ?
    ret.nodeValue :
    undefined;
  },
  set: function( elem, value, name ) {
   // Set the existing or create a new attribute node
   var ret = elem.getAttributeNode( name );
   if ( !ret ) {
    ret = document.createAttribute( name );
    elem.setAttributeNode( ret );
   }
   return ( ret.nodeValue = value + "" );
  }
 };

解決策

いろいろな記事を見て回ると、jQuery の中身を書き換えたりする方法もありましたが、ちょっと怖いので互換モードの指定で逃げました。

以下のように X-UA-Compatible で IE が利用可能な最新のレンダリングモードを指定します。

<meta http-equiv="X-UA-Compatible" content="IE=edge" />

おわりに

いろいろなところでこの問題がおきてテンション下がりました。

jquery.validate.unobtrusive は ASP.NET MVC のテンプレートに入っているし、form がページ内にあるだけでエラーが起こるので、結構影響大きいのではと思うのですが、全然日本語の記事がなかったので書いてみました。

2013年6月16日日曜日

値オブジェクトのためのカスタムモデルバインダー

値オブジェクト

単一の値を表すオブジェクトのことで、いわいる不変オブジェクトのことです。(イミュータブルってやつですね)
例えばこんな感じのヤツですね。

バージョン情報 (e.g: "1.2", "11.101") を単一の値として扱う
public class Version
{
    public Version(int major, int minor)
    {
        Major = major;
        Minor = minor;
    }

    public int Major { get; private set; }

    public int Minor { get; private set; }

    public override string ToString()
    {
        return string.Format("{0}.{1}", Major, Minor);
    }
}

Major, Minor といったプリミティブ値を Version という型にまとめ、状態を変えれないようにすることで扱いがシンプルになります。

これはオブジェクト指向プログラミングのプラクティスとしてよく言われるものですが、ASP.NET MVC で不変オブジェクトを扱うと困ることがあります。

困る例

例えばこんなURLを使いたいと思って、アクションを用意するとします。

/Home/SomeAction?version=1.2
public ActionResult SomeAction(Models.Version version)
{
    ViewBag.Version = version; // version は null で、ModelState にもエラーが入る
    return View("SomeView");
}

当然 version 引数にはクエリパラメータで指定した "1.2" が Major と Minor に入ってきてほしいと思うわけですが、これはうまくいきません。

それもそのはずで、Version という型にどのように値を入れていいか、モデルバインダが知らないからです。

カスタムのモデルバインダを作る

せっかく値オブジェクトをコツコツ作っても、モデルバインダで使えないのでは寂しいので、カスタムのモデルバインダを作りましょう。

上記の Version 型の場合はこんな感じのモデルバインダを作ります。

public class VersionBinder : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var valueKey = bindingContext.ModelName;
        var valueResult = bindingContext.ValueProvider.GetValue(valueKey);
        string rawValue = null;

        if (valueResult != null)
            rawValue = valueResult.AttemptedValue;

        if (string.IsNullOrEmpty(rawValue))
            return null;

        Version result;

        if (Version.TryParse(rawValue, out result))
        {
            return result;
        }
        else
        {
            bindingContext.ModelState.AddModelError(
                valueKey,
                string.Format("'{0}' は無効な値です。", rawValue)
                );

            return null;
        }
    }
}

実際の文字列からの変換は TryParse パターン とかで適当に実装します。

上記のように作ったモデルバインダを、Application_Start 時に登録します。
App_Start ディレクトリに、以下の様な起動用クラスをまとめるといいと思います。

public class BinderConfig
{
    public static void RegisterBinders(ModelBinderDictionary binders)
    {
        binders.Add(typeof(Models.Version), new VersionBinder());
    }
}

これを Global.asax の Application_Start メソッドで呼んであげます。

protected void Application_Start()
{
    // その他いろいろな起動設定...
    BinderConfig.RegisterBinders(ModelBinders.Binders);
    // その他いろいろな起動設定...
}

これで Version 型の値がしっかりモデルバインディングされます。

これたくさん作るの面倒だよね?

モデルバインディングしたい値オブジェクトがたくさんあったら、一つ一つカスタムのモデルバインダを実装するのは恐ろしく面倒です。

コードも定形なのでスーパークラスにまとめると楽です。

public abstract class ValueObjectBinder<T> : IModelBinder
{
    public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        var valueKey = bindingContext.ModelName;
        var valueResult = bindingContext.ValueProvider.GetValue(valueKey);
        string rawValue = null;

        if (valueResult != null)
            rawValue = valueResult.AttemptedValue;

        if (string.IsNullOrEmpty(rawValue))
            return null;

        T result;

        if (TryParse(rawValue, out result))
        {
            return result;
        }
        else
        {
            bindingContext.ModelState.AddModelError(
                valueKey,
                GetParseErrorMessage(rawValue)
                );

            return null;
        }
    }

    protected abstract bool TryParse(string input, out T result);

    protected virtual string GetParseErrorMessage(string rawValue)
    {
        return string.Format("'{0}' は無効な値です。", rawValue);
    }
}

さっきの Version 型の場合、コレを使うとこうなります。

public class VersionBinder : ValueObjectBinder<Version>
{
    protected override bool TryParse(string input, out Version result)
    {
        return Version.TryParse(input, out result);
    }
}

これならまあ作ってもいいかなってレベルじゃないでしょうか。もし大規模なアプリケーションでこれでも大変なら、T4 とか使う手もありそうです。

まとめ

これで値オブジェクトをたくさん使えるよ。

2013年5月18日土曜日

C# で Project Euler に挑戦: Problem 6

問題

二乗和の差

最初の10個の自然数について, その二乗の和は,

12 + 22 + ... + 102 = 385

最初の10個の自然数について, その和の二乗は,

(1 + 2 + ... + 10)2 = 3025

これらの数の差は 3025 - 385 = 2640 となる.

同様にして, 最初の100個の自然数について二乗の和と和の二乗の差を求めよ.

ぼくの解答

static void Main(string[] args)
{
    int n = 100;

    var numbers = Enumerable.Range(1, n);

    var sum = numbers.Sum();
    var sumSquare = sum * sum; // 和の二乗を求めます。
    var squreSum = numbers.Sum(i => i * i); // 二乗数の和を求めます。

    var answer = sumSquare - squreSum;

    Console.WriteLine(answer);
    Console.ReadLine();
}

解説

あまり説明するところがありません... ごく普通に 1 から 100 までの総和の二乗と、二乗の総和を求めています。

この問題はなんか簡単ですね。もっと面白い解き方があるのかな。

2013年5月16日木曜日

C# で Project Euler に挑戦: Problem 5

問題

最小の倍数

2520 は 1 から 10 の数字の全ての整数で割り切れる数字であり, そのような数字の中では最小の値である.

では, 1 から 20 までの整数全てで割り切れる数字の中で最小の正の数はいくらになるか.

ぼくの解答

static void Main(string[] args)
{
    int n = 20;

    int multiple = 1;
    int counter = 1;

    var numbers = Enumerable.Range(1, n);

    foreach (var i in numbers)
    {
        while (counter % i != 0)
        {
            counter += multiple;
        } 

        multiple = counter;
    }

    Console.WriteLine(multiple);
    Console.ReadLine();
}

解説

最小公倍数を求める問題です。単純に 1 から 20 まで順番に最小公倍数を見つけていきます。

最小公倍数を multiple として初期値を 1 とします。カウンタ用の変数(counter)も初期値を 1 にします。

  1. この時点の最小公倍数(1) と 1 の最小公倍数を求める。
    • 1 / 1 = 1 ... 0 => 割り切れるので最小公倍数1 に更新。
  2. この時点の最小公倍数(1) と 2 の最小公倍数を求める。
    • 1 / 2 = 0 ... 1 => 割り切れないので countermultiple を加算。
    • 2 / 2 = 1 ... 0 => 割り切れるので最小公倍数2 に更新。
  3. この時点の最小公倍数(2) と 3 の最小公倍数を求める。
    • 2 / 3 = 0 ... 2 => 割り切れないので countermultiple を加算。
    • 4 / 3 = 1 ... 1 => 割り切れないので countermultiple を加算。
    • 6 / 3 = 2 ... 0 => 割り切れるので最小公倍数6 に更新。
  4. この時点の最小公倍数(6) と 4 の最小公倍数を求める。
    • 6 / 4 = 1 ... 2 => 割り切れないので countermultiple を加算。
    • 12 / 4 = 3 ... 0 => 割り切れるので最小公倍数12 に更新。
  5. この時点の最小公倍数(12) と 5 の最小公倍数を求める。
    • 12 / 5 = 2 ... 2 => 割り切れないので countermultiple を加算。
    • 24 / 5 = 4 ... 4 => 割り切れないので countermultiple を加算。
    • 36 / 5 = 7 ... 1 => 割り切れないので countermultiple を加算。
    • 48 / 5 = 9 ... 3 => 割り切れないので countermultiple を加算。
    • 60 / 5 = 12 ... 0 => 割り切れるので最小公倍数60 に更新。
  6. この時点の最小公倍数(60) と 6 の最小公倍数を求める。
    • 60 / 6 = 10 ... 0 => 割り切れるので最小公倍数60 に更新。
  7. ... という風に続く。

他のプローチ

調べたら他にも良さそうなやり方がありました。最大公約数を元に解くのとか、数学っぽくていいですね。ぼくの頭では思いつきませんでした...

2013年5月13日月曜日

C# で Project Euler に挑戦: Problem 4

問題

最大の回文積

左右どちらから読んでも同じ値になる数を回文数という. 2桁の数の積で表される回文数のうち, 最大のものは 9009 = 91 × 99 である.

では, 3桁の数の積で表される回文数のうち最大のものを求めよ.

ぼくの解答

using System;
using System.Collections.Generic;
using System.Linq;

class Program
{
    static void Main(string[] args)
    {
        var threeDigitNumbers = Enumerable.Range(100, 900).ToList();
        var answer = threeDigitNumbers
            .SelectMany(i => threeDigitNumbers.Select(j => i * j))
            .Where(i => i.IsPalindrome())
            .Max();

        Console.WriteLine(answer);
        Console.ReadLine();
    }
}

public static class NumberHelper
{
    public static bool IsPalindrome(this int n)
    {
        return n.Digits().SequenceEqual(n.Digits().Reverse());
    }

    public static IEnumerable<int> Digits(this int n)
    {
        if (n == 0)
            yield return 0;

        const int radix = 10;
        var q = n;

        while (q != 0)
        {
            yield return q % radix;
            q /= radix;
        }
    } 
}

解説

この問題のポイントは、どうやって回文数かどうか判定するか、でしょうか。以下のどちらかになると思います。

  1. 文字列で判定する
  2. 各桁の数値をリストにして判定する

文字列でやるほうが簡単ですが、せっかくなので数値の桁を列挙する Digits() メソッドを作って解いてみました。

ある数値の桁の求め方

求めたい進法の基数で割っていって余りを取り出すと桁を求めることができます。例えば 4603 の場合、手順は以下です。

  1. 4603 / 10 = 460 ... 3 (商を次の割られる数へ回します)
  2. 460 / 10 =  46 ... 0
  3.   46 / 10 =   4 ... 6
  4.    4 / 10 =   0 ... 4 (商が 0 になったら終了です)
回文数かどうか

上記のように桁のリストを求めるか、文字列に変換して内容を反転させて元のものと同じかどうか調べればよいですね。

今回は int の拡張メソッドに IsPalindrome というメソッドを用意しています。内容は簡単です。

上記の Digits メソッドで桁を列挙し、Reverse メソッドで反転させたものと同じかを、SequenceEqual メソッドで調べます。SequenceEqual メソッドが true を返せば回文数です。

三桁の数字の積

単純にループしても良いですが、せっかく C# なので LINQ です。

はじめに三桁の数字のリストを作ります。Enumerable.Range(100, 900) で簡単ですね。(100 ∼ 999)

組み合わせを作ってそれらをかければいいので、SelectMany と Select メソッドを使って簡単に記述できます。

実行効率

この解は実行効率が悪いです。三桁の数字の組み合わせを全部作ってしまう点がネックですね。
実行効率でいえば、999 * 999 から 999 * 998, 999 * 997 ... とデクリメントしながら積を求め、回文数になった時点で処理を中断するほうが良いです。

Factorio: Space Exploration クリア記録

 工場建設クラフトゲーム、Factorio の MOD である Space Exploration のクリア記録です。 はじめに プレイ時間は約 350 時間、2023年10月から2025年2月にかけて15ヶ月間に及びました。この期間中3人の友人と毎週末、工場勤務に明け暮れました...