我對大型系統的理解,從數量上面來講,源代碼超過百萬行以上,系統有超過300個以上的功能,從質量上來講系統應該具備良好的可擴展性和可維護性,系統中的功能緊密關聯。除去業務上的復雜性,如何設計這樣的一個協作良好的系統,搭建開發人員基礎平台,一直是我研究的方向。
SouceCounter(版本3.3.91.79)對源代碼的統計信息如下:
下面來詳細解析一下這個系統的設計架構,純.NET技術架構方案,C/S WinForms系統。
系統分為Framework和Application兩個部分,前者是框架(Framework),包含核心的基礎功能,如公共類庫,許可授權,數據字典,公共控件,公共窗體,多線程組件,通信基礎,會話管理等基礎部分,后者是應用部分,根據業務邏輯的不同,對於ERP系統而言,可分為進銷存(Distribution),工程(Engineering),生產(Production),生產計划(Production Planning),財務(Account Receivable,Account Payable,General Ledger),客戶關系(CRM)等模塊。
Framework 開發框架
1. Common 公共類庫
- 定義異常類型,用於系統異常處理:LicenseException 許可授權異常,字段驗證異常FieldValidationException,實體驗證異常,應用程序異常AppException,水晶報表異常CrystalReportException。
- 定義版本信息,設定四個版本字段的值
public const string Major = "3"; public const string Minor = "4"; public const string Build = "0"; public const string Revision = "0";
- 定義許可授權方案, SystemLicense系統許可文件,HaredwareInformation硬件信息。SystemLicense中包含一個核心的方法Verify,用於驗證授權許可,比如過期時間,版本(Standard標准版,企業版Enterprise),硬件信息,參數設置(最大用戶數,可用模塊數量,可用數據庫)。
public enum LicenseType { Internal, Standard, Enterprise }
- 優化.NET DataSet傳輸,主要是AdoNetHelper,這個類型來自CodeProject上的一篇文章,標題是Optimize .NET DataSet,完全自定義DataSet在.NET Remoting服務器和客戶端傳輸方法。
- 定義幫助類庫,DataSetHelper數據集操作,CrystalReportHelper水晶報表幫助類,WmiHelper操作系統WMI幫助類,加密與解密幫助類RSACryptionHelper,SafeInvokeHelper 用於COM操作幫助類,數據庫操作幫助類SqlHelper,StatisticsHelper數據統計幫助類,StrongNameSignatureHelper強命名驗證幫助類。
- FTP文件傳輸 System.Net.FtpClient,這個引用於netftp,有很多例子代碼可快速入手。
- 類型轉換 Type Converter,在設計ASP.NET控件的時候會經常遇到這個類型,用於將ASPX中的文本定義轉化為.NET類型,比較常用的如bool值轉換,0轉換為false,1或其它值轉化為true,字符Y轉化true,字符N轉化為false,MSDN中很多TypeConverter的例子可以參考。
- Excel導入類,對於Excel導入類,對功能的唯一的要求是不能一定要求安裝Microsoft Excel,客戶的服務器中可以安裝的軟件非常少,所以全盤否定微軟自帶的Office Interop。
- 定義系統常用的資源文件,全部以嵌入的資源內置在程序集中。
2. Component 公共控件與組件
- Editor 編輯控件,文本編輯控件TextEditor, 日期編輯控件DateTimeEditor,下拉選擇控件ComboEditor,多行明細數據編輯控件Grid,單選控件CheckBoxEditor,圖片編輯控件PictureBox,富文本編輯控件RichTextBoxEditor,數字編輯控件NumericEditor,時間編輯控件TimeEditor,甘特圖編輯控件GanttaEditor。
- Calendar日期控件 日期控件用於顯示當前時間范圍內的任務(工作單,生產計划,送貨計划),可參考Outlook中的日期控件,代碼來自於CodeProject中的文章。
- Form 窗體基類,這是很核心的部件,子類窗體的代碼能否簡潔高效,全在於這個地方。定義FormBase類型,包含基本的功能:多語言(可讀取界面上所有控件進行Text屬性重設置),布局可持久化(Layout Persistence),用於將用戶修改過的界面偏號保存,常用於Grid列順序修改,SplittContainer上下面板位置大小調整,自定義布局(Custom Layout) 用於窗體設計器修改界面布局,窗體快捷鍵定義,比如將Enter鍵重寫成Tab鍵方便跳轉。
- Query Form/Enquiry Form 查詢窗體基類,用於定義通用查詢界面,用戶將一個SQL語句,或存儲過程,或是通過查詢生成工具生成一個查詢,通過這個界面基類解析產生界面和產數據。
- Crystal Report Form 水晶報表基類,用戶設計完報表,放置到配置的目錄中,即可查看水晶報表,方便打印設定,報表多語言,報表參數傳遞,報表導出為Excel或PDF,報表生成為指定格式發送到用戶郵件。
- Error Dialog 異常對話框,CodeProject中有一篇文章是講解如何獲取.NET堆棧的信息,當系統業務拋出異常時,需要將此異常信息封裝,比如獲取當前程序的版本,構建時間等封裝傳遞到異常對話框,在異常對話框中點一個簡單的復制到剪貼版按鈕即可將出錯的信息收集起來,這樣方便程序員診斷問題。
- Form Designer 窗體設計器,用於自定義布局Layout,修改之后將布局保存為Xml文件,FormBase會預先在自定義布局表(FormLayout)中搜索此功能的布局定義,如發現有則加載。.NET CLR Form Host中有文章介紹如何生成Xml文件定義的內容,簡單的理解可以將Visual Studio定義為一個代碼生成工具,拖放控件到窗體中會生成不同的源代碼,C#,VB.NET,還有Xml,Visual Studio不支持生成Xml格式界面配置,但CLR Form Host可以實現這一點。
- Graph 圖表基類 用於以圖表的方式呈現數據,柱子圖/餅圖/點狀圖/曲線圖/K線圖,打開Excel的圖表功能即可看到各種類型的圖表,Visual Studio 2009內置了豐富的圖表控件,進行簡單的封裝即可。
- Field Range Control 查詢組件 涉及到ORM的查詢,只需要給這個控件填寫到實體名稱和屬性名稱,它即可為我生成查詢條件,這樣在設計時很方便設計查詢條件,節省了大量的拼接條件的代碼。
- Condition Editor 條件編輯控件,主要用於工作流中的IF-ELSE活動的條件編輯。
- BackgroundWorker 多線程組件,用於多線程處理,.NET系統已經包含很實用的后台線程控件BackgroundWorker,避免主界面卡死,也實現了多線程處理,但是這個控件需要依托在Form窗體中,需要增加一個WorkerThreadBase,參考Code Project中的文章。
- Misc 雜項 SQL編輯器控件,系統中比如生成查詢語句,工作流的條件設置,預警提醒的查詢語句等地方會涉及到SQL顯示或書寫,有一個語法高亮的編輯控件會給系統增色不少,Code Project上有大量的SQL Hightlight Editor控件可供使用,Auto CAD圖紙顯示控件,工程模塊的物料清單可能會增加圖紙附件,需要聯機在線查看DWG格式的圖紙,桌面提醒控件 Desktop Alert 這個的參考例子是MSN的消息提醒,Outlook新郵件到時的提醒,參考Code Project上有許多類似控件。Tab MDI布局控件(DockManager),這個組件也相當重要,因Infragistics中就有一個這樣的組建,開箱即用,可以將傳統的MDI布局改成Tab MDI,不需要修改代碼。圖表控件Image Control用於給圖片設定縮放模式,攝像頭控件,用於人事系統中的員工主檔給員工拍攝相片,瀏覽器控件Web Browser用於顯示網頁,重寫.NET系統提供的控件,修改幾個屬性,關閉錯誤腳本提示。
3. 預定義基礎功能 Administration UI
設計精良的系統應該先預定義好一系列的基礎界面,用於管理框架功能中的元數據,這一節分兩個部分講解,一是框架要定義什么,二是如何去實現這些功能。先來看一下框架數據庫Framework有哪些基礎的元數據表定義:
先寫一個基礎查詢語句,用於查詢框架系統數據庫的所有表。
SELECT * FROM sys.tables ORDER BY name
數據表 | 定義 |
Attachment | 系統中所有功能的附件,可直接存儲文件或是存放一個FTP文件路徑 |
Branch / BranchDetail |
實現多庫存組織 |
Company | 實現多公司帳套 |
Configuration | 系統參數配置 |
ContextFunction ContextFunctionDetail |
系統上下文菜單項定義 |
Dictionary |
可變的數據字典定義,用於可修改的數據字典 |
FormLayout |
自窗義界面(Form Designer設計之后保存的Xml文件) |
FormProfile | 用戶偏號(網格排序,控件位置或大小拖動) |
LanguageTranslation | 多國語言翻譯 |
Component | 系統啟動時讀取的程序集 |
Lookup | 查找數據對話框 |
Message MessageDetail |
消息盒子 |
Report | 報表參數定義 |
ScheduledTask | 系統預置的計划任務 |
SystemModule SystemFunction |
系統包含的模塊與功能 |
User | 系統用戶 |
UserDefinedQuery | 預定義查詢 |
UserGroup | 用戶組別 |
UserGroupAuthorization | 用戶組別授權 |
UserGroupMenu | 用戶組別菜單 |
UserGroupMenuBitmap | 用戶組別導航圖 |
UserLog | 用戶日志 |
Workflow | 工作流定義 |
- 可修改的數據字典,比如員工工作等級,從T1到T4,再到M1,T表示Technican技術員,M表示Manaer經理,這些都可以根據實現需要增減。不可修改的數據字典如性員Gender,只有Male或Female兩種選項,不能修改,這種不可變的數據字典直接用代碼寫死。
- 系統預置的計划任務,常用於工作流中的消息提醒或報表生成需求。前者如當庫存不夠時發送消息提醒采購人員,后者用於每周定期生成老板報表發送到老板郵箱中。
- 工作流定義保存工作流設計器生成的XOML文件。Visual Studio不支持生成XOML格式的工作流定義,但.NET Workflow Host可通過修改輸出獲取工作流設計器生成的工作流定義文件。
- 單據編碼/單據序列號 有的客戶偏好於放在業務數據庫中,在上表中沒有提到。
通過上面框架數據庫表的定義即可看到框架的基礎功能,也就是對以上數據進行讀寫。我按照窗體的類別簡單介紹。
- 登入退出類功能:LoginDialog用戶登入,登入成功后可顯示Splash Screen,之后進入主界面Main Form,用戶退出時,還要檢測當前是否有窗體的數據沒有保存(dirty)逐個提醒用戶保存數據或是放棄修改,用戶可能會修改登入密碼,用戶也可能做一些菜單或功能的自定義。
- 設計類功能:Query Designer 設計查詢,將查詢文本保存到數據表UserDefinedQuery中。Report Dialog Designer用於維護報表參數,報表的設計由水晶報表設計完成,實在沒有精力去維護一個Report Designer,所以直接用功能強大的水晶報表,在報表對話框中只需要維護報表的參數,這樣不用寫代碼即可完成將參數值傳遞到報表中。Workflow Designer用於設計工作流,保存結果XOML到Workflow表中。Form Designer用於修改自定義布局,隱藏或調整控件位置,或修改查詢條件。Lookup Designer 用於設計查找窗體,直觀的解釋是當光標放到客戶編號控件中時,此控件會顯示一個小按鈕,點擊按鈕會彈出客戶編號選擇對話框,這里的設計查找窗體,也就是設計這個客戶編號選擇對話框。
- 應用程序類功能:控制應用程序的啟動,關閉,控制通信層,實現Application Recovery模式,這個功能可以從Windows 7 Code Package中獲取源代碼參考。當應用程序崩潰時,可以提示重新啟動再恢復崩潰前的數據。
- 自動更新類功能:支持從FTP,HTTP,XCOPY等方式下載最新版本的文件更新系統。目前沒有實現調用微軟的Background Intelligent Transfer Service,也沒有實現調用第三方下載API的功能,不支持斷點續傳或是快速的大文件下載。調用迅雷API速度很快的原因可能是要求服務器中有備份文件,這對於企業應用來說不可行,另外第三方的下載API會遭遇限速等困擾,還是老老實實的實現局域網內的快速穩定更新。
- 通用附件類功能:實現一個通用的附件管理模塊,其它功能只需要設置一個SupportAttachment=true屬性即可擁有附件管理功能。附件可保存在數據庫或FTP文件服務器中。
4 工作流基礎 Workflow Essential
工作流實現的四大基礎功能:通知提醒,批核,計划任務,調用自定義代碼。
通知提醒:ERP系統中包含大量的提醒功能,每加一個業務功能就寫一遍提醒功能的代碼調用顯得有些繁瑣,在此只做一個設置即可實現通知設置,包括要通知的人員,通知的內容。
批核:框架應該抽象出所有單據的批核需求,建立一個獨立的批核系統,任何功能只需要簡單設置一下即可調用工作流代碼,實現批核流程。
計划任務: 系統中有一些定期執行的任務,比如員工生日提醒,庫存余額報警,待收貨記錄提醒,老板報表定時發送。
調用自定義代碼:如果系統中的功能存在缺陷defect,軟件公司不願意修改的情況下,可以考慮增加自定義的.NET代碼解決問題。只需要在合理的事件點上插入合適代碼,完成重復的數據修復工作。
基於微軟工作流的解決方案,先看一下包含的基礎組件:
Activities 活動庫,活動是工作流定義中一個基本的代碼執行單元,可固化執行的代碼均可封裝到一個活動中。包含文檔批核活動,發送報表活動,查詢活動,調用.NET代碼活動等。調用.NET代碼活動的設定方法參考如下:
assembly=Microsoft.Applications.MyReportDll; class=Microsoft.Applications.MyReportDll.EmployeelistingDAL; method=GetEmployeeListing();
熟悉.NET框架反射調用方法的朋友一看就明白上面定義的含義,這個活動的源代碼可以簡化如下所示:
protected override ActivityExecutionStatus Execute(ActivityExecutionContext executionContext) { assembly = Assembly.Load(segments[0]); type = assembly.GetType(segments[1]); method = segments[2]; type.GetMethod(method, BindingFlags.Public | BindingFlags.Static).Invoke(null, null); }
Contracts 接口與實現 在第一步活動定義中,大量的調用了WF中的CallExternalMethod活動,這個活動用於調用外部自定義方法,實現接口與實現的分離。在設計活動時只需要指定要調用的接口和方法,具體的實現可根據需要變化。
.NET WF要求在啟動WF Runtime時,需要先注冊要執行的服務,代碼參考如下:
SqlWorkflowPersistenceService persistenceService=new SqlWorkflowPersistenceService (ConnectionString);
runtime.AddService(persistenceService);
在活動的代碼中,可通過以下的方式引用經過注冊的服務:
SqlWorkflowPersistenceService persistenceService=context.GetService<SqlWorkflowPersistenceService>(); //調用 persistenceService服務的方法
這樣,服務中可調用ERP代碼的接口,實現了ERP邏輯與.NET WF工作流的整合。
Workflows 給每種常見的流程定義一個工作流類型,方便做持久化和驗證工作。
Workflow Designer Control re-host工作流定義組件,直接參考借用MSDN中的例子。
Monitor 工作流監控,查看流程的執行情況,當前執行結點,執行路徑。
5 服務器體系 Server Series
從功能上來講,系統應該具備以下四個基礎服務器,實現數據讀寫分離。
Application Server 業務邏輯服務器,.NET Remoting服務器端。
Report Server 報表服務器,用於報表呈現,減低Application Server的壓力。
Workflow Server 工作流服務器 執行工作流。
Scheduling Server 計划任務服務器,減低Application Server的壓力。
每種服務器都配置Console版和Service版,代碼完全一樣,Console以控制台程序呈現,Service以Windows 服務應用形式實現,前者方便開發,后者用於部署和實際使用。
Application 應用程序
根據ERP項目的功能分類,分為進銷存(Distribution),工程(Engineering),生產(Production),生產計划(Production Planning),財務(Account Receivable,Account Payable,General Ledger),客戶關系(CRM)等模塊。每個模塊獨立為一個Visual Studio Project,編譯成一個程序集。通過插件式結構,實現使用時只需要在Component表中插入一行記錄,即可讓系統識別到此程序集,運用反射方法調用程序集中的功能。
1 業務實體與業務邏輯 Business Logic
如果是用LLBL Gen Pro開發系統,則業務實體層具備以下文件夾層次結構。
以銷售單表頭為例子,數據庫表SalesOrder,生成實體為SalesOrderEntity,設計讀寫接口文件為ISalesOrderManager, 接口的實現類為SalesOrderManager,用簡單的圖表示如下:
SalesOrder –> SalesOrderEntity –> ISalesOrderManager –> SalesOrderManager
系統強制執行以上約定,並且設計了Code Smith模板代碼生成來減少出錯的可能。既提供強制性約束,又提供工具輔助開發人員遵守約定,系統開發效率成倍提升。
業務實體層還實現了數據審計(Audit)功能,記錄表的每個記錄的修改值。剛畢業參加工作時,常常混淆Audit和Approval的區別,現在一些系統還存在用Audit作為批核的意義。
2 數據字典 Enum
定義系統中不可變的數據字典,雖然用代碼寫死字典的方法值得商議,但它的好處也是非常明顯的。
定義一個勞動合同的枚舉,分固定期限和無固定期限的合同,參考下面的代碼。
public enum ContractType { [StringValue("F")] [DisplayText("Fixed Time")] FixedTime, [StringValue("U")] [DisplayText("Unlimited Time")] UnlimitedTime }
獲取它的值用如下方法,值用於存儲到數據庫中或程序代碼使用:
StringEnum<ContractType>.GetStringValue(ContractType.FixedTime)
獲取它的描述用如下方法,描述用於界面中呈現:
StringEnum<ContractType>.GetDisplayText(ContractType.FixedTime)
3 業務實現 Business Logic Implementation
一部分邏輯在Business Logic中實現,比如類型初始化值,自動帶值,值驗證等邏輯。復雜的邏輯比如進出倉,涉及到的關聯表會多一些,可能要扣減庫存,修改物料庫存余額,批號生成,生成出倉平均單價等,復雜的邏輯要單獨放在一個項目中設計源代,這樣方便維護。
4 報表 Report
從形式上來分,分為圖形報表和數據報表。圖形報表用微軟.NET 自帶的圖形控件完成,MSDN上包含Samples Environments for Microsoft Chart Controls的大量例子。數據報表用水晶報表完成,600多個水晶報表文件,涵蓋單據的打印,列表查詢打印,主檔數據打印等類別。豐富的水晶報表功能為系統增色不少。
5 系統維護 System Maintenance
集成一些常用的功能,不需要進入系統即可完成系統維護。比如數據庫升級,數據庫備份,數據庫還原,新帳套創建,系統參數設定,數據庫性能優化(主要是索引重建),這些實用工具程序減輕了系統管理員的負擔。
6 功能模塊/界面 Presentation
Distribution 進銷存,包含銷售,采購,倉庫模塊。
模塊 | 功能 |
Sales 銷售 | 銷售合同,銷售訂單,送貨,退貨,銷售包裝,銷售發票 |
Purchasing 采購 | 采購申請,采購訂單,采購收貨,采購退貨,供應商發票 |
Inventory 倉庫 | 進倉(Receipt),出倉(Issue),轉倉(Transfer),盤點(Cycle-count) |
Engineering 工程 物料清單,標准成本設定。
Production 生產 工作單,工作單發料,倒沖與組裝,物料退回,發散料。
Production Planning 生產計划 主生產計划MPS,物料需求計划MRP, 能力需求計划CRP
Finance 財務 Account Receivable應收,Account Payable應付,General Ledger總帳。
CRM 客戶關系 銷售線索,銷售日報,銷售月報,出差申請與費用報銷。
在我的從業經歷以來,我認為搭建一套開發框架對企業開發是很有用處的。前期所花費的精力和時間在后期都會得到充分的回報。然而搭建框架所花費的時間和精力,值得商榷。公司一直都是對股東負責,能用最少的時間做完項目,收回合同款即可,大費周折的去做產品基礎功能,對於小公司而言生存都是問題,精心設計的框架需要大量的精力去維護和改善,拋開公司因素,學習一個大型系統對個人的職業發展和成長也是相當重要的。