[Architecture Design] 3-Layer基础架构

[Architecture Design] 3-Layer基础架构

三层式体系结构

[Architecture Design] 3-Layer基础架构

只要是软件从业人员,不管是不是本科系出身的,相信对于三层式体系结构一定都不陌生。在三层式体系结构中,将软件开发所产出的程序代码,依照不同用途归类为:系统表示层、领域逻辑层、数据存取层。其中:

  • 系统表示层 (Presentation Layer),用来归类「提供操作接口」的相关程序代码。例如:提供Textbox接受用户输入地址数据、透过MessageBox通知用户处理结果、甚至是提供Web API给远程系统使用,这些程序代码都会被归类在表示层之中。

  • 领域逻辑层 (Domain Layer),用来归类「封装系统逻辑」的相关程序代码。例如:商城系统的商品数据、购物车、对账单,或者是出勤系统的上下班记录、员工数据…等等,这些程序代码都会被归类在领域逻辑层 之中。

  • 资料存取层 (Access Layer),用来归类「实作数据存取」的相关程序代码。例如:将数据存放到SQL Server、或者是从远程WebService取得数据…等等,这些程序代码都会被归赖在存取层之中。

透过三层式体系结构的设计,让开发人员能够粗略的将系统拆解为三个不同面向的分类。透过套用这样的架构设计,在开发过程中,能够减少需要思考的设计内容,让开发人员一次只需要思考某个面向的设计内容。而在后续的维护过程中,也让开发人员能够分门别类的去理解既有的程序内容。

基础架构设计

[Architecture Design] 3-Layer基础架构

虽然说,只要是软件从业人员,对于三层式体系结构都不陌生。但是三层式体系结构毕竟是在比较早期的年代所提出,随着面向对象语言的发明、后续DI与IoC概念的加入、以及DDD、TDD等技术的出现。让开发人员在撰写一个套用三层式体系结构的软件系统,需要考虑的方方面面变得越来越多,一层堆一层的知识体系,已经慢慢榨干开发人员的脑细胞活力。不过还好的是,现代的软件业界出现了「软件架构师」这一个职缺。在项目开发的过程中,透过软件架构师的努力,来消化知识体系的堆积,产出为符合项目需求、团队能力的软件架构。后续团队内的开发人员只要照着软件架构去按图施工,就能生产具有一定水平的系统软件。

接下来的内容,介绍一个以三层式体系结构为核心所发展出来的基础软件架构。期望让没有软件架构师加持的开发人员,能有一个基础的软件架构来按图施工,除了增加软件开发的速度之外,也让项目产出的程序代码能够满足:进行单元测试、重用系统逻辑、抽换数据源….等等非功能性需求。

领域逻辑层 (Domain Layer)

[Architecture Design] 3-Layer基础架构

接着从三层式体系结构中的领域逻辑层来看看,领域逻辑层所包含的对象该如何设计。领域逻辑层主要用来归类「封装系统逻辑」的相关程序代码,而「系统逻辑」这个概念,可以拆解为几个模式来分门别类的设计:

  • Entity:Entity主要用来将数据单元封装成为对象。在设计系统的时候,一个一个系统要处理的数据单元可以封装成为Entity对象,每个Entity对象的属性则是用来提供数据单元所包含的数据内容,像是上图中的Employee对象、Product对象用来代表系统所要处理的员工数据、商品数据。另外,Entity对象的方法,也用来封装各种运算功能;例如一个判断员工是否成年的功能,就可以选择封装成为Employee对象的方法来提供使用。

  • IRepository:IRepository主要用来定义Entity对象进出领域逻辑层的接口(系统边界)。在设计系统的时候,当有Entity对象需要进出数据库或是远程服务,可以将进出功能封装成为IRepository接口的方法,像是上图中的IEmployeeRepository接口用来提供Employee对象进出系统数据库的方法定义。另外,IEmployeeRepository接口的方法,也用来封装各种查询功能;例如一个查询所有售价少于100元商品的功能,就可以选择封装成为IEmployeeRepository界面的方法来提供使用。

  • Service:Service主要用来封装系统终难以归类的系统功能。在设计系统的时候,当有一个系统功能分派给Entity对象不适合、分派给IRepository接口也不适合的时候,就可以考虑额外将这个系统功能封装为Service对象。例如一个转账功能,可能牵扯好几个帐户、并且需要许多交易纪录,这时分派给Entity对象、IRepository接口会让这两者的耦合度过高,这时就可以选择封装一个独立的转账服务对象来提供使用。

  • Context:Context主要用来提供统一的进入点来使用系统功能。在设计系统的时候,系统功能会被分派到 Entity对象、IRepository接口、Service对象来提供使用,这时加入Context对象将原本四散的职责黏合,能够简化使用时的复杂度。另外一个进阶的议题,是统一由Context对象来提供注入与保存IRepository接口实作,也是一个减少领域逻辑层与DI Framework相依的一种解决方案。

透过上列几个模式来分门别类的设计,基本上就可以将领域逻辑层所要封装的系统逻辑,都建立成为一个个的对象。而套用这样的设计架构,因为透过IRepository接口来建构出系统边界,所以让单元测试得以进行;透过Context对象来简化系统复杂度,让重用系统逻辑、抽换数据源变成可行。

系统表示层 (Presentation Layer)

[Architecture Design] 3-Layer基础架构

谈完最核心的领域逻辑层,接着看系统表示层就比较轻松一点了。系统表示层 (Presentation Layer),用来归类「提供操作接口」的相关程序代码,这边所定义的操作接口有一个很重要的概念是,操作接口包含:

  • 给人使用的UI接口:给人使用的UI接口,就是应用程序的用户接口。透过应用程序的所提供的可视化接口,系统就可以提供信息给用户,并且接收用户所输入的数据以及操作。

  • 给系统使用的API接口:给系统使用的API接口,就是服务程序的通讯接口。服务程序提供各种通讯接口来开放功能给外部系统使用,这些接口可能会是WCF、Web Service、甚至是最近很夯的SignalR,都会是系统所提供的API接口的其中一种可选择方案。

回过头综观整个系统表示层的设计,简单说就是对上提供接口给其他人使用,对下依照接口操作去使用领域逻辑层所提供的功能。例如说:领域逻辑层提供了一个「查询所有售价少于100元商品的功能」,当系统提供UI接口给人使用时,可能就会提供一个显示页面来条列所有从资料存取层查询出来的商品;当系统提供API接口给系统使用时,可能就会建立一个Web API的方法来提供查询,并且使用JSON格式来回传所有从数据存取层查询出来的商品。

当然,没有好处的事,我们不要去做。系统中切割出系统表示层,在项目开发的过程中会得到一个很大的好处就是:系统表示层是用户最会修改的设计。做项目时,使用者最会有意见的就是系统表示层,当开发人员将系统表示层独立切割开来,就算画面怎么变化背后的逻辑还是不会变。这样开发人员心理上,就能比较轻松去面对客户的挑战(反正也只是画面调调而已),而实质上因为不会动到核心的领域逻辑层,也避免了改这边坏那边的窘境。

资料存取层 (Access Layer)

[Architecture Design] 3-Layer基础架构

最后看看在三层式体系结构中最单纯的数据存取层。数据存取层 (Access Layer),用来归类「实作数据存取」的相关程序代码,这边所定义的实作数据存取,就是很简单的实作领域逻辑层中所定义的IRepository接口。但是虽然说是很简单的实作IRepository接口,却也是包含了许多的变化:

  • 数据存放到数据库(远程):一般的Web网站、应用程序,会选择将数据存放到远程的Sql Server。

  • 数据存放到数据库(近端):在一些APP的开发项目中,会选择将数据存放在本地端的SQLite。

  • 数据存放到系统(远程):在一些APP的开发项目中,会选择将数据透过Web Service存放在远程的服务器上。

  • 数据存放到系统(近端):在一些复杂的开发项目中,会选择数据源是另外一个领域逻辑层。例如:一个购物商城的客户数据源,有可能是由另外一个客户管理系统来提供。

  • 依照运行状态决定:在一些APP的开发项目中,会依照网络联机状态来决定数据存放位置。例如:网络断线时存取本地端的SQLite、网络联机时存取远嘟的WebService。

  • ......

在上列这些可以选择的解决方案中,比较有趣的是数据存放到系统、依照运行状态决定这两个变化样式。其中数据存放到系统这个变化样式,为系统加入了串接各种系统的可能性,让系统能够像堆积木一样,一个串一个的互相结合,大幅增加系统的重用性。而依照运行状态决定,则是一个重用程序代码的设计,只要额外加入一个IRepository实作来判断运行状态,并且分配该去呼叫哪个现有的Sql实作、Web实作,就可以重复使用既有程序。

同样的,没有好处的事,我们不要去做。系统中切割出数据存取层,在项目开发的过程中会得到一个很大的好处就是:数据存取层是开发过程中最会修改的设计。做项目的时候,系统开发的过程中很多数据字段都要到项目中后期客户才会发现(很神奇,但真实)。当开发人员将资料存取层独立切割开来,并且在数据字段定案之前,都先透过对象操作的方式来提供数据,并在最后的最后才去建立SQL数据表。这样在开发的过程中,就不需要反复的调整数据表字段、检查对应的SQL指令,大量减少开发人员花在这些琐碎工作上的时间成本。

下载

范例程序代码:点此下载

上一篇:Windows 查看端口占用和关闭进程


下一篇:Spring+SpringMVC+MyBatis+easyUI整合进阶篇(一)设计一套好的RESTful API