关于两个MVC示例的思考(MVCStore和Oxite)

      最近看了一些关于MVC框架的东西,加以之前就研究过一些关于 MVC架构的信息,碰巧在网上又看
到了 这样一篇文章,是关于微软内部的开发者对Oxite项目的个人攻击,让我产生了写篇文章来表达一 
下自己对于这种架构模式的思考。

    声明,如果之前没看过这两个项目的朋友建议下载相应的源码:
    MVCStore:http://www.codeplex.com/mvcsamples
    Oxite:http://www.codeplex.com/oxite

    好了,开始今天的正文:)

1.Controller干了些什么

     先说一下我的看法,这个所谓控制器的最大作用应该是“控制和调度”,控制即前台视图(view)的
显示(显示那个视图), 调度即执行相应的业务逻辑 (在这两个项目中就是那些Services,而Services
即完成对model数据模型的封装调用,并实现相关的业务逻辑)。这里业务规则如何定义应该是在Ser-
vices里进行,与Controller无关。

     就其工作性质而言还是比较简单的,因此简要的工作内容就应该有简单的实现(指代码),这里可以
看看MVCStore是如何搞的,请见下面代码:

  (摘自Commerce.MVC.Web"App"Controller"AuthenticationController.cs):

public   class  AuthenticationController : Controller
{
        .
        
public  ActionResult Login()
        {
            
string  oldUserName  =   this .GetUserName();

            
string  login  =  Request.Form[ " login " ];
            
string  password  =  Request.Form[ " password " ];

            
if  ( ! String.IsNullOrEmpty(login)  &&   ! String.IsNullOrEmpty(password))
            {
                var svc 
=   new  AspNetAuthenticationService();
                
bool  isValid  =  svc.IsValidLogin(login, password);

                
// log them in
                 if  (isValid)
                {
                    SetPersonalizationCookie(login, login);

                    
// migrate the current order
                    _orderService.MigrateCurrentOrder(oldUserName, login);

                    
return  AuthAndRedirect(login);
                }
            }
            
return  View();
        }
    
}


     一看便知这是一个登陆验证操作,其使用Request.Form方式从表单中获取数据,这里暂不说其获取的方式
优不优雅(因为与本文要聊的内容关系不大)。可以看出其实现的过程也之前采用webform方式开发出现的代码
也差不多,只不过是将相应的login.aspx.cs中的操作放到这controller中,这种好处主要就是将原本分散但功
能上应该同属于认证的类(Authentication类是按架构设时划分出来的)放置在了一起,这样在代码分布上会
更合理一些。另外就是进行单元测试时也会很容易编写测试代码。当然还有好处,我想就是将那些经常变化的
代码使用这种方式约束在了controller中,为将来的后续开发,特别是维护以及查找BUG上会有一个比较清晰的
范围。

     当然在看Oxite代码时,这块会有所差异,即Oxite使用了IModelBinder来实现将表单中的数据绑定到相应
的类上以完成Model中(实体)类的初始化绑定工作,如下:

public   class  UserModelBinder : IModelBinder
{
    
public   object  BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        NameValueCollection form 
=  controllerContext.HttpContext.Request.Form;
        User user 
=   null ;

        Guid siteID 
=  Guid.Empty;
        
if  ( ! string .IsNullOrEmpty(form[ " siteID " ]))
        {
            form[
" siteID " ].GuidTryParse( out  siteID);
        }

        
if  (siteID  ==  Guid.Empty)
        {
            user 
=   new  User
            {
                Name 
=  form[ " userName " ],
                Email 
=  form[ " userEmail " ],
                DisplayName 
=  form[ " userDisplayName " ],
                Password 
=  form[ " userPassword " ]
            };

            Guid userID;
            
if  ( ! string .IsNullOrEmpty(form[ " userID " ])  &&  form[ " userID " ].GuidTryParse( out  userID))
            {
                user.ID 
=  userID;
            }
        }

        
return  user;
    }
}



     其实这种做法有一定的好处,就是将这类在功能上类似的操作进行了封装。比如大家可以想像通过web请求提交过来
一个大表单,上面有几十个字段属性(不要问我为什么会有这种大表单),而如何将这些字段绑定的操作与controller中
接下来的业务流程操作放在一起,会让controller中的相应方法代码长度增长过快,所以倒不如通过上面这个方法将相应
的实体类与Web页面信息的绑定工作分离出来,当然 Oxite使用声明相应UserModelBinder类的方式进行封装的做法还
有待商榷


     说来说去,还是如Rob Conery所说的, 要始终保持用Controller与View的轻快,易于测试这一原则。这一点其实正
与SOA架构中的一些思想相符合,下面结合我的理解来解释一下:

     在SOA中,有组件(component)的概念,即组件是业务逻辑的原子级功能操作,其按照高内聚低耦合的方式进行设计,
当业务流程开发运作时,其工作原理就是正确组合相应的业务组件来实现相应的应用。这样的好处就是可以将这些组件分
布式布署,同时当业务流程发生变化时,只要调整相应的业务流程逻辑(soa中称为bpel)即能够快速响应业务变化。而
如何构造这种可重用的组件在SOA中也是有相应规范的,被称为 SCA.

     说到这里有些远了,那在MVC架构中又有什么类似的思想呢?其实在这里controller的一些设计要求与SOA中的bpel
有着相似的设计理念,即完成对业务组件(即MVCStore解决方案中的Commerce.Services项目下的相应文件)的流程
编排和调用。这样即便将来需求变化,而导致了业务流程的变化(不是业务规则变化),也只是修改Controller这一层应
该可以满足了,而正确而快速的修改的“前提”,应该就是该Controller应该设计得“ 尽可能的轻快”。


 
2.需求变化了,导致了业务规则变化怎么办?
   
     正如Ivar jacbson 在传授“明智开发”模型时所说的那样, “软件开发中不变的是--需求的不断变化”。这一点相信
大家是有强烈共鸣的。

     这里我们先来简单的看一下MVCStore和Oxite的处理方式,从设计思路上两个项目基本一致,即使用接口分离方式来
应对这种变化。比如:Oxite"Services"中就有这样的代码:

public   class  UserService : IUserService
{
      
private   readonly  IUserRepository repository;
      
private   readonly  IValidationService validator;

      
public  UserService(IUserRepository repository, IValidationService validator)
      {
          
this .repository  =  repository;
          
this .validator  =  validator;
      }

      
#region  IUserService Members

      
public  User GetUser( string  name)
      {
          
return  repository.GetUser(name);
      }

      
public  User GetUser( string  name,  string  password)
      {
          User user 
=   string .Compare(name,  " Anonymous " true !=   0   ?  repository.GetUser(name) :  null ;

          
if  (user  !=   null   &&  user.Password  ==  saltAndHash(password, user.PasswordSalt))
              
return  user;

          
return   null ;
      }

      
public   void  AddUser(User user,  out  ValidationStateDictionary validationState,  out  User newUser)
      {
          validationState 
=   new  ValidationStateDictionary();

          validationState.Add(
typeof (User), validator.Validate(user));

          
if  ( ! validationState.IsValid)
          {
              newUser 
=   null ;

              
return ;
          }
       .
}


      其实现了IUserService服务接口。

      而MVCStore中的Commerce.Services项目中的代码也使用了类似接口定义,比如:

[Serializable]
public   class  OrderService : Commerce.Services.IOrderService {

    IOrderRepository _orderRepository;
    ICatalogRepository _catalogRepository;
    IShippingRepository _shippingRepository;
    IShippingService _shippingService;

    
public  OrderService() { }
    
public  OrderService(IOrderRepository rep, ICatalogRepository catalog,
        IShippingRepository shippingRepository, IShippingService shippingService)
    {
        _orderRepository 
=  rep;
        _catalogRepository 
=  catalog;
        _shippingRepository 
=  shippingRepository;
        _shippingService 
=  shippingService;
    }

    
///   <summary>
    
///  Gets all orders in the system
    
///   </summary>
    
///   <returns></returns>
     public  IList < Order >  GetOrders() {
        
return  _orderRepository.GetOrders().ToList();
    }
 .
}


     定义并实现这些服务接口之后,就可以通过IOC这类方式来实现最终的注入,以决定在程序运行时使用那些具体
实现类了,比如Oxite中的Oxite/ContainerFactory.cs是这样进行注册的( 使用了Unity框架):

public  IUnityContainer GetOxiteContainer()
{
     IUnityContainer parentContainer 
=   new  UnityContainer();

     parentContainer
         .RegisterInstance(
new  AppSettingsHelper(ConfigurationManager.AppSettings))
         .RegisterInstance(RouteTable.Routes)
         .RegisterInstance(HostingEnvironment.VirtualPathProvider)
         .RegisterInstance(
" RegisterRoutesHandler " typeof (MvcRouteHandler));

     
foreach  (ConnectionStringSettings connectionString  in  ConfigurationManager.ConnectionStrings)
     {
         parentContainer.RegisterInstance(connectionString.Name, connectionString.ConnectionString);
     }

     parentContainer
         .RegisterType
< ISiteService, SiteService > ()
         .RegisterType
< IPluginService, PluginService > ()
         .RegisterType
< IUserService, UserService > ()
         .RegisterType
< ITagService, TagService > ()
         .RegisterType
< IPostService, PostService > ()
         .RegisterType
< ITrackbackOutboundService, TrackbackOutboundService > ()
     ..
}



    当然这种做法是有普遍性的,好处也是很明显。就是将来如果业务规则变化时(对应service接口实现类
也要发生变化),这时不需要真正修改已有的代码,只需再开发一个相应的实现类即可满足需求,这种扩展
方式也是与设计模式中的思想相符合的。

    说到这里,把话题再深入一下,就是微软模式与实践小组的Service Layer Guidelines中对象这块还会
有一个Application Facade(在其Business层中),如下图:

   

     其完成的是对这些service组件的“应用层面级”封装,说的再白一些,其可以包括对业务工作流,业务
实体,业务组件的三者的封装。以便于对外实现(暴露)统一的服务访问接口。就这部分而言,MVCStore
做的比Oxiete要好,其在工作流中对各类已定义的服务组件的逻辑调用写的很有味道,比如Commerce.-
Services项目下的 AcceptPayPalWorkflow.csShipOrderWorkflow.cs

     当然就目前工作流的作用远不止这些,必定其也可以采用WCF服务的方式把自己暴露给外界。就这一
点,其自身也可以转化为一个服务组件,到这里就出现了一个有趣的现象,即:

     已将一些服务组件囊括的工作流自己也成了一个服务组件而被其它服务组件所调用。不是吗?

     在SOA架构中,这种情况是很普遍的,因为组件是一些基本的业务规则逻辑,其应允许被其它组件访问
甚至包含以使业务规则更加清晰,说白了就是可复用性。
   
     对开发者而言只有这样才可能提升开发速度(重用已有组件的好处不仅仅是少写代码,还包括测试和布
署等方面的成本也会降低),这一点想一想那些开源的框架就会理解了。而对于企业管理者而言就是保护“
已有投资”


3.两个项目中的困惑

   
     的确,看了这两个MVC之后,还是有些让我感觉不是太清晰的地方,比如MVCStore中,Commerce.Data
项目下的Model/Order.cs类,我刚开始一看,还真被震住了,很有 充血模型的味,下面是部分代码:

Code


     可正当我带着兴趣去观察其它相应的域模型类时,又回到了贫血域模型。不是吗?的方法是要感觉好像
不是一个开发人员写的才会出现这种情况,因为按其架构设计上来看,这个类中被放到Serivce中实现的,
因为我不是该项目的开发人员,想不出个所以然来。


     白乎了这些,其它在这两个项目中还有一些差异,当然本文开头提到的那篇文章也说出了一些“问题”。
不过还是那句话,没有最好的设计只有最适合的设计,这两个项目都有可圈可点的地方,但对自己所在公
司部门是不是“完全适合”只能结合自己团队的情况而定了。

     比如说关于Commerce.MVC.Web中将controller和view放在了一起,就是个问题,比如在团队中
有如下分工:

     VIEW开发人员 + Controller开发人员 + Service组件开发

     那么将View目录与Controller目录放在不同的项目中应该是个不错的方式,起码在项目级别上将这
两类开发者进行了分离。当然有人会说,一般情况下VIEW 和Controller的设计者应该是一个人而不是
两个人, 但分工明确才能尽一步提升生产力,特别是MVC这个框架还很新,有些开发人员学习是从View
语法入手,有些人从Controller入手,有些人比如我是从Service入手。这就导致关注和侧重点不同,最
后导致自己的理解和优势也会不同。将View分离出来的好处在于发挥各自的优势,让前台开发人员可以
将精力放在与UI设计师交流设计实现,界面实现,js(目前是JQuery)封装调用等方面。相信随着项目
的不断扩大和开发人员的后续补充势必会造成这样的问题。


     好了,今天的内容就先到这里了。
     
    
     原文链接:http://www.cnblogs.com/daizhj/archive/2009/02/26/1398689.html

     作者: daizhj, 代震军

     Tags: soa,mvc,sca,bpel

     网址: http://daizhj.cnblogs.com
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值