在数据库中存储业务逻辑

34
我们希望编写一些业务逻辑规则,以在某些数据上构建报告。不确定将它们存储在数据库MySQL中是否是最佳选择。

enter image description here

它可以有一系列规则,然后是结果的语句,如上所示。

底层数据在规则中的使用有多大?它有多复杂?对于一个表中有10列的1000行与存储在复杂数据结构中的数十亿个交易,有不同的处理方法。 - Gordon Linoff
@GordonLinoff 这是最近60-90天的分析数据,大约有12列。 - Harsha M V
您想要包含多复杂的业务规则?它们只是对列进行基本筛选条件和一些新计算值的简单过滤条件吗?还是更加复杂? - Gordon Linoff
@GordonLinoff http://i.imgur.com/KOFPY.jpg 阴影部分会重复几次,最多可能是3-5次。 - Harsha M V
非常抱歉,我遇到了浏览器问题。规则的数据存储在单个表中吗?例如,如何计算独立访客的变化?是否有一列包含此信息?您是从两个不同的行获取差异吗? - Gordon Linoff
每行都有日期和访客、独立访客、移动访客等数据。因此,用户增长是在两个由日期和唯一的profile_id标识的行之间计算的。 - Harsha M V
11个回答

21

用任何编程语言编写的业务逻辑都可以用于生成报告。并且可以使用数据库中的数据生成报告。

反对将业务逻辑存储在数据库中

我非常重视表达的力量,而且我并不认为SQL空间具有很强的表达能力。为最适当的任务使用你手头最好的工具。与逻辑和高级概念的纠缠最好在最高层次上完成。因此,存储和大规模数据操作最好在服务器层面上完成,可能是在存储过程中。

但这取决于情况。如果您有多个应用程序与一个存储机制交互,并且您希望确保它保持完整性和工作流程,那么您应该将所有逻辑卸载到数据库服务器中。或者,准备管理多个应用程序中的并发开发。

来源: 存储过程中业务逻辑的支持和反对

另请参阅:

  1. 数据库中的业务逻辑
  2. 存储过程中的业务逻辑
  3. 在数据库中存储条件逻辑表达式/规则

如果您有多个应用程序与一个存储机制进行交互,并且希望确保其完整性和工作流程,则应将所有逻辑卸载到数据库服务器中。-- 在这种情况下,更明智的选择肯定是在存储之上构建共享的高级服务。 - brianpeiris

16

模型

CREATE TABLE businessRule (
  id INT NOT NULL ,
  name VARCHAR(32) NOT NULL ,
  description VARCHAR(255) NULL ,
  statement VARCHAR(255) NOT NULL ,
  PRIMARY KEY (id) )
ENGINE = InnoDB;

CREATE TABLE leftOperand (
  id INT NOT NULL ,
  value VARCHAR(255) NOT NULL ,
  PRIMARY KEY (id) )
ENGINE = InnoDB;

CREATE TABLE ruleItem (
  id INT NOT NULL ,
  businessRuleId INT NOT NULL ,
  operator ENUM('if','and','or','not') NOT NULL ,
  loperand INT NOT NULL ,
  comparator ENUM('<','=','>') NOT NULL ,
  roperand VARCHAR(255) NOT NULL ,
  roperand_ispercentage TINYINT(1)  NOT NULL ,
  PRIMARY KEY (id) ,
  INDEX businessRule_FK (businessRuleId ASC) ,
  INDEX leftOperand_FK (loperand ASC) ,
  CONSTRAINT businessRule_FK
    FOREIGN KEY (businessRuleId )
    REFERENCES mydb.businessRule (id )
    ON DELETE CASCADE
    ON UPDATE RESTRICT,
  CONSTRAINT leftOperand_FK
    FOREIGN KEY (loperand )
    REFERENCES mydb.leftOperand (id )
    ON DELETE RESTRICT
    ON UPDATE RESTRICT)
ENGINE = InnoDB;

请分享一个如何将数据插入这些表的示例。 - sachingupta

15

批评“软编码”商业逻辑的一个观点:http://thedailywtf.com/Articles/Soft_Coding.aspx

我们发现自己在进行软编码的原因是因为我们害怕改变。不是普通的对改变的恐惧,而是害怕我们编写的代码将必须由于业务规则的更改而进行更改。这是一个相当愚蠢的恐惧。软件的整个意义(也就是“软件”的意思)就是它可以改变,而且它确实会改变。唯一保护你的软件免受业务规则更改的影响的方法是构建一个完全通用的程序,该程序没有任何业务规则,但可以实现任何规则。哦,他们已经建立了这个工具。它被称为C++、Java、C#、Basic和COBOL。”


10
我能提供的只是解决这个问题的方法,而不是答案本身。
设计一个用于存储像这样复杂数据的数据库的一般方法是先设计将它们作为对象保存在内存中的方式,然后尝试相应地设计数据库。毕竟,您将在编程语言中评估规则。该过程如下:首先是类图, Class diagram 然后是将其转换为ERD: enter image description here 一旦您有了用于存储/重新加载对象的数据库结构,您可以简单地创建您的类,以便每个对象负责加载/存储自己。
例如,如果您想将语句 a + b * -c 存储到数据库中,它可以被翻译成以下插入操作:
-- c
INSERT INTO statement (statement_id) VALUES (1);
INSERT INTO operand (statement_id, type) VALUES (1, 'double');
-- - (minus)
INSERT INTO statement (statement_id) VALUES (2);
INSERT INTO operator (statement_id, type) VALUES (2, 'minus');
-- -c
INSERT INTO binary (operator_statement_id, operand_statement_id) VALUES (2, 1);
-- b
INSERT INTO statement (statement_id) VALUES (3);
INSERT INTO operand (statement_id, type) VALUES (3, 'double');
-- * (multiply)
INSERT INTO statement (statement_id) VALUES (4);
INSERT INTO operator (statement_id, type) VALUES (4, 'multiply');
-- b * -c
INSERT INTO unary (operator_statement_id, operand_statement_id1, operand_statement_id2) VALUES (4, 3, 2);
-- a
INSERT INTO statement (statement_id) VALUES (5);
INSERT INTO operand (statement_id, type) VALUES (5, 'double');
-- + (plus)
INSERT INTO statement (statement_id) VALUES (6);
INSERT INTO operator (statement_id, type) VALUES (6, 'sum');
-- a + b * -c
INSERT INTO unary (operator_statement_id, operand_statement_id1, operand_statement_id2) VALUES (6, 5, 4);

Mehran,非常有趣的方法。你能提供这个数据库结构的一些逻辑和数据插入示例吗?谢谢。 - Paktas
请问,如果我想要获取a+b*-c的值,应该使用什么样的选择查询语句? - sachingupta
@saching 我认为这里有一个误解。在这个解决方案中,类似 a+b*-c 的语句不会在数据库层面进行评估,因此也没有 SELECT 查询。你需要做的是提取与你的语句相关的所有记录(使用 statement_id),并在应用程序层面(使用任何编程语言)重构语句对象,然后对语句进行评估。 - Mehran
感谢@Mehran的解释。我甚至不能制作那个选择查询,以获取方程式的结构。你能帮帮我吗? - sachingupta
就像我之前评论中所说的那样,它可能只是这么简单:SELECT * FROM <table name> WHERE statement_id = ? - Mehran

6
我认为首先需要考虑的是是否应该将规则放入数据库中。
数据库是一种笨重的解决方案,通常并不需要。我曾经使用过各种形式的规则引擎,包括基于数据库的引擎,我可以告诉你,这可能会变得非常令人沮丧和低效。其中一个最大的错误是尝试编写自己的特定规则语言,并使用它来通过数据库驱动条件逻辑。至少要使用已被证明的语言(如Python、JavaScript等)并将其嵌入其中。
更好的方法是,如果规则足够复杂,我个人更喜欢使用Excel电子表格。我们使用它来进行自动化(处理基于生效日期的可变逻辑等),我们还将相当复杂的保险费率逻辑编译为Perl脚本,通过Web服务接口使用此产品:http://decisionresearch.com/products/rating.html
与将逻辑存储在数据库中相比,例如Excel电子表格:
  1. 与Excel相比,数据库中的逻辑更难以测试和开发,因为Excel提供即时反馈。
  2. 与Excel相比,数据库的表达能力较弱。
  3. 您可以对Excel进行着色并添加各种其他视觉提示,以使错误条件等真正突出。
当然,正如您可以想象的那样,基于Web服务的Excel规则引擎并不适合每种情况。它也不是唯一的解决方案。
我的意思是确保在可用性/表达能力/可测试性/性能方面做出正确的权衡。在我工作的地方,正确和高效比执行速度更重要,因此我们使用Excel/Web服务。
为了扩展slavik262的评论,最终您真正想要通过规则引擎实现的是抽象和泛化,以最小化移动部件并增加可靠性、可测试性和可理解性。根据我的经验,与仅仅制作基于Java的规则相比,数据库规则引擎通常是次优的。只要它们被沙盒化并且组织得当,并且隐藏在一个通用和一致的接口后面,那么它们就可以很好地工作。
在我的公司中,我们会根据规则的规模和更改频率来决定采用哪种方法。对于保险费率-毫无疑问是Excel。一些特定于州的逻辑?接口Java规则文件足以胜任。

5

如果您不需要根据规则的组件执行搜索,则可以将规则存储在数据库中的两个字段中。一个字段用于执行语句的条件,另一个字段用于执行语句本身。

id, name, description, condition, statement

您的规则可以使用JSON或类似格式进行存储。
我需要定义一些术语。有原子术语,系统值与用户输入值进行比较并评估为真/假,以及复合术语,使用逻辑运算符组合的术语。
原子术语中,var表示系统将提供的值(例如访客数量或唯一访客数量)。比较确定如何将 varvalue 进行评估。当 var 和 value 都是数字时,比较可以是“<”,“<=”,“=”,“>=”,或“>”。 当var和value都是字符串时,比较可以是“equals”,“begins with”,“ends with”,或“contains”。原子术语可以存储如下:
{ var: varName, comp: comparison, value: numberOrString }

您可以使用以下格式存储由连接词,或者,非运算符(and/or/not)组成的复杂术语。
// Conjunction
{ op: "and", terms: [ term, ..., term ] }

// Disjunction
{ op: "or", terms: [ term, ..., term ] }

// Negation
{ op: "not", term: term }

您可以使用这些方法构建评估为真/假的语句。以下是一个示例:
{ op: "and", terms: [
    {op "or", terms: [
        { field: "numVisitors", comp: ">", value: 1000 },
        { field: "numUniqueVisitors", comp: ">=" 100 }
    ]},
    { op: "not", term: {
        { field: "numVisitors", comp: "<", value: 500 }
    }}
]}

以上示例在访问者数量大于1000或独特访问者数量大于等于100,且访问者数量不少于500时等同于true。

当规则评估为true时,您可以执行所谓的“语句”。


4
所以,如果我理解正确,您想使用前端让用户动态创建逻辑,该逻辑将应用于查询(根据使用的规则在运行时动态构建where子句)?
如果是这样,您需要非常具体地说明他们可以选择哪些条件(更改哪个值(列)),因此他们只能针对数据集中存在的列设置条件规则。
如果我理解您的问题正确,我建议首先绘制出您希望他们能够选择条件的表/列。这将是您设计规则的网页的控件。
但是,如果您只是询问如何在选定规则后将其存储在数据库中,我建议将其存储在单个包含以下内容的表中:
ID  |  RuleSetName         |  Table     |  Column      |  Comparison  |  Value   |  Percentage  |  Notes  |  CreatedDate  |  Created By
1   |  'VisitorAnalytics'  |  Visitors  |  SUM(Views)  |  >           |  null    |  10          |  n/a    |  1/1/2012     |  JohnDoe

一旦这些记录被创建,您将通过将表格注入到from子句中,将列注入到where子句中来使用它们以用于动态sql。

我知道这可能听起来很困惑,但是您所要求的是一个相当复杂的解决方案。 但最终,您只需要在一个地方存储规则,以便可以循环通过并动态生成和执行SQL来生成报告。 希望这能为您指明正确的方向。


2
我猜测规则的目的是从现有的数据库表(或表)中命名计算字段。否则,仅用于报告目的,您可以将数据转储到Excel中,让用户使用Excel函数和数据透视表来实现其目的。
关键问题是您将如何将规则转化为行动。如果目的仅是存储业务规则,以便您可以创建业务规则报告,则SQL中的简单数据结构就足够了。
然而,如果您想将规则转换为代码,则需要考虑代码将在何处运行。当数据存储在SQL中时,您有几个选项:
- 创建提取“业务规则”结果的SQL代码。 - 创建一个用户定义的函数来解析和执行业务规则。 - 将数据提取到另一个环境(例如C++或C#)并在那里运行代码。
我倾向于第一种。主要原因是它将工具限制在一个:SQL。
我不确定您的规则正在做什么;关键是“语句”组件所做的事情。让我假设这是一个常量或可计算数据的表达式。在这种情况下,您的规则开始看起来很像一个case语句。一个警告是该语句可能需要查看数据表中的多行(以处理随时间变化的情况)。
我的建议是将这些规则存储在数据库中。这种存储方式将允许您使用SQL编码从一系列业务规则构建查询。 Mysql允许动态SQL(现在)。如果不了解底层表和规则的更多信息,很难提供更多信息。
我可以说,我设计了一个用于情景分析的更复杂的系统。场景本身存储在电子表格中,以一系列表格、常量等形式存在 - 很像您的业务规则。该系统通过使用SQL(和一些Excel)将场景的电子表格表示转换为(巨大的)查询来工作。然后它可以运行查询以生成相关报告。这个系统已被证明是灵活、高效和强大的。

2
一种简单的方法是使用对象数据库管理系统(OODBMS)。在那里,方法被封装到对象中的插槽中,并且它们甚至可以在数据库中执行(如触发器)。
现在,如果您坚持要使用SQL数据库,您可以使用动态编程语言,并拥有一个用于存储代码的表,可能与其他表或行相关联。
几年前,我看到了阿尔及利亚政府税务系统的招标书,在其中他们计划将业务规则(税收规则)存储为RDBMS中的Visual Basic代码。
您可以选择任何一种您可以轻松嵌入解释器的语言来进行编写应用程序(Common Lisp http://ecls.sourceforge.net;或者如果您在Java中编写应用程序,则可以选择http://common-lisp.net/project/armedbear/),Lua,Javascript,Scheme等。
它会倾向于选择Common Lisp或Scheme,因为使用这些语言,您可以轻松地为业务规则编写DSL。
给出的示例可以编写为符号表达式,例如:
(rule :name "RuleName"
      :description "Some description"
      :body (if (and (< (change-in total-visitor)   (percent 10))
                     (> (change-in unique-visitors) (percent 2)))
                (do-something)))

在Lisp中,这样的符号表达式可以使用PRINT或PRINT-TO-STRING操作符进行可读性打印,以便您可以将此表达式插入SQL数据库中:
insert into rules (id,expression) values (42,"(rule :name \"RuleName\"
      :description \"Some description\"
      :body (if (and (< (change-in total-visitor)   (percent 10))
                     (> (change-in unique-visitors) (percent 2)))
                (do-something)))");

你可以从SQL中获取它,使用lisp的READ或READ-FROM-STRING操作符将其作为符号表达式读取回来,然后再使用正确的DSL,通过lisp的EVAL操作符进行评估:
;; with the right DSL written:
(eval (read-from-string (sql-select (expression) :where (= id 42))))

0

使用存储过程的唯一可能好处是可以从使用不同技术(如Python和Java)的应用程序访问数据库。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接