在Visual Studio 2012中使用VMSDK開發領域特定語言(一)


前言

本專題主要介紹在Visual Studio 2012中使用Visualization & Modeling SDK進行領域特定語言(DSL)的開發,包括兩個部分的內容。在第一部分中,將對領域特定語言進行簡單介紹,並講解如何使用Visual Studio 2012創建一個領域特定語言的開發解決方案,以及Visual Studio 2012集成開發環境對DSL開發的支持;在第二部分中,將以實際應用為例,介紹開發DSL的主要步驟,包括設計、定制、調試、發布以及使用等。本文為本專題的第一部分。

領域特定語言概述

在軟件開發過程中,領域特定語言(Domain Specific Language,DSL)可以是一種面向特定應用領域的編程語言,可以是針對某一類問題的表述形式或解決方案,也可以是一種規約(Specification)集合。給DSL下一個明確的定義並不是件容易的事情,比如XML,在大多數情況下,它當然不是DSL,因為能夠使用XML進行描述的信息種類數不勝數,但如果我們把XML用於.NET應用程序的配置中,讓配置文件以XML的形式進行組織,那么這種具有特定結構的XML就可以看成是一種DSL:這種DSL面向.NET應用程序的配置領域,為這一領域中的問題提供解決方案。在這里,我們暫且不討論用XML來實現某種DSL是否合適。

對領域驅動設計(Domain Driven Design,DDD)有一定了解的讀者,一定對“通用語言(Ubiquitous Language)”這一詞語並不陌生。不錯,通用語言是DDD的討論核心,換句話說,所有與DDD相關的理念和實踐,都是圍繞通用語言而存在和進行的。通用語言解決了軟件系統(或者說應用程序)開發過程中的一個重要問題:溝通。目前的現狀是:領域專家(業務人員)對軟件開發技術和過程並不了解,甚至是一無所知;而軟件開發人員又對其所涉及的領域難以理解。所以,DDD認為,應該基於軟件系統所面臨的領域,設計一套能夠同時被領域專家和軟件開發人員所理解的語言,以便盡量減少由溝通引起的誤解和分歧。這就是通用語言的由來,這里我們還需要區分一下“通用語言”和“普通編程語言(General-Purpose Programming Language)”的差別,雖然兩者都有“通用”的含義,但前者是指“在領域專家和軟件開發人員之間的通用性”。

由此引出兩個問題:首先,通用語言的定義和設計是需要一定成本的,這對於經費本不充裕的項graph showing complexity and effort目來說,無疑是難以實踐的;其次,通用語言的學習和普及也是需要一定成本的,相對於普通編程語言而言,通用語言的學習成本更大:整個團隊需要了解並逐步接受一個新的事物,無論是時間上,還是經費上,都有着一定的開銷。結合DDD來看,DDD已經在通用語言的基礎上抽象了一些語言元素,比如:實體、值對象、聚合與聚合根、領域服務、領域事件等,然而讓一個普通的團隊來理解並在自己所面對的領域中准確把握這些元素,也不是一件容易的事情。我想,這也算是側面解答了“為什么要領域驅動”的疑問:如果你的項目規模不是很大,並希望以最快速度看到開發結果的話,請不要選擇DDD。相反,當項目達到一定的規模時,現實復雜度使我們不得不對其進行長遠規划,通用語言的定義和學習成本也就變得不那么突出,更重要的是,通用語言的引入和DDD的應用將幫助項目向着良性的方向發展,使項目的各個方面處於一種可控狀態。右圖來自Martin Fowler《企業應用架構模式》一書,該圖對Transaction Script、Table Module(Active Record)和Domain Model三種模式的應用進行了比較,可以看到,對於邏輯相對簡單的情況而言,應用Domain Model模式的成本會比較高,然而隨着領域邏輯復雜度的增加,相對於其它兩種模式而言,使用Domain Model模式的開發和維護成本會更小。雖然DDD並非Domain Model模式,但我們同樣可以用這幅圖進行類比,以解答“為什么要領域驅動”的問題。

“通用語言”就是面向特定領域的DSL,根據現行的DSL分類方式,“通用語言”基本上都是以“外部DSL”的方式實現,因為嵌入式(或者說“內部”)DSL都需要一種“普通編程語言”作為宿主,但領域專家不一定熟悉普通編程語言。相比之下,UML就不是一種DSL,因為UML並非專注於一個特定的領域。

DSL一般分為兩種:嵌入式(內部)DSL(Embedded/Internal DSL)和外部DSL(External DSL)。上面也簡單提到過,內部DSL通常需要一種普通編程語言作為宿主,就編譯器角度而言,內部DSL和普通編程語言之間沒有差別,但對於開發人員來說,內部DSL提供了一種可讀性更強的、更能表達特定領域語義的編程方式。較為流行的內部DSL實現方式就是我們熟知的“流暢接口”(Fluent Interface):通過一系列屬性和方法的連貫調用,讓人覺得好像是在使用一種自然語言來表達和解決特定領域的具體問題。在.NET中,LINQ就是這樣一種內部DSL,它以另一種形式集成於宿主編程語言(C#或VB.NET)中,開發人員可以通過這種形式的代碼來很順利地表達查詢語義;從編譯器的角度看,LINQ形式的代碼其實就是流暢接口的調用,如果您對這個問題感興趣的話,您可以使用C#語言寫兩個方法,在A方法中使用LINQ方式對一個列表結構進行篩選,比如:var query = from l in list where l == 3 select l;;而在B方法中則使用System.Linq命名空間中的擴展方法,以流暢接口的方式對列表結構進行同樣的篩選操作,比如:var query = list.Where(l => l == 3);,當你使用ildasm.exe工具查看這兩個方法所產生的IL代碼時,你會發現兩者完全相同。至於“流暢接口”的實現方式,我已經在《在C#中使用裝飾器模式和擴展方法實現Fluent Interface》一文中給出了解決方案。

另一種是外部DSL,外部DSL的實現通常需要借助於新的編譯器/解釋器,這也就意味着團隊需要設計並開發一套能夠將外部DSL翻譯成普通編程語言,或者能夠將外部DSL轉換為能在程序環境中運行的組件的編譯器/解釋器。外部DSL也有兩種不同的形式:文本式的和圖形化的。文本式的優點在於維護簡單,冗余信息比較少(基於XML的外部DSL除外),而圖形化的優點則在於直觀,但它會連帶很多冗余信息,在團隊環境中使用圖形化DSL會產生一些版本控制上的問題,因為這種圖形化的DSL會夾帶很多與顯示相關的特性,比如圖形位置、尺寸、顏色等等,而這些信息本不屬於DSL的內容,但在版本控制系統中,又無法忽略對這些信息的更改操作,所以很容易增加解決版本沖突和代碼合並的工作量。外部DSL的例子很多,例如:NHibernate的映射文件、web/app.config,雖然這兩種是基於XML的;還有Entity Framework的圖形化設計器,它是一種面向ORM(Object Relational Mapping,對象關系映射)領域的圖形化DSL,它能通過T4將edmx轉換成C#源代碼,然后通過編譯器聯編並產生可執行程序。

接下來,我們將討論如何在Visual Studio 2012中使用Visualization & Modeling SDK(VMSDK)開發DSL。通過VMSDK開發的DSL屬於外部DSL。

使用Visual Studio 2012開發DSL的先決條件

要使用Visual Studio 2012開發DSL,就需要安裝Visual Studio Visualization & Modeling SDK(VMSDK),而安裝VMSDK之前,需要確保Visual Studio SDK安裝正確。VMSDK的前身是DSLTools,DSLTools是面向Visual Studio 2005/2008的DSL開發包。以下給出Visual Studio 2012 SDK和Visualization & Modeling SDK的下載地址:

在后面的案例中可以了解到,我們所開發的DSL會被打包成一個VSIX的Visual Studio擴展包,於是DSL的發布也變得異常簡單:只需要在客戶機上安裝這個VSIX擴展包即可完成發布。這也是為什么VMSDK需要基於Visual Studio SDK的原因之一。

DSL解決方案的創建

現在,我們使用Visual Studio來創建一個新的DSL解決方案,以便了解DSL的開發過程。在Visual Studio的New Project對話框中,在Other Project Types | Extensibility分類下,可以看到一個名為Domain-Specific Language Designer的工程模板,選擇這個模板,並選擇解決方案的保存路徑,然后為解決方案設置一個名稱,點擊OK按鈕:

1

現在可以看到Domain-Specific Language Designer Wizard對話框,這個向導對話框能幫助開發人員一步步地完成DSL解決方案的設置,它包含了以下這些內容:

  • Solution Settings頁面:該頁面提供了DSL模板的選擇列表,開發人員需要根據自己的實際應用來選擇一個合適的模板,例如,如果希望開發一個類似於UML中類圖結構的DSL,那么就選擇Class Diagrams模板,我們熟知的Entity Framework Model Designer所設計的edmx就屬於這種結構的DSL。在這個頁面中,還需要為DSL指定一個名稱
    2

  • File Extension頁面:在這個頁面中,為DSL指定一個文件擴展名,比如Entity Framework Model DSL的文件擴展名是edmx。當輸入文件擴展名后,向導會自動搜索系統注冊表以確保所輸入的擴展名沒有被占用
    3

  • Product Settings頁面:這里主要是對DSL作為一種產品的一些設置,包括三個方面的內容:DSL所屬的產品名稱、開發這套產品的公司名稱,以及DSL所使用的命名空間。例如,Contoso公司正在開發一套軟件產品,產品名為ContosoSoftware,並在這套產品中使用了一種面向某個業務領域的DSL,於是,在開發DSL的時候,就可以將這些信息填寫在這個頁面中
    4

  • Signing頁面:Visual Studio會在DSL解決方案中創建一個產生Visual Studio擴展包(VSIX)的工程,通過編譯和使用這個擴展包,就能很方便地將DSL部署到Visual Studio開發環境,這部分內容后面還會涉及到。根據Visual Studio擴展包的開發規范,工程需要簽名,因此開發人員需要在這個頁面中設置一個強名密鑰文件。與程序集簽名類似,可以創建一個新的強名密鑰文件,或者使用一個已有的強名密鑰文件
    5

  • Summary頁面:在此列出了之前各個頁面中的設置選項,以便開發人員能夠在解決方案創建之前,對這些設置選項進行確認。當確認無誤之后,單擊Finish按鈕以在Visual Studio中創建DSL解決方案
    6

在Solution Explorer中,我們可以看到,Visual Studio為DSL解決方案創建了兩個工程:Dsl和DslPackage。Dsl是DSL開發的主要工程,今后7我們對DSL的開發和測試都會在這個工程中進行;DslPackage則是一個Visual Studio Extension工程,其主要作用就是產生一個.vsix的Visual Studio擴展包,以方便DSL的部署。DslPackage相關的內容不是本專題的重點,請感興趣的讀者參閱MSDN中有關Visual Studio SDK和擴展包開發的相關專題內容。

Dsl工程中比較重要的內容有:GeneratedCode文件夾、Resources文件夾以及DSL的定義文件:DslDefinition.dsl。GeneratedCode文件夾下包含了很多T4模板,這些T4模板會根據DslDefinition.dsl中DSL的定義產生相應的C#代碼;Resources文件夾下則包含了DSL所使用的資源文件,比如用於在Visual Studio工具欄中表示某個工具的圖標圖片、在設計器中所使用的鼠標光標文件等等;我們開發DSL的大部分工作都是在DslDefinition.dsl文件上進行的,它指定了DSL包含了哪些領域類型(或者說領域概念)、這些領域類型之間的關系是什么,以及領域類型及關系在設計器上的展現形式。其它的兩個文件不是我們討論的重點,在常規的場景中無需深究這兩個文件。

接下來,讓我們了解一下使用Visual Studio進行DSL開發的集成開發環境。

DSL集成開發環境簡介

Visual Studio 2012 DSL集成開發環境主要由以下窗體構成:

8

  • 工具欄(Toolbox):就像Windows Forms設計器工具欄一樣,它提供了DSL的設計工具,包括領域類型(Domain Class)、關系(Relationship)等工具,我們會在后面對這些工具進行簡要介紹
  • DslDefinition.dsl設計器:DSL設計器界面,其中定義了DSL所涉及的所有領域類型及其之間的關系。它同時還定義了這些類型和關系在實際應用中的表現形式,比如某個領域類型的實例是以圓形表示,還是以圓角矩形表示,包括這些圖形的顏色、字體、線條粗細等定義
  • DSL細節(DSL Details):開發人員可以在這個窗口中對所選的DSL對象的某些屬性細節進行設置,比如通過Mapping Details可以設置領域類型與所使用的圖形之間的映射關系
  • DSL瀏覽器(DSL Explorer):事實上這里才是真正設計DSL的地方,DslDefinition.dsl設計器無非也就是對DSL瀏覽器中特定內容的圖形化表示。在DSL Explorer中,我們可以定義領域類型(Domain Class)、關系(Relationship)、圖形(Shapes)、連接器(Connectors),以及DSL本身所提供的工具(Tools)和驗證機制(Validation)。在本專題的第二部分對案例進行介紹的時候,我們會使用到DSL Explorer,但並不一定會涵蓋DSL Explorer的各個方面

在日常開發過程中,主要用到的也就是上面所介紹的四個窗體,另外我們還有可能需要根據實際情況來編寫一些定制化的C#代碼,所以C#程序編輯器和解決方案資源管理器(Solution Explorer)也會被用到。在下一個部分的介紹中,我們會經常涉及到Visual Studio 2012 DSL集成開發環境的這些內容。

DSL工具欄(Toolbox)簡介

DSL工具欄中常用的工具主要分為三類:領域類型(Domain Class)類工具、關系(Relationship)類工具以及圖形(Shape)類工具,接下來讓我們簡單地了解一下這些常用的工具。

領域類型類工具

領域類型類工具包括:Domain Class和Named Domain Class。Domain Class就是DSL的重要組成元素,它對DSL中某個特定的概念進行了准確的定義;Named Domain Class其實就是Domain Class,只不過它默認包含了一個字符串類型的領域屬性:Name屬性。在實際應用中使用所設計的DSL時,當我們創建一個Named Domain Class的實例時,就可以通過Name屬性給這個實例設置一個名稱。需要注意的是,當某個Domain Class繼承於Named Domain Class時,無需再對Domain Class指定Name屬性,此時Name屬性將會被繼承下來。

關系類工具

關系類工具主要是用於指定領域類型之間的關系,這類工具包括:Embedding Relationship、Reference Relationship以及Inheritance。

  • Embedding Relationship:用於指定兩個領域類型之間的包含關系。在定義Embedding Relationship的時候,可以在關系的兩端指定對方類型的重復數(Multiplicity)。最常見的應用就是用於定義DSL模型與領域類型之間的關系:一個DSL模型可以包含某種領域類型,可以用Has一詞來表述這種關系。下圖中的ExampleModel和ExampleElement就是用這種關系關聯起來的,它表述了這樣的概念:ExampleModel可以包含0至多個ExampleElement,而ExampleElement則必須屬於某個ExampleModel:
    9

  • Reference Relationship:用於指定兩個領域類型之間的引用關系。比如在設計類圖(Class Diagram)的DSL時,我們可以設計面向對象編程語言中的“類”可以引用關聯到多個“接口”,當然在面向對象技術中,這種引用關聯有個專業術語,稱為“實現(Realization)”。試想一下,如果將“接口”方面的重復數設置為1,那么我們所設計的這門面向對象語言就僅支持“單接口實現”,也就是一個“類”只能實現一個“接口”。以下是引用關系的一個樣例。在這個樣例中,我們可以了解到,在我們所設計的DSL中,ExampleElement可以引用0至多個ExampleElement,而它也能被0至多個其它的ExampleElement所引用
    10

  • Inheritance:用於指定兩個領域類型之間的繼承關系。在DSL的定義中使用繼承關系,可以將具有相同行為和屬性的領域類型抽象為一個基類型,並在基類型中對這些行為和屬性進行定義。比如:某個狀態流的DSL定義中包含三種類型的狀態:起始狀態、中間狀態和結束狀態,這些都是“狀態”概念的具體表述形式,因此,可以設定一個“狀態”的抽象領域類型,並使得“起始狀態”、“中間狀態”和“結束狀態”都繼承於“狀態”抽象領域類型。以下是DSL設計器中對繼承關系的表現方式:
    11

圖形類工具

在實際應用中,我們可以使用某種圖形來表示DSL中的概念。這些圖形的形式是多樣化的,比如在狀態流DSL中,我們可以使用空心圓來表示起始狀態,使用圓角矩形來表示中間狀態,以及使用實心圓來表示結束狀態。Visual Studio 2012 DSL集成開發環境為DSL開發人員提供了豐富的圖形類工具,包括:Geometry Shape、Compartment Shape、Image Shape、Connector、Port Shape以及Swimline。

  • Geometry Shape:表示一種幾何圖形,包括矩形、圓角矩形、橢圓形和圓形四種形式。在開發DSL時,可以在設計器上對幾何圖形的線條粗細、線條顏色、填充色等屬性進行設置
  • Compartment Shape:這是一種組合型的圖形,這種圖形由多個部分組成,每個部分被稱為一個“間隔”(Compartment)。例如:類圖(Class Diagram)中就是使用Compartment Shape對類和接口進行展示的,類或接口的圖形會被分為幾個部分:首先是類或接口的名稱,然后是“字段”部分,再是“屬性”部分,之后就是“方法”部分。Compartment Shape對於創建類似於類圖這樣的DSL是非常適用的,以下就是一個類圖的例子,可以幫助我們直觀地了解Compartment Shape:
    12

  • Image Shape:使用一幅圖片來表示某個DSL中的概念。比如可以在所設計的DSL中,使用一個“小人”的圖片來表示“角色”的概念
  • Connector:這種圖形主要用在領域關系上。例如領域類型A通過引用關聯與領域類型B建立了關系,那么在DSL的實際應用中,我們就可以使用一根線條來連接A和B的實例,以表示兩者之間的引用關系。這根線條就是一個Connector圖形,在DSL的開發過程中,我們也能夠通過DSL設計器對Connector圖形的線型、線條粗細、顏色、箭頭等屬性進行設置
  • Port Shape:用以表示端點的圖形。例如有些類圖中,會以一個端點的形式來表示其所實現的接口,還有一些組件視圖也會用這種圖形來表示組件向外界提供的接口等
  • Swimline:用以表示泳道圖形。泳道通常只有在一些非常特殊的情形下才會被用到,例如在設計分層架構的DSL中,可以把架構中不同的層看成是獨立的泳道,在每個泳道中,只能包含一些特定的領域類型,比如“客戶端展現機制”只能放在“展現層”泳道中等

其它工具

最后一個需要介紹的工具就是Diagram Element Map,它是一個連接工具,用於將DSL設計器中左邊的領域類型/關系跟右邊的圖表元素(Diagram Element)連接起來,以表示領域類型/關系應該使用哪個圖形進行展現。在開發DSL的時候,選擇這個工具,然后用鼠標從領域類型/關系拖拽到相應的圖表元素上,即可完成兩者映射的建立。

使用Visual Studio 2012進行DSL開發調試

在解決方案資源管理器中,Dsl工程上單擊鼠標右鍵,選擇Set as StartUp Project選項將其設置為啟動工程,然后按F5鍵即可啟動Visual Studio 2012 Experimental Instance,並對DSL進行調試。

13

在調試環境中,可以在“解決方案資源管理器”中看到兩個DSL文件:Sample.mydsl1和Test.mydsl1,雙擊Sample.mydsl1即可在設計器中看到DSL的范例,而Test.mydsl1則是供開發人員測試用,開發人員可以在Test.mydsl1的設計器中,使用工具欄中的工具添加一些領域類型/關系的實例,以便對自己所開發的DSL進行測試。在下一個部分中,我們還會使用這個Experimental Instance來調試我們的客戶化代碼,並創建一些T4模板以實現自動化代碼生成。

小結

本文是《在Visual Studio 2012中使用VMSDK開發領域特定語言》專題文章的第一部分。本文首先對領域特定語言(DSL)進行了簡單的介紹,包括DSL的概念、分類和例子;然后還講解了Visual Studio 2012中開發DSL的先決條件、環境配置以及開發環境的窗口布局和工具欄等;最后簡要地介紹了Visual Studio 2012下DSL的調試功能。在接下來的第二部分中,我們將從一個實際案例入手,介紹DSL開發的基本過程、客戶化定制、T4自動化代碼產生、DSL的部署,以及在生產環境中使用DSL等內容。


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM