应用与插件自动升级机制说明?
李小翔
2015-08-08 19:04

内容列表

供稿人：张文秀
插件或应用更新后，经常需要修改老的数据或数据库结构、新增基础数据（如配置、数据字典）等，部署到服务器时，为了实现自动化升级，我们通常在 XxxxBootStrap.groovy 中实现数据的升级，但是，通过 BootStrap 升级存在下面的问题：
· 	运行时间晚于 Grails 的数据库结构升级，因此，无法提前对数据进行预处理
· 	升级代码需要写很多判断条件（如XX不存在、则新增XX），影响每次启动性能
· 	升级代码容易变得过长，不易维护
· 	升级代码的版本不好控制
· 	如果中间升级失败，不易回滚、定位、重新执行
· 	因此，在 BroFramework 中，新增了一种 Updater 的 Artefact，用于编写代码自动升级；并且将每次升级的插件名、应用名、版本号、升级成功与否等信息记录在数据表 brofwk_update_log 中。
以插件 BroBPM 为例，插件的 grails-app\ conf\  目录下有一个 update 文件夹，包下有多个前缀为 “Updater” 的 groovy 文件，分别是 UpdaterXXXInitiator 和 UpdaterXXX，升级的代码主要写在这些文件。
此外，子文件夹 resource 下面又存放了一些脚本，这些脚本会在 UpdaterXXXInitiator 和 UpdaterXXXX 中被调用执行；这些资源并不是必要的，而是根据情况自行添加、调用。
除了类名前缀外，这些升级器类的属性、方法规范如下面的模板所示：
class UpdaterXxxx { 
   def initiator = true/false
   
   def version = ”4.1.0.92940”
   
   def pluginName = ”broBpm”
   
   def description = ”......”
   def beforeDbUpdate( Sql sqlInst, String dbName, String dbSchema ) { 
      ....
   } 
   
   def beforeDbUpdate_auditlog( Sql sqlInst, String dbName, String dbSchema ) { 
       ....
   } 
   
   def beforeBootStrap() { 
       ....
   } 
   
   def afterBootStrap() { 
       ....
   } 
   
} 
属性：
initiator：是否为始化版本
每个应用或插件中只允许有一个 true 的升级器，类名我们通常命名为 UpdaterXXXInitiator，该升级器中主要是一些基础数据初始化代码，并且只是在应用第一次启动的时候执行一次，之后启动就不会再执行。
version：升级器的版本号
格式为不超过四段的数字，如 w.x.y.z、x.y，前三段每段最多3个字符、最后一段最多10个字符，共22个字符。
目前版本号组成是插件自己的版本号+svn版本号，假如你做了一个新功能，在升级的时候需要初始化一个新的系统参数，比如BPM的版本是4.1.0，提交了代码之后查看svn版本是92940，此时UpdaterXXX版本号就要更新为 4.1.0. 92940。
版本号是-1 时则禁用，需要注意的是，如果修改了升级器类，则必须升级 version，否则新的升级代码在启动时不会被执行的。
pluginName：插件名称
默认为 app_应用名，即不属于任何插件
description：升级器描述
事件：
beforeDbUpdate( Sql sqlInst, String dbName, String dbSchema )
在 Grails 自动更新数据库表结构前执行的。
此方法支持多数据源，beforeDbUpdate 表示更新默认数据源，beforeDbUpdate_auditlog 表示更新名为 auditlog 的数据源，这个是和dataSource中数据源的配置是类似的。
参数Sql sqlInst已初始化的数据库连接，用来执行sql；参数String dbName数据库名称，如数据库是oracle则这个参数的值是oracle，因为不同数据库的sql有差异，通过这个参数就可以判断不同数据库的时候执行不同的sql；参数String dbSchema数据库名，用于拼sql，如表名前缀：select * from ${ dbSchema} .abc。
beforeBootStrap()
在所有的 BootStrap 类和 bootStrapInit 方法之前执行
afterBootStrap()
在所有的 BootStrap 类和 bootStrapInit 方法之前执行
下面是一个初始化升级器的示例：
package update
class UpdaterAppInitiator { 
   def initiator = true
   def version = ”1.0.0.1234”
   def description = ”初始化应用 Foobar，版本 1.0”
   
   def settingService
   
   def beforeBootStrap() { 
        settingService.create(”foobar.foo.bar”, ....).save()
       ....
   } 
} 
下面是一个非初始化的升级器的示例：
package update
class UpdaterApp11 { 
   def version = ”1.1.0.12345”
   def description = ”版本 1.0 到 1.1 的升级器”
   
   def beforeDbUpdate( Sql sqlInst, String dbName, String dbSchema ) { 
       try { 
           if ( dbName == ”oracle” ) { 
                sqlInst.executeUpdate(”ALTER TABLE ...”)
           }  else { 
                sqlInst.executeUpdate(”ALTER TABLE ...”)
           } 
       }  catch(Exception e) { } 
   } 
   
   def beforeDbUpdate_xxxx( Sql sqlInst, String dbName, String dbSchema ) { 
       ....
   } 
   
   def beforeBootStrap() { 
       if ( Xxxx.countByXX() == 0 ) { 
           new Xxxx(....).save()
       } 
       if ( .... ) { 
           ....
       } 
   } 
   
}