我们在系统中遇到了一个神对象。该系统包括面向客户的公共服务,中台服务和后台服务。
流程如下:用户注册一些……
我没有WCF的经验,但我认为上帝类和重载接口似乎是一般的OOD问题。
在设计系统时,您应该寻找行为(或业务逻辑)而不是数据结构和操作。不要看你将如何实现它,而是看客户如何使用它以及如何命名它。根据我的经验,拥有正确的方法名称通常会提供关于对象及其耦合的大量线索。
对我来说,令人大开眼界的是 Mark IV咖啡机的设计 ,摘自Robert C. Martin的“UML for Java Programmers”。对于有意义的名字,我推荐他的书“清洁代码”。
因此,而不是构建离散操作的接口,如:
GetClientByName(string name); AddOrder(PartNumber p, ContactInformation i); SendOrder(Order o);
做类似的事情:
PrepareNewOrderForApproval(PartNumber p, string clientName);
完成此操作后,您还可以重构为单独的对象。
这里的挑战是重构代码而不改变代码的大部分以避免潜在的回归。
避免使用数千行的大型业务代码的一种解决方案是将接口/实现拆分为多个部分,每个部分代表给定的业务域。
例如,你的 IPublicService 接口可以写成如下(使用接口继承, 的 每个业务域的一个接口 强> ):
IPublicService
IPublicService.cs :
IPublicService.cs
[ServiceContract] public interface IPublicService : IPublicServiceDomain1, IPublicServiceDomain2 { }
IPublicServiceDomain1.cs :
IPublicServiceDomain1.cs
[ServiceContract] public interface IPublicServiceDomain1 { [OperationContract] string GetEntity1(int value); }
IPublicServiceDomain2.cs :
IPublicServiceDomain2.cs
[ServiceContract] public interface IPublicServiceDomain2 { [OperationContract] string GetEntity2(int value); }
现在,对于服务实现,您可以使用部分类将其拆分为多个部分( 的 每个业务域的一个部分类 强> ):
Service.cs :
Service.cs
public partial class Service : IPublicService { }
Service.Domain1.cs :
Service.Domain1.cs
public partial class Service : IPublicServiceDomain1 { public string GetEntity1(int value) { // Some implementation } }
Service.Domain2.cs :
Service.Domain2.cs
public partial class Service : IPublicServiceDomain2 { public string GetEntity2(int value) { // Some implementation } }
对于服务器配置,仍然只有一个端点:
<system.serviceModel> <services> <service name="WcfServiceLibrary2.Service"> <endpoint address="" binding="basicHttpBinding" contract="WcfServiceLibrary2.IPublicService"> <identity> <dns value="localhost" /> </identity> </endpoint> <endpoint address="mex" binding="mexHttpBinding" contract="IMetadataExchange" /> <host> <baseAddresses> <add baseAddress="http://localhost:8733/Design_Time_Addresses/WcfServiceLibrary2/Service1/" /> </baseAddresses> </host> </service> </services> <behaviors> <serviceBehaviors> <behavior> <serviceMetadata httpGetEnabled="True" httpsGetEnabled="True" /> <serviceDebug includeExceptionDetailInFaults="False" /> </behavior> </serviceBehaviors> </behaviors> </system.serviceModel>
对于客户端也是如此:仍然是一个服务引用:
<system.serviceModel> <bindings> <basicHttpBinding> <binding name="BasicHttpBinding_IPublicService" /> </basicHttpBinding> </bindings> <client> <endpoint address="http://localhost:8733/Design_Time_Addresses/WcfServiceLibrary2/Service1/" binding="basicHttpBinding" bindingConfiguration="BasicHttpBinding_IPublicService" contract="ServiceReference1.IPublicService" name="BasicHttpBinding_IPublicService" /> </client> </system.serviceModel>
这允许通过将大型服务拆分为多个逻辑部分(与给定业务域相关联的每个部分)来重构服务器端。
这并不会改变您的3个服务中的每个服务仍然有600个操作的事实,因此客户端代理生成仍然需要很长时间。至少你的代码将更好地组织在服务器端,并且重构将是便宜的并且没有那么危险。
这里没有银弹,这只是代码重组,以提高可读性/维护性。
200个服务,每个服务10个操作,20个服务,每个服务100个操作,这是另一个主题,但可以肯定的是,重构需要更多的时间, 的 你仍然可以进行2000次操作 强> 。除非您重构整个应用程序并减少此数量(例如,通过提供更“高级”(并非总是可能)的服务)。
拥有太多的操作合同在给定的服务中没有意义,因为它会导致维护问题。如果说如果Add(),Delete,Update(),AddChildItem(),RemoveChildItem()等操作应该在一起,那么不要担心操作合同的数量达到30-40。因为应该在一起的东西应该来自一个界面(凝聚力)。
但是,给定服务合同中的600个操作实际上是压倒性的。您可以开始识别操作: -
基于此,您可以将操作拆分为不同的服务。
如果客户端没有直接使用某些方法,那么请考虑基于BUSSINESS逻辑公开该方法(也可参见“Matthias B盲脽ler”)。
假设您要公开MoneyTransfer功能。那么你不需要暴露
因此,您只需向Web应用程序公开聚合服务即可。在这种情况下,它可能是IAccountService与方法一样
在您的实现内部,您可以创建其他服务,提供相关操作,如: -
这样,给定服务中的方法数量将降至可维护的水平。
你的问题不是上帝对象问题,因为它是一个服务组合问题。由于不同的原因,上帝对象存在问题,而不是基于crud的巨大服务接口存在问题。
我当然会同意你所描述的3份服务合同已达到有效无法管理的程度。与重构相关的痛苦将比过程中的代码高得多,所以你采取正确的方法非常重要,因此你的问题。
不幸的是,soa中的服务可组合性是一个非常大的主题,你不太可能在这里获得大量有用的答案;虽然显然有用,但其他人的经验不太可能适用于您的情况。
我在SO上写过这个 之前 ,为了它的价值,我将包括我的想法:
我发现服务操作可以存在于哪个级别是最好的 他们有商业意义。 这意味着如果一个商人被告知该操作 名字,他们会大致了解调用该操作的内容 做,并可以猜测它需要传递什么数据 它。 为此,您的操作应完全或部分完成 一些业务流程。 例如,以下操作签名具有业务含义: void SolicitQuote(int brokerId, int userId, DateTime quoteRequiredBy); int BindPolicyDocument(byte[] document, SomeType documentMetadata); Guid BeginOnboardEmployee(string employeeName, DateTime employeeDateOfBirth); 如果您在考虑服务组合时使用此主体 好处是你很少偏离最佳路径; 你知道每个操作的作用,你知道什么时候操作没有 需要更久。 另一个好处是因为业务流程公平变化 很少你不需要改变你的服务合同。
我发现服务操作可以存在于哪个级别是最好的 他们有商业意义。
这意味着如果一个商人被告知该操作 名字,他们会大致了解调用该操作的内容 做,并可以猜测它需要传递什么数据 它。
为此,您的操作应完全或部分完成 一些业务流程。
例如,以下操作签名具有业务含义:
void SolicitQuote(int brokerId, int userId, DateTime quoteRequiredBy); int BindPolicyDocument(byte[] document, SomeType documentMetadata); Guid BeginOnboardEmployee(string employeeName, DateTime employeeDateOfBirth);
如果您在考虑服务组合时使用此主体 好处是你很少偏离最佳路径; 你知道每个操作的作用,你知道什么时候操作没有 需要更久。
另一个好处是因为业务流程公平变化 很少你不需要改变你的服务合同。