web-dev-qa-db-ja.com

ドメインクラスとSQLクエリ間のロジックの重複を回避する方法は何ですか?

以下の例は完全に人工的なものであり、その唯一の目的は私のポイントを理解させることです。

SQLテーブルがあるとします。

CREATE TABLE rectangles (
  width int,
  height int 
);

ドメインクラス:

public class Rectangle {
  private int width;
  private int height;

  /* My business logic */
  public int area() {
    return width * height;
  }
}

ここで、データベース内のすべての長方形の総面積をユーザーに表示する必要があるとします。これを行うには、テーブルのすべての行をフェッチし、それらをオブジェクトに変換して繰り返します。しかし、テーブルにはたくさんの長方形があるので、これはばかげているように見えます。

だから私はこれをします:

SELECT sum(r.width * r.height)
FROM rectangles r

これは簡単、高速で、データベースの長所を利用しています。ただし、ドメインクラスでも同じ計算を行っているため、ロジックが重複しています。

もちろん、この例では、ロジックの複製は致命的ではありません。しかし、私は他のドメインクラスと同じ問題に直面しています。より複雑なクラスです。

22
Escape Velocity

Lxrecが指摘したように、コードベースによって異なります。一部のアプリケーションでは、この種のビジネスロジックをSQL関数やクエリに組み込んで、それらの値をユーザーに表示する必要があるときにいつでも実行できます。

ばかげているように見えることもありますが、主な目的としては、パフォーマンスよりも正確さを重視してコーディングする方が適切です。

サンプルで、ユーザーの領域の値をWebフォームで表示している場合は、次のようにする必要があります。

1) Do a post/get to the server with the values of x and y;
2) The server would have to create a query to the DB Server to run the calculations;
3) The DB server would make the calculations and return;
4) The webserver would return the POST or GET to the user;
5) Final result shown.

サンプルにあるような単純なことには馬鹿げていますが、銀行システムにおけるクライアントの投資のIRRの計算など、より複雑なものが必要になる場合があります。

正確さのコード。ソフトウェアは正しいが遅い場合、(プロファイリング後に)必要な場所を最適化する機会があります。それがビジネスロジックの一部をデータベースに保持することを意味するのであれば、そうしてください。そのため、リファクタリング手法を採用しています。

DRYの原則に違反するなど、いくつかの最適化を行うよりも遅くなるか、応答しなくなった場合は、適切な単体テストと整合性テストに身を包んだとしても、これは罪ではありません。

11
Machado

あなたは例が人工的であると言っているので、私がここで言っていることがあなたの実際の状況に適しているかどうかはわかりませんが、私の答えは [〜#〜] orm [〜#〜] を使用します=(オブジェクトリレーショナルマッピング)レイヤーは、データベースの構造とクエリ/操作を定義します。これにより、すべてがモデルで定義されるため、重複するロジックがなくなります。

たとえば、 Django (python)フレームワークを使用して、長方形のドメインクラスを次の model として定義します。

class Rectangle(models.Model):
    width = models.IntegerField()
    height = models.IntegerField()

    def area(self):
        return self.width * self.height

定義する合計面積(フィルタリングなし)を計算するには:

def total_area():
    return sum(rect.area() for rect in Rectangle.objects.all())

他の人が述べたように、最初にコードを正確にして、本当にボトルネックになったときにのみ最適化する必要があります。したがって、後日、絶対に最適化する必要がある場合は、次のような生のクエリの定義に切り替えることができます。

def total_area_optimized():
    return Rectangle.objects.raw(
        'select sum(width * height) from myapp_rectangle')
2
yoniLavi

私はアイデアを説明するために愚かな例を書きました:

class BinaryIntegerOperation
{
    public int Execute(string operation, int operand1, int operand2)
    {
        var split = operation.Split(':');
        var opCode = split[0];
        if (opCode == "MULTIPLY")
        {
            var args = split[1].Split(',');
            var result = IsFirstOperand(args[0]) ? operand1 : operand2;
            for (var i = 1; i < args.Length; i++)
            {
                result *= IsFirstOperand(args[i]) ? operand1 : operand2;
            }
            return result;
        }
        else
        {
            throw new NotImplementedException();
        }
    }
    public string ToSqlExpression(string operation, string operand1Name, string operand2Name)
    {
        var split = operation.Split(':');
        var opCode = split[0];
        if (opCode == "MULTIPLY")
        {
            return string.Join("*", split[1].Split(',').Select(a => IsFirstOperand(a) ? operand1Name : operand2Name));
        }
        else
        {
            throw new NotImplementedException();
        }
    }
    private bool IsFirstOperand(string code)
    {
        return code == "0";
    }
}

したがって、いくつかのロジックがある場合:

var logic = "MULTIPLY:0,1";

ドメインクラスで再利用できます。

var op = new BinaryIntegerOperation();
Console.WriteLine(op.Execute(logic, 3, 6));

または、SQL生成レイヤーで:

Console.WriteLine(op.ToSqlExpression(logic, "r.width", "r.height"));

そしてもちろん、簡単に変更できます。これを試して:

logic = "MULTIPLY:0,1,1,1";
1
astef