幸福框架:全站式代码生成器示例

背景

一直在做企业应用,也一直在使用代码生成器,代码生成器分两个维度,一个维度是”主动或被动“,另外一个维度是”运行时或编译时“,这两种维度会有四种组合,每个组合都有其应用的场景,今天我就介绍一下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的话,需要抽象出很多元数据,比如:聚合根、实体、值对象和他们的关系等等。

 

posted on 2013-06-04 08:08  幸福框架  阅读(6655)  评论(26编辑  收藏  举报

导航

我要啦免费统计