幸福框架:全站式代码生成器示例
背景
一直在做企业应用,也一直在使用代码生成器,代码生成器分两个维度,一个维度是”主动或被动“,另外一个维度是”运行时或编译时“,这两种维度会有四种组合,每个组合都有其应用的场景,今天我就介绍一下Happy是如何使用代码生成器的。
概念介绍
主动:可以生成多次,会”主动“的合并生成代码和用户自定义代码,C#的部分类和ExtJs的扩展类就是,通过一些文本合并工具也是可以实现的。
被动:不可以生成多次,每次生成都会覆盖用户自定义的代码。
运行时:运行时的代码生成,也叫元编程,动态语言几乎都支持,静态语言可以使用动态编译。
编译时:编译时的代码生成,是狭义的代码生成器的代名词。
还有一点需要说明的,如果是基于应用框架的代码生成器,生成的代码会非常少。
示例
编译时主动+编译时被动代码生成器代码
这里的生成器我是基于NodeJs开发的,T4、CodeSmith和其它代码生成器也不错。
这里的编译时主动是指每次都会生成,用户如果希望个性化代码,就写C#的部分类或ExtJs的扩展类。
这里的编译时被动是指每次都会覆盖用户配置,当然你可以指定只生成一次。
生成后的项目
Application.Generator.cs
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 using Happy.Command; 8 using Happy.Application; 9 using Happy.Query.PetaPoco; 10 using Demo.Domain; 11 12 namespace Demo.Application 13 { 14 15 public partial class TestGridService : AggregateService<IDemoUnitOfWork, ITestGridRepository, TestGrid> { } 16 17 public partial class TestGridCreateCommand : SimpleCreateCommand<TestGrid> { } 18 19 public partial class TestGridCreateCommandHandler : SimpleCreateCommandHandler<TestGridService, TestGrid, TestGridCreateCommand> { } 20 21 public partial class TestGridUpdateCommand : SimpleUpdateCommand<TestGrid> { } 22 23 public partial class TestGridUpdateCommandHandler : SimpleUpdateCommandHandler<TestGridService, TestGrid, TestGridUpdateCommand> { } 24 25 public partial class TestGridDeleteCommand : SimpleDeleteCommand<TestGrid> { } 26 27 public partial class TestGridDeleteCommandHandler : SimpleDeleteCommandHandler<TestGridService, TestGrid, TestGridDeleteCommand> { } 28 29 public partial class TestGridDynamicQueryService : PetaPocoDynamicQueryService 30 { 31 public TestGridDynamicQueryService() : 32 base(@"Data Source=(LocalDB)\v11.0;AttachDbFilename=" + AppDomain.CurrentDomain.BaseDirectory + @"App_Data\TestDatabase.mdf;Integrated Security=True;Connect Timeout=30", "System.Data.SqlClient", "TestGrids") 33 { } 34 } 35 36 public partial class TestTreeService : AggregateService<IDemoUnitOfWork, ITestTreeRepository, TestTree> { } 37 38 public partial class TestTreeCreateCommand : SimpleCreateCommand<TestTree> { } 39 40 public partial class TestTreeCreateCommandHandler : SimpleCreateCommandHandler<TestTreeService, TestTree, TestTreeCreateCommand> { } 41 42 public partial class TestTreeUpdateCommand : SimpleUpdateCommand<TestTree> { } 43 44 public partial class TestTreeUpdateCommandHandler : SimpleUpdateCommandHandler<TestTreeService, TestTree, TestTreeUpdateCommand> { } 45 46 public partial class TestTreeDeleteCommand : SimpleDeleteCommand<TestTree> { } 47 48 public partial class TestTreeDeleteCommandHandler : SimpleDeleteCommandHandler<TestTreeService, TestTree, TestTreeDeleteCommand> { } 49 50 public partial class TestTreeDynamicQueryService : PetaPocoDynamicQueryService 51 { 52 public TestTreeDynamicQueryService() : 53 base(@"Data Source=(LocalDB)\v11.0;AttachDbFilename=" + AppDomain.CurrentDomain.BaseDirectory + @"App_Data\TestDatabase.mdf;Integrated Security=True;Connect Timeout=30", "System.Data.SqlClient", "TestTrees") 54 { } 55 } 56 57 }
运行时代码生成
目前只做了基于JS的运行时代码生成。
Happy.metadata.Manager.js
1 /** 2 * 元数据管理器,主要完成元数据的合并和根据元数据生成常用配置和类型。 3 * 4 * @class Manager 5 * @namespace Happy.metadata 6 * @constructor 7 * @param {Object} config 8 * @param {Array} config.metadatas 要合并的元数据对象数组,索引越大优先级越高。 9 */ 10 Ext.define('Happy.metadata.Manager', { 11 requires: [ 12 'Happy.metadata.DatabaseTypeMapper', 13 'Happy.data.proxy.Ajax' 14 ], 15 16 /** 17 * 要合并的元数据对象数组,索引越大优先级越高。 18 * 19 * @private 20 * @property metadatas 21 * @type Array 22 */ 23 24 /** 25 * 合并后的元数据。 26 * 27 * @private 28 * @property metadata 29 * @type Object 30 */ 31 32 /** 33 * 根据合并后的元数据生成的表单控件配置数组。 34 * 35 * @private 36 * @property formEditors 37 * @type Array 38 */ 39 40 /** 41 * 根据合并后的元数据生成的表格列配置数组。 42 * 43 * @private 44 * @property gridColumns 45 * @type Array 46 */ 47 48 /** 49 * 根据合并后的元数据生成的模型类。 50 * 51 * @private 52 * @property model 53 * @type Ext.data.Model 54 */ 55 56 /** 57 * 根据合并后的元数据生成的仓储类。 58 * 59 * @private 60 * @property store 61 * @type Ext.data.Store 62 */ 63 64 /** 65 * @method constructor 66 */ 67 constructor: function (config) { 68 var me = this; 69 70 me.metadatas = config.metadatas; 71 72 me.initMetadata(); 73 74 me.initFormEditors(); 75 76 me.initGridColumns(); 77 78 if (me.isTree()) { 79 me.initTreeColumns(); 80 } 81 82 me.defineModel(); 83 me.defineStore(); 84 85 if (me.isTree()) { 86 me.defineTreeModel(); 87 me.defineTreeStore(); 88 } 89 }, 90 91 /** 92 * 合并并初始化元数据。 93 * @private 94 * @method initMetadata 95 */ 96 initMetadata: function () { 97 var me = this; 98 99 me.metadata = {}; 100 101 Ext.Array.each(me.metadatas || [], function (metadata) { 102 Ext.merge(me.metadata, metadata); 103 }); 104 }, 105 106 /** 107 * 获取合并后的元数据。 108 * @method getMetadata 109 * @return {Object} 110 */ 111 getMetadata: function () { 112 var me = this; 113 114 return me.metadata; 115 }, 116 117 /** 118 * @private 119 * @method getMetadata 120 * @return {Object} 121 */ 122 isTree: function () { 123 var me = this; 124 125 return !!me.metadata.columns['NodePath']; 126 }, 127 128 /** 129 * 初始化表单控件配置数组。 130 * @private 131 * @method initFormEditors 132 */ 133 initFormEditors: function () { 134 var me = this; 135 136 var columns = Ext.Object.getValues(me.metadata.columns); 137 138 me.formEditors = Ext.Array.map(columns, function (column) { 139 var dataTypeName = column.dataType.typeName; 140 var editorConfig = me.getDatabaseTypeMapper().getFormEditorConfig(dataTypeName); 141 142 return Ext.apply({ 143 name: column.name, 144 fieldLabel: column.text || column.name 145 }, editorConfig); 146 }); 147 }, 148 149 /** 150 * 获取表单控件配置数组。 151 * @method getFormEditors 152 * @return {Array} 153 */ 154 getFormEditors: function () { 155 var me = this; 156 157 return me.formEditors; 158 }, 159 160 /** 161 * 初始化表格列配置数组。 162 * @private 163 * @method initGridColumns 164 */ 165 initGridColumns: function () { 166 var me = this; 167 168 var columns = Ext.Object.getValues(me.metadata.columns); 169 170 me.gridColumns = Ext.Array.map(columns, function (column) { 171 var dataTypeName = column.dataType.typeName; 172 var columnConfig = me.getDatabaseTypeMapper().getGridColumnConfig(dataTypeName); 173 174 return Ext.apply({ 175 dataIndex: column.name, 176 text: column.text || column.name 177 }, columnConfig); 178 }); 179 }, 180 181 /** 182 * 获取表格列配置数组。 183 * @method getGridColumns 184 * @return {Array} 185 */ 186 getGridColumns: function () { 187 var me = this; 188 189 return me.gridColumns; 190 }, 191 192 /** 193 * 获取表单控件配置数组。 194 * @method getFormEditors 195 * @return {Array} 196 */ 197 getFormEditors: function () { 198 var me = this; 199 200 return me.formEditors; 201 }, 202 203 /** 204 * 初始化树表格列配置数组。 205 * @private 206 * @method initTreeColumns 207 */ 208 initTreeColumns: function () { 209 var me = this; 210 211 var columns = Ext.Object.getValues(me.metadata.columns); 212 213 me.treeColumns = Ext.Array.map(columns, function (column) { 214 var dataTypeName = column.dataType.typeName; 215 var columnConfig = me.getDatabaseTypeMapper().getGridColumnConfig(dataTypeName); 216 217 return Ext.apply({ 218 dataIndex: column.name, 219 text: column.text || column.name 220 }, columnConfig); 221 }); 222 }, 223 224 /** 225 * 获取树表格列配置数组。 226 * @method getTreeColumns 227 * @return {Array} 228 */ 229 getTreeColumns: function () { 230 var me = this; 231 232 return me.treeColumns; 233 }, 234 235 /** 236 * 定义模型类。 237 * @private 238 * @method defineModel 239 */ 240 defineModel: function () { 241 var me = this; 242 243 me.model = Ext.define(me.getModelName(), { 244 extend: 'Ext.data.Model', 245 fields: me.getModelFields(), 246 idProperty: 'Id', 247 proxy: { 248 type: 'happy-ajax', 249 api: { 250 create: '/' + me.metadata.singular + 'Command/Create', 251 read: '/' + me.metadata.singular + 'DynamicQuery/Page', 252 update: '/' + me.metadata.singular + 'Command/Update', 253 destroy: '/' + me.metadata.singular + 'Command/Delete' 254 }, 255 reader: { 256 type: 'json', 257 root: 'items', 258 idProperty: 'Id', 259 messageProperty: 'message' 260 }, 261 writer: { 262 type: 'json', 263 encode: true, 264 root: 'item' 265 } 266 }, 267 268 getTableName: function () { 269 return me.metadata.name; 270 } 271 }); 272 }, 273 274 /** 275 * 获取定义的模型类。 276 * @method getModel 277 * @return {Ext.data.Model} 278 */ 279 getModel: function () { 280 var me = this; 281 282 return me.model; 283 }, 284 285 /** 286 * 定义树模型类。 287 * @private 288 * @method defineTreeModel 289 */ 290 defineTreeModel: function () { 291 var me = this; 292 293 var fields = me.getModelFields(); 294 295 fields.push({ 296 name: 'parentId', 297 type: 'string', 298 defaultValue: null, 299 useNull: false, 300 persist: false 301 }); 302 303 fields.push({ 304 name: 'leaf', 305 type: 'bool', 306 defaultValue: false, 307 persist: false 308 }); 309 310 me.treeModel = Ext.define(me.getTreeModelName(), { 311 extend: 'Ext.data.Model', 312 fields: fields, 313 idProperty: 'Id', 314 proxy: { 315 type: 'happy-ajax', 316 api: { 317 create: '/' + me.metadata.singular + 'Command/Create', 318 read: '/' + me.metadata.singular + 'DynamicQuery/Page', 319 update: '/' + me.metadata.singular + 'Command/Update', 320 destroy: '/' + me.metadata.singular + 'Command/Delete' 321 }, 322 reader: { 323 type: 'json', 324 root: 'items', 325 idProperty: 'Id', 326 messageProperty: 'message' 327 }, 328 writer: { 329 type: 'json', 330 encode: true, 331 root: 'item' 332 } 333 }, 334 335 getTableName: function () { 336 return me.metadata.name; 337 } 338 }); 339 }, 340 341 /** 342 * 获取定义的树模型类。 343 * @method getTreeModel 344 * @return {Ext.data.Model} 345 */ 346 getTreeModel: function () { 347 var me = this; 348 349 return me.treeModel; 350 }, 351 352 /** 353 * 获取定义模型类需要的字段数据。 354 * @private 355 * @method getModelFields 356 * @return {Array} 357 */ 358 getModelFields: function () { 359 var me = this; 360 361 var columns = Ext.Object.getValues(me.metadata.columns); 362 363 return Ext.Array.map(columns, function (column) { 364 var dataTypeName = column.dataType.typeName; 365 var fieldConfig = me.getDatabaseTypeMapper().getModelFieldConfig(dataTypeName); 366 367 return Ext.apply({ 368 name: column.name 369 }, fieldConfig); 370 }); 371 }, 372 373 /** 374 * 定义仓储类。 375 * @private 376 * @method defineGridStore 377 */ 378 defineStore: function () { 379 var me = this; 380 381 me.store = Ext.define(me.metadata.namespace + '.' + me.metadata.singular.toLowerCase() + '.store.' + me.metadata.singular, { 382 extend: 'Ext.data.Store', 383 model: me.getModelName(), 384 385 getTableName: function () { 386 return me.metadata.name; 387 } 388 }); 389 }, 390 391 /** 392 * 获取定义的仓储类。 393 * @method getGridStore 394 * @return {Ext.data.Store} 395 */ 396 getStore: function () { 397 var me = this; 398 399 return me.store; 400 }, 401 402 /** 403 * 定义树仓储类。 404 * @private 405 * @method defineTreeStore 406 */ 407 defineTreeStore: function () { 408 var me = this; 409 410 me.treeStore = Ext.define(me.metadata.namespace + '.' + me.metadata.singular.toLowerCase() + '.treestore.' + me.metadata.singular, { 411 extend: 'Ext.data.TreeStore', 412 413 defaultRootId: '00000000-0000-0000-0000-000000000000', 414 model: me.getTreeModelName(), 415 root: me.metadata.treeRoot || { 416 text: me.metadata.singular, 417 expanded: true 418 }, 419 proxy: { 420 type: 'happy-ajax', 421 api: { 422 create: '/' + me.metadata.singular + 'Command/Create', 423 read: '/' + me.metadata.singular + 'DynamicQuery/ReadNode', 424 update: '/' + me.metadata.singular + 'Command/Update', 425 destroy: '/' + me.metadata.singular + 'Command/Delete' 426 }, 427 reader: { 428 type: 'json', 429 root: 'items', 430 idProperty: 'Id', 431 messageProperty: 'message' 432 }, 433 writer: { 434 type: 'json', 435 encode: true, 436 root: 'item' 437 } 438 }, 439 440 getTableName: function () { 441 return me.metadata.name; 442 } 443 }); 444 }, 445 446 /** 447 * 获取定义的树仓储类。 448 * @method getTreeStore 449 * @return {Ext.data.Store} 450 */ 451 getTreeStore: function () { 452 var me = this; 453 454 return me.treeStore; 455 }, 456 457 /** 458 * 获取定义模型类需要的类名。 459 * @private 460 * @method getModelName 461 * @return {String} 462 */ 463 getModelName: function () { 464 var me = this; 465 466 return me.metadata.namespace + '.' + me.metadata.singular.toLowerCase() + '.model.' + me.metadata.singular; 467 }, 468 469 /** 470 * 获取定义树模型类需要的类名。 471 * @private 472 * @method getTreeModelName 473 * @return {String} 474 */ 475 getTreeModelName: function () { 476 var me = this; 477 478 return me.metadata.namespace + '.' + me.metadata.singular.toLowerCase() + '.treemodel.' + me.metadata.singular; 479 }, 480 481 /** 482 * 获取数据库类型映射器。 483 * @private 484 * @method getModelName 485 * @return {Happy.metadata.DatabaseTypeMapper} 486 */ 487 getDatabaseTypeMapper: function () { 488 var me = this; 489 490 return Happy.metadata.DatabaseTypeMapper; 491 } 492 });
运行效果
备注
刚开了个头,这篇文章只是介绍了代码生成器的一些使用场景,但是真正重要的是系统的架构风格,我的偏好是DDD + CQRS,因此最终的目标是支持DDD + CQRS,代码生成器只不过帮我写了一些代码,如果要做到支持DDD + CQRS的话,需要抽象出很多元数据,比如:聚合根、实体、值对象和他们的关系等等。