基于函数的数据库

时间:2017-08-24 12:57:11

标签: postgresql version-control liquibase flyway

我正在开发一个Web应用程序,其后端高度基于数据库功能,即大部分业务逻辑都发生在Postgres PLV8函数中。 (无论好坏,我们都坚持这种结构。)

目前,我们正在使用Flyway来管理功能代码。如果一切都保持线性,这很有效。但是,想象一下以下情况:

给定这样的函数:

CREATE OR REPLACE FUNCTION public.feed_dog()
  RETURNS jsonb AS
$BODY$
   plv8.execute("SELECT prepare_cat_food()");
   plv8.execute("UPDATE dog SET hunger_status = 'good'");
$BODY$
  LANGUAGE plv8;

让我们假设这个功能已经部署到今天生产,明天我们就开始研究我们的新功能"狗粮健康检查" (对于"正常"生活在文件系统上的代码,我们为此创建一个Git分支。对于DB:也许是一个新的数据库?)。 5天后,新功能分支中的功能可能如下所示:

CREATE OR REPLACE FUNCTION public.feed_dog()
  RETURNS jsonb AS
$BODY$
   plv8.execute("SELECT dog_food_health_check()");
   plv8.execute("UPDATE dog_food_health SET 'status' = 'healthy'");
   plv8.execute("SELECT prepare_cat_food()");
   plv8.execute("UPDATE dog SET hunger_status = 'good'");
$BODY$
  LANGUAGE plv8;

但是,由于缺少重要部分,我们尚未部署。

现在,在同一天,有人发现我们混淆了狗和猫,做prepare_cat_food而不是prepare_dog_food

因此,使用Flyway迁移完成了一个修补程序,它将完全覆盖整个函数feed_dog

CREATE OR REPLACE FUNCTION public.feed_dog()
  RETURNS jsonb AS
$BODY$
   plv8.execute("SELECT prepare_dog_food()");
   plv8.execute("UPDATE dog SET hunger_status = 'good'");
$BODY$
  LANGUAGE plv8;

因此,如果我将该迁移应用于"狗食健康检查"分支在feed_dog函数中开发的所有新功能都将被Flyway迁移覆盖。 相反,我们需要的是一种Git风格的合并和冲突解决机制,在这种情况下,它会停在plv8.execute("SELECT prepare_dog_food()");行并需要人工审查,这样最终新功能将保留在那里修复了错误。

我们如何使用Flyway做到这一点?

或者它与Liquibase一起使用?它们在某种程度上是一个分支概念,但到目前为止我还不了解它如何用于非线性开发。

2 个答案:

答案 0 :(得分:1)

如果我理解你的问题,你需要结合两种技巧:

(1)将每个功能保存在源代码文件中: 您需要这个来利用版本控制系统的冲突解决功能。

(2)支持Flyway的无序迁移: 如何实现这一点在这里的一篇优秀文章中描述: http://www.jeremyjarrell.com/using-flyway-db-with-distributed-version-control/

基本上你需要做的是在每次更改函数之后,你必须在包含新版本函数的相关分支中创建一个Flyway迁移脚本。举个例子,这在实践中将如下所示:

  1. 初始状态:
  2. trunk中的feed_dog.sql

    CREATE OR REPLACE FUNCTION public.feed_dog()
      RETURNS jsonb AS
    $BODY$
       plv8.execute("SELECT prepare_cat_food()");
       plv8.execute("UPDATE dog SET hunger_status = 'good'");
    $BODY$
      LANGUAGE plv8;
    
    1. 创建健康检查分支后
    2. 分支中的feed_dog.sql:

      CREATE OR REPLACE FUNCTION public.feed_dog()
        RETURNS jsonb AS
      $BODY$
         plv8.execute("SELECT dog_food_health_check()");
         plv8.execute("UPDATE dog_food_health SET 'status' = 'healthy'");
         plv8.execute("SELECT prepare_cat_food()");
         plv8.execute("UPDATE dog SET hunger_status = 'good'");
      $BODY$
        LANGUAGE plv8;
      
      1. 在主干中应用修复程序后:
      2. 主干中的feed_dog.sql:

        CREATE OR REPLACE FUNCTION public.feed_dog()
          RETURNS jsonb AS
        $BODY$
           plv8.execute("SELECT prepare_dog_food()");
           plv8.execute("UPDATE dog SET hunger_status = 'good'");
        $BODY$
          LANGUAGE plv8;
        

        后备箱中的V20170830_124459_hotfix_dogfood.sql: 与feed_dog.sql相同的内容

        1. 将trunk的更改合并到功能分支后:
        2. 功能分支中的feed_dog.sql:

          CREATE OR REPLACE FUNCTION public.feed_dog()
            RETURNS jsonb AS
          $BODY$
             plv8.execute("SELECT dog_food_health_check()");
             plv8.execute("UPDATE dog_food_health SET 'status' = 'healthy'");
             plv8.execute("SELECT prepare_dog_food()");   -- Merged here
             plv8.execute("UPDATE dog SET hunger_status = 'good'");
          $BODY$
            LANGUAGE plv8;
          

          功能分支中的V20170831_135559_merged.sql: 与feed_dog.sql相同的内容

          feed_dog.sql是函数feed_dog()的源代码文件。根据上述文章中的建议,“V”文件是具有命名约定的迁移脚本。 重新集成功能分支后,Flyway将执行V20170830_124459_hotfix_dogfood.sql,然后执行V20170831_135559_merged.sql。

答案 1 :(得分:1)

Wombat's answer's理解......

为基础

在每个函数的自己的文件中使用repeatable migrations。然后Flyway将在每次更改时应用它们。使用版本控制管理每个功能文件,例如R__feed_dog.sql。这样可以避免每次都将更新的函数复制并粘贴到版本化的迁移中。

以往经验中的注释:

  • 如果您在函数之间存在依赖关系,则在描述的开头添加一个数字,以强制flyway按顺序应用,例如R__010_framework.sql, R__200_use_of_framework.sql。这在进入空架构时最为重要
  • 启用调试日志记录,该日志记录将在日志中输出可重复迁移的内容,这有助于审核
  • Flyway在版本化之后应用可重复迁移,因此如果版本化迁移使用这些功能,则需要小心