在工作中接触过几个MVC的项目,基本都用到了依赖注入,其中IOC框架有用Unity、Ninject、Castle windsor。基本也会使用仓储模式,数据库方面有使用EF、Nhibernate和Dapper。基本都是那么多工具,但是整个项目的架构去各不相同。最近读到国外一个MVP作者的文章,觉得是一个很好的架构总结,记录在这里。

下面是基于MVC的系统架构设计图截图:

下面是整个项目的文件结构截图:

Models

这个类库是存放所有领域对象的地方。这里只有纯粹的POCO的类,对应到数据库表,除了POCO文件以外,不要有任何东西。建议不要使用DataAnnotations属性来配置Code First,把配置都写到专门的配置文件,使用Fluent API.

Data Access Layer and Repositories

这个层的目的是直接访问数据库,这也是唯一负责与数据库沟通的层,他通常是一个单独的类库,这里包含Repository的抽象接口和实现类,Entity Framework的配置,DbContext的实现类,以及其他与数据库访问相关的东西。

这里会实现仓储模式,工作单元模式。

Service Layer

这里通常是放置业务逻辑的地方,控制器可以调用服务层的一些操作。

Presentation Layer

这里是我们的表现层,即MVC项目。

服务层的业务逻辑会通过依赖注入的方式,注入到MVC的控制器里面,然后控制器把服务层处理的数据,发送到视图,从而展现到页面上。

在真是的应用里面,领域模型会有很多属性,但是你不希望把所有这些属性展现到页面上,也不希望从浏览器接收那么多的数据。例如从form表单创建对象,你只需要post领域模型少量的属性。这样我们就需要定义ViewModel。当然,在领域模型和视图模型之间,我们可能会使用工具来进行转换。

 

对这个设计的一些疑问及回答:
1.在服务层,Repository和UnitOfWork拥有各自的dbContext,那么UnitOfWork怎么去保存Repository的会话数据到数据库?
回答:其实对于每次请求,Repository和UnitOfWork里面的dbContext都是通过Autofact依赖注入的同一个上下文。

2.在服务层,为什么在创建和修改的方法里面,不直接调用Commit方法把修改立即保存到数据库,而需要控制器在调用修改或者删除方法后,再单独的调用一次Commit方法?
回答:也并不是不能这样做,看你怎么设计数据访问层。一个服务层的方法负责一个特定的任务,一个控制器的调用可能想在完成多个操作后,统一保存到数据库。这样也是把创建和保存的职责分开到两个方法里面了。

3.视图模型和领域模型的映射发生在UI层,这样导致我的表现层要引用领域模型层。那么,能不能把视图模型传递给服务层,在服务层去做模型之间的转换?这样业务相关的领域模型比较安全,又不用暴露到UI层。
回答:当然可以把转换操作移到服务层,但是这样做可能会有问题:当另外一个组件需要以不同的方式使用服务层的时候,就会有问题。例如一个Web API层可能想要从领域实体暴露的属性与视图模型不一致,可能比我们在UI层使用的视图模型多或者少一些属性。
需要记住的重要的事情是:Web或者API层应该通过服务层访问数据库。

本文的源代码在原作者的博客可以下载。

发表评论

电子邮件地址不会被公开。 必填项已用*标注