【问题标题】:Solution for services being bloated with getter methods?使用 getter 方法导致服务臃肿的解决方案?
【发布时间】:2014-10-08 09:49:03
【问题描述】:

我们正在将核心 Web 应用程序重构为 3 层 SOA 架构:

  • 演示文稿(asp mvc)
  • 服务层(wcf、net.tcp)
  • 持久性 (sql)。

大约有 5 个内部网站,每个都有自己的服务层。 服务和 UI 通过 DTO 在彼此之间共享信息。 问题是,大多数服务方法都是专门为 UI 设计的,因此一些服务变得非常庞大,例如:

// CompanyService:
CompanyDTO GetByName(String)  
CompanyDTO GetByNameAndCity(String, String)
CompanyDTO GetByNameOrCity(String, String)
CompanyDTO GetByStartsWithNameOrStartsWithCity (String, String)
CompanyDTO GetByNameOrStartsWithCity(String, String)
CompanyDTO GetByNameOrStartsWithCityAndHavingAtLeastOneUser(String, String)
...
CompanyPeopleDTO GetByNameWithPeople(String)
CompanyPeopleDTO GetByNameAndCityWithPeople(String,String)
CompanyPeopleActivityDTO GetByNameWithPeopleAndActivites(String)
...

有时会有一些非常具体的查询,例如:

CompanyAdmin1DTO GetComplexForAdmins1(String, String, String, String, Boolean, Boolean, int) // name,city,country,email, is deleted, is active, founded
CompanyAdmin2DTO GetComplexForAdmins2(String, String, String, Boolean) // name starts with, city starts with, have users
CompanyAdmin3DTO GetComplexForAdmins3(String, String, String, int) // name OR city AND have users

我们最终得到了大量的 fetch 方法,其中实际的逻辑方法正在丢失。 是否有更好的命名约定,甚至是完全不同的方法?(不暴露域/持久性) Web/服务层在物理上是分开的,所以 WCF 是必须的。

【问题讨论】:

  • 可能有替代方案,但我知道这些是 façade 方法,也许看起来很乱.. 但归根结底,façades 试图解决使用底层域时的复杂性..

标签: c# .net web-services wcf soa


【解决方案1】:

我喜欢将过滤器传递给通用的 get 问题,类似的事情

IEnumerable<stuff> GetTheStuff(params Func<stuff, bool> filters)
{
    IQueryable<stuff> resultList = DAL.Stuff.Queryable;
    foreach (var filter in filters)
    {
        resultList = resultList.Where(filter);
    }
}

(语法是近似的,我手边没有 IDE)

优点是您可以在调用中组合任何过滤器以使其执行您需要的操作:查询保持显式并且您不会死于 Get* 签名过载

GetStuff(stuff => stuff.Name.Contains("great stuff"), stuff => stuff.CreationDate.Year == 1900 );

如果您需要在调用中明确表达,您甚至可以将过滤器表示为参数

var NamedGreat = stuff => stuff.Name.Contains("great stuff");
var CreatedIn1900 = stuff => stuff.CreationDate.Year == 1900;

GetStuff(NamedGreat, CreatedIn1900);

在此基础上您有很多变体,但它应该可以帮助您避免方法过于显式的重载。


这会暴露部分域,但您可以创建能够针对 DTO 表达的过滤器(例如)。请注意,尽管不要将过滤器应用于已解析的对象列表(例如 DTO 本身),因为这意味着您正在查询整个数据库而不是在其中进行过滤

【讨论】:

  • 服务内的域是隔离的,因此不会暴露给 UI。这将在服务层中很好地工作。
  • 您可以将过滤器表示为参数,请参阅编辑
【解决方案2】:

samy 提出的一个变体如下:

你可以这样表达GetCompany操作:

CompanyDTO GetCompany(CompanyQueryBase query)

其中 CompanyQueryBase 是一个抽象类,从它派生出以下数据协定:

CompanyNameQuery
CompanyNameAndCityQuery
CompanyNameOrCityQuery
CompanyStartsWithNameOrStartsWithCityQuery
CompanyNameOrStartsWithCityQuery
CompanyNameOrStartsWithCityAndAtLeastOneUserQuery

如果您愿意,您可以将逻辑附加到查询类,但我更喜欢使用某种查询处理程序工厂,它将为您提供一个可以处理和执行此类查询的类。这意味着基本上你当前所有与公司相关的操作都会变成“Handler”类,剩下的 GetCompany 操作只会调用工厂并执行查询,例如:

CompanyDTO GetCompany(CompanyQueryBase query)
{
   // _queryHandlerFactory is a member of the service class
   // it is best to use DI for injecting the specific query handler factory
   // as a dependency to the class
   var queryHandler = _queryHandlerFactory.GetHander(query);
   return queryHandler.Execute();
}

这将允许您保持当前逻辑大部分不变,只公开一个 Get 方法(用于公司),并且可以轻松扩展以供您需要支持的未来查询。

然而,该解决方案存在一个令人讨厌的小问题。您的客户不会知道 CompanyQueryBase 是一个抽象类(这是因为 DataContractSerializer 不支持 xsd 抽象复杂类型声明,而奇怪的是,XmlSerializer 支持),这意味着您的客户将能够意外地向您发送 CompanyQueryBase,这将导致在服务端的异常中无法实例化抽象类(因为服务知道它的抽象)。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2012-02-07
    • 1970-01-01
    • 2022-10-26
    • 1970-01-01
    • 1970-01-01
    • 2011-06-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多