背景
准備采用CQRS架構,之前也簡單的應用過(只是把讀和寫在程序級別進行了分離),這篇文章是我最近幾天的思考,寫下來希望大家多提意見。這篇文章不會涉及Command端的設計,重點關注如何設計查詢。
真心的希望大家看完后能給出你們的意見和想法。
什么是CQRS
CQRS:Command Query Responsibility Separation。我喜歡職責分離,這也是我采用這種架構的原因,確實能帶來單一職責的優點。
簡單的CQRS
復雜的CQRS
CQRS的常見查詢需求
下面是系統的一些查詢需求:
查詢面板
高級查詢
數據行級別的權限
如:個人、部門、分公司、品種。
固定約束
如:啟用、合法、租戶ID。
需求總結
CQRS的查詢設計
充分利用SQL和動態類型的優勢,不做太多無謂的封裝。
關鍵決策:
-
- 直接查詢數據庫返回Dynamic類型,不需要定義強類型。
- 直接用SQL,支持動態查詢面板和動態數據行權限。目前沒有找到封裝SQL的理由,最多是在外圍再封裝一層,但是不會隱藏SQL(我之前寫過一個簡單的查詢對象)。
- 利用一些策略防止SQL注入和權限提升(這篇文章不介紹)。
示例代碼
下載地址:http://happy.codeplex.com/SourceControl/latest。
AJAX程序
1 /// <reference path="Ext/ext-all-debug-w-comments.js" /> 2 3 Ext.onReady(function () { 4 var query = { 5 TableOrViewName: 'Users', 6 WhereClause: "Name NOT LIKE '%段%'" 7 }; 8 9 Ext.Ajax.request({ 10 url: 'TestDynamicQuery/Fetch', 11 method: 'POST', 12 params: { query: Ext.encode(query) }, 13 success: function (response) { 14 console.log(response.responseText); 15 } 16 }); 17 18 query = { 19 TableOrViewName: 'Users', 20 WhereClause: "Age >= 20 AND Age <= 27" 21 }; 22 23 Ext.Ajax.request({ 24 url: 'TestDynamicQuery/Fetch', 25 method: 'POST', 26 params: { query: Ext.encode(query) }, 27 success: function (response) { 28 console.log(response.responseText); 29 } 30 }); 31 });
萬能查詢控制器
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 using System.Web.Mvc; 7 8 using Newtonsoft.Json; 9 10 using Happy.Query; 11 12 namespace Happy.Web.Mvc 13 { 14 /// <summary> 15 /// 動態查詢控制器。 16 /// </summary> 17 public abstract class DynamicQueryController<TDynamicQueryService> : AjaxController 18 where TDynamicQueryService : IDynamicQueryService 19 { 20 /// <summary> 21 /// 動態查詢服務。 22 /// </summary> 23 protected abstract TDynamicQueryService QueryService { get; } 24 25 /// <summary> 26 /// 獲取分頁數據,面向表格。 27 /// </summary> 28 public ActionResult Page(DynamicQueryObject query) 29 { 30 var result = this.QueryService.Page(query); 31 32 return this.Json(result); 33 } 34 35 /// <summary> 36 /// 獲取列表數據,面向不需要分頁的表格或下拉框。 37 /// </summary> 38 public ActionResult Fetch(DynamicQueryObject query) 39 { 40 var result = this.QueryService.Fetch(query); 41 42 return this.NewtonsoftJson(result); 43 } 44 45 /// <summary> 46 /// 獲取一個數據,面向表單。 47 /// </summary> 48 public ActionResult SingleOrDefault(DynamicQueryObject query) 49 { 50 var result = this.QueryService.Fetch(query); 51 52 return this.NewtonsoftJson(result); 53 } 54 } 55 }
萬能查詢對象
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Threading.Tasks; 6 7 namespace Happy.Query 8 { 9 /// <summary> 10 /// 動態查詢對象。 11 /// </summary> 12 public sealed class DynamicQueryObject 13 { 14 /// <inheritdoc /> 15 public DynamicQueryObject() 16 { 17 this.Columns = new List<string>(); 18 this.Page = 1; 19 this.ItemsPerPage = 25; 20 } 21 22 /// <summary> 23 /// 表或試圖名字。 24 /// </summary> 25 public string TableOrViewName { get; set; } 26 27 /// <summary> 28 /// 表或試圖名字。 29 /// </summary> 30 public List<string> Columns { get; set; } 31 32 /// <summary> 33 /// Where子句。 34 /// </summary> 35 public string WhereClause { get; set; } 36 37 /// <summary> 38 /// Order子句。 39 /// </summary> 40 public string OrderClause { get; set; } 41 42 /// <summary> 43 /// 第幾頁數據。 44 /// </summary> 45 public long Page { get; set; } 46 47 /// <summary> 48 /// 每頁條數。 49 /// </summary> 50 public long ItemsPerPage { get; set; } 51 } 52 }
備注
寫這篇文章的目的,是系統大家多給些意見,我想知道你們是如何應對這種查詢需求的。