डोमेन कक्षाओं और एसक्यूएल प्रश्नों के बीच तर्क के दोहराव से बचने के तरीके क्या हैं?


21

नीचे दिया गया उदाहरण पूरी तरह से कृत्रिम है और इसका एकमात्र उद्देश्य मेरी बात को पार करना है।

मान लीजिए कि मेरे पास एक 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

यह आसान, तेज और डेटाबेस की ताकत का उपयोग करता है। हालाँकि, यह डुप्लिकेट लॉजिक का परिचय देता है, क्योंकि मेरी डोमेन क्लास में भी यही गणना है।

बेशक, इस उदाहरण के लिए तर्क का दोहराव बिल्कुल भी घातक नहीं है। हालांकि, मैं अपने अन्य डोमेन वर्गों के साथ एक ही समस्या का सामना कर रहा हूं, जो अधिक जटिल हैं।


1
मुझे संदेह है कि इष्टतम समाधान कोडबेस से कोडबेस तक बहुत बेतहाशा भिन्न होगा, इसलिए क्या आप कुछ अधिक जटिल उदाहरणों में से एक का वर्णन कर सकते हैं जो आपको परेशानी दे रहा है?
Ixrec

2
@lxrec: रिपोर्ट। एक व्यावसायिक अनुप्रयोग जिसमें नियम हैं जो मैं कक्षाओं में कैप्चर कर रहा हूं, और मुझे ऐसी रिपोर्ट बनाने की भी आवश्यकता है जो समान जानकारी दिखाती हैं, लेकिन संघनित होती हैं। वैट की गणना, भुगतान, कमाई, इस तरह का सामान।
वेग

1
क्या यह सर्वर और क्लाइंट के बीच लोड को वितरित करने का प्रश्न नहीं है? निश्चित रूप से, क्लाइंट को गणना के कैश्ड परिणाम को डंप करना आपका सबसे अच्छा दांव है, लेकिन यदि डेटा में अक्सर बदलाव होता है और कई अनुरोध होते हैं, तो इसके बजाय क्लाइंट पर केवल सामग्री और नुस्खा फेंकने में सक्षम होना फायदेमंद हो सकता है उनके लिए खाना बनाना। मुझे लगता है कि वितरित प्रणाली में एक से अधिक नोड होना एक बुरी बात नहीं है जो एक निश्चित कार्यक्षमता प्रदान कर सकता है।
अशक्त

मुझे लगता है कि इस तरह के कोड उत्पन्न करने का सबसे अच्छा तरीका है। बाद में समझाऊंगा।
ज़ेवियर कॉम्बेल

जवाबों:


11

जैसा कि lxrec ने बताया है, यह कोडबेस से कोडबेस में भिन्न होगा। कुछ एप्लिकेशन आपको उन प्रकार के व्यापारिक तर्क को SQL फ़ंक्शंस और / या क्वेरी में डालने की अनुमति देंगे और आपको उन मूल्यों को चलाने की अनुमति देंगे जब भी आपको उपयोगकर्ता को उन मूल्यों को दिखाने की आवश्यकता होती है।

कभी-कभी यह बेवकूफ लग सकता है, लेकिन प्राथमिक उद्देश्य के रूप में प्रदर्शन की तुलना में शुद्धता के लिए कोड करना बेहतर है।

आपके नमूने में, यदि आप किसी वेबफ़ॉर्म में उपयोगकर्ता के लिए क्षेत्र का मूल्य दिखा रहे हैं, तो आपको निम्न करना होगा:

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.

यह नमूने पर एक जैसी सरल चीजों के लिए बेवकूफ है, लेकिन यह एक बैंकिंग प्रणाली में क्लाइंट के निवेश के आईआरआर की गणना करने जैसे अधिक जटिल सामान के लिए आवश्यक हो सकता है।

शुद्धता के लिए कोड । यदि आपका सॉफ्टवेयर सही है, लेकिन धीमा है, तो आपके पास जहां (प्रोफाइलिंग के बाद) की आवश्यकता है, आपको ऑप्टिमाइज़ करने के मौके होंगे। यदि इसका मतलब है कि डेटाबेस में कुछ व्यावसायिक तर्क रखना, तो यह हो। इसलिए हमारे पास रिफैक्टिंग तकनीक है।

यदि यह धीमा हो जाता है, या अनुत्तरदायी हो जाता है, तो आप की तुलना में कुछ अनुकूलन हो सकते हैं, जैसे DRY सिद्धांत का उल्लंघन करना, जो कि एक पाप नहीं है यदि आप अपने आप को उचित इकाई परीक्षण और स्थिरता परीक्षण से घेर लेते हैं।


1
एसक्यूएल में (प्रोसीजरल) बिजनेस लॉजिक लगाने में परेशानी रिफ्लेक्टर के लिए बेहद दर्दनाक है। यहां तक ​​कि अगर आपके पास शीर्ष नॉच SQL रीफैक्टरिंग उपकरण हैं, तो वे आमतौर पर आपके आईडीई में कोड रीफैक्टरिंग टूल के साथ इंटरफ़ेस नहीं करते हैं (या कम से कम मैंने अभी तक ऐसा टूलसेट नहीं देखा है)
रोलाण्ड टीपीपी

2

आप कहते हैं कि उदाहरण कृत्रिम है, इसलिए मुझे नहीं पता कि मैं यहां जो कह रहा हूं वह आपकी वास्तविक स्थिति के अनुकूल है, लेकिन मेरा जवाब है - संरचना और क्वेरी / हेरफेर को परिभाषित करने के लिए एक ORM (ऑब्जेक्ट-रिलेशनल मैपिंग) परत का उपयोग करें। आपका डेटाबेस। इस तरह आपके पास कोई डुप्लिकेट तर्क नहीं है, क्योंकि सब कुछ मॉडल में परिभाषित किया जाएगा।

उदाहरण के लिए, Django (अजगर) ढांचे का उपयोग करके , आप अपने आयत डोमेन वर्ग को निम्नलिखित मॉडल के रूप में परिभाषित करेंगे :

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')

1

मैंने एक विचार समझाने के लिए एक मूर्खतापूर्ण उदाहरण लिखा है:

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

जैसा कि @ मचाडो ने कहा, इसे करने का सबसे आसान तरीका है कि आप इससे बचें और अपने मुख्य जावा में अपनी सारी प्रक्रिया करें। हालाँकि, कोड आधार दोनों के लिए कोड उत्पन्न करके अपने स्वयं को दोहराए बिना समान कोड के साथ कोड आधार होना अभी भी संभव है।

उदाहरण के लिए, कॉग का उपयोग करके तीन स्निपेट को एक सामान्य परिभाषा से उत्पन्न किया जा सकता है

स्निपेट 1:

/*[[[cog
from generate import generate_sql_table
cog.outl(generate_sql_table("rectangle"))
]]]*/
CREATE TABLE rectangles (
    width int,
    height int
);
/*[[[end]]]*/

स्निपेट 2:

public class Rectangle {
    /*[[[cog
      from generate import generate_domain_attributes,generate_domain_logic
      cog.outl(generate_domain_attributes("rectangle"))
      cog.outl(generate_domain_logic("rectangle"))
      ]]]*/
    private int width;
    private int height;
    public int area {
        return width * heigh;
    }
    /*[[[end]]]*/
}

स्निपेट 3:

/*[[[cog
from generate import generate_sql
cog.outl(generate_sql("rectangle","""
                       SELECT sum({area})
                       FROM rectangles r"""))
]]]*/
SELECT sum((r.width * r.heigh))
FROM rectangles r
/*[[[end]]]*/

एक संदर्भ फ़ाइल से

import textwrap
import pprint

# the common definition 

types = {"rectangle":
    {"sql_table_name": "rectangles",
     "sql_alias": "r",
     "attributes": [
         ["width", "int"],
         ["height", "int"],
     ],
    "methods": [
        ["area","int","this.width * this.heigh"],
    ]
    }
 }

# the utilities functions

def generate_sql_table(name):
    type = types[name]
    attributes =",\n    ".join("{attr_name} {attr_type}".format(
        attr_name=attr_name,
        attr_type=attr_type)
                   for (attr_name,attr_type)
                   in type["attributes"])
    return """
CREATE TABLE {table_name} (
    {attributes}
);""".format(
    table_name=type["sql_table_name"],
    attributes = attributes
).lstrip("\n")


def generate_method(method_def):
    name,type,value =method_def
    value = value.replace("this.","")
    return textwrap.dedent("""
    public %(type)s %(name)s {
        return %(value)s;
    }""".lstrip("\n"))% {"name":name,"type":type,"value":value}


def generate_sql_method(type,method_def):
    name,_,value =method_def
    value = value.replace("this.",type["sql_alias"]+".")
    return name,"""(%(value)s)"""% {"value":value}

def generate_domain_logic(name):
    type = types[name]
    attributes ="\n".join(generate_method(method_def)
                   for method_def
                   in type["methods"])

    return attributes


def generate_domain_attributes(name):
    type = types[name]
    attributes ="\n".join("private {attr_type} {attr_name};".format(
        attr_name=attr_name,
        attr_type=attr_type)
                   for (attr_name,attr_type)
                   in type["attributes"])

    return attributes

def generate_sql(name,sql):
    type = types[name]
    fields ={name:value
             for name,value in
             (generate_sql_method(type,method_def)
              for method_def in type["methods"])}
    sql=textwrap.dedent(sql.lstrip("\n"))
    print (sql)
    return sql.format(**fields)
हमारी साइट का प्रयोग करके, आप स्वीकार करते हैं कि आपने हमारी Cookie Policy और निजता नीति को पढ़ और समझा लिया है।
Licensed under cc by-sa 3.0 with attribution required.