我们在系统中遇到god object
。该系统由public service
向我们的客户提供,middle office service
和back office service
。
流程如下:用户在public service
中注册了一些交易,然后来自middle office service
的经理检查交易并批准或拒绝交易,最后来自back office service
的经理最终确定或拒绝事务。
我正在使用transaction
这个词,但实际上这些是不同类型的操作,例如CRUD on entity1
,CRUD on entiny2
...不仅CRUD
操作,而且许多其他操作,如approve/send/decline entity1
,make entity1 parent/child of entity2
等等......
现在WCF
服务合同只是根据系统的那些部分分开。所以我们有3份服务合同:
PublicService.cs
MiddleOfficeService.cs
BackOfficeService.cs
每个人都有大量的经营合同:
public interface IBackOfficeService
{
[OperationContract]
void AddEntity1(Entity1 item);
[OperationContract]
void DeleteEntity1(Entity1 item);
....
[OperationContract]
void SendEntity2(Entity2 item);
....
}
在所有3项服务中,这些运营合同的数量已经达到2000个,每个服务合同约为600个。它不仅打破了最佳实践,而且只需更新服务引用就会非常痛苦,因为它需要很长时间。而且系统每天都在增长,每次迭代都会在这些服务中添加越来越多的操作。
现在我们面临两难困境,因为我们如何将这些神服务分解为逻辑部分。有人说服务不应包含超过12~20次操作。其他人说一些不同的事情。我意识到没有黄金法则,但我希望听到一些关于此的建议。
例如,如果我只是按实体类型拆分这些服务,那么我可以在项目中获得大约50个服务端点和50个服务引用。在这种情况下,可维护性是什么?
还有一件事需要考虑。假设我选择了按实体拆分这些服务的方法。例如:
public interface IEntity1Service
{
[OperationContract]
void AddEntity1(Entity1 item);
[OperationContract]
void ApproveEntity1(Entity1 item);
[OperationContract]
void SendEntity1(Entity1 item);
[OperationContract]
void DeleteEntity1(Entity1 item);
....
[OperationContract]
void FinalizeEntity1(Entity1 item);
[OperationContract]
void DeclineEntity1(Entity1 item);
}
现在发生的事情是我应该在public client
和back office client
中添加对此服务的引用。但back office
仅需FinalizeEntity1
和DeclineEntity1
次操作。因此,这是Interface segregation principle
中SOLID
的经典违规行为。所以我必须进一步分为3个不同的服务,如IEntity1FrontService
,IEntity1MiddleService
,IEntity1BackService
。
答案 0 :(得分:5)
这里的挑战是重构代码而不更改代码的大部分以避免潜在的回归。
避免使用数千行的大型业务代码的一种解决方案是将接口/实现拆分为多个部分,每个部分代表一个给定的业务域。
例如,您的IPublicService
界面可以编写如下(使用界面继承,每个业务域一个界面):
IPublicService.cs
:
[ServiceContract]
public interface IPublicService : IPublicServiceDomain1, IPublicServiceDomain2
{
}
IPublicServiceDomain1.cs
:
[ServiceContract]
public interface IPublicServiceDomain1
{
[OperationContract]
string GetEntity1(int value);
}
IPublicServiceDomain2.cs
:
[ServiceContract]
public interface IPublicServiceDomain2
{
[OperationContract]
string GetEntity2(int value);
}
现在,对于服务实现,您可以使用部分类(每个业务域的一个部分类)将其拆分为多个部分:
Service.cs
:
public partial class Service : IPublicService
{
}
Service.Domain1.cs
:
public partial class Service : IPublicServiceDomain1
{
public string GetEntity1(int value)
{
// Some implementation
}
}
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次操作 。除非您重构整个应用程序并减少此数量(例如,通过提供更多&#34;高级别&#34;(并非总是可能)的服务)。
答案 1 :(得分:2)
拥有太多的操作合同在给定的服务中没有意义,因为它会导致维护问题。如果说如果Add(),Delete,Update(),AddChildItem(),RemoveChildItem()等操作应该在一起,那么不要担心操作合同的数量达到30-40。因为应该在一起的东西应该来自一个界面(凝聚力)。
但是,给定服务合同中的600个操作实际上是压倒性的。您可以开始识别操作: -
基于此,您可以将操作拆分为不同的服务。
如果客户端没有直接使用某些方法,那么请考虑根据BUSSINESS逻辑公开方法(“MatthiasBäßler”也建议)。
假设您要公开MoneyTransfer功能。那么你不需要公开
因此,您可以在此处向Web应用程序公开聚合服务。在这种情况下,它可能是IAccountService,其方法就像
在您的实施内部,您可以创建其他服务,提供相关操作,如: -
这样,给定服务中的方法数量将降低到可维护的水平。
答案 2 :(得分:1)
你的问题不是上帝对象问题,因为它是一个服务组合问题。由于不同的原因,上帝对象存在问题,而不是基于crud的巨大服务接口存在问题。
我当然同意你所描述的3份服务合同已达到有效无法管理的程度。与重构相关的痛苦将不成比例地高于过程中的代码,因此您采取正确的方法非常重要,因此您的问题。
不幸的是,soa中的服务可组合性是一个非常大的主题,你不太可能在这里获得大量有用的答案;虽然显然有用,但其他人的经验不太可能适用于您的情况。
我已经在SO before上写过这篇文章,所以对于它的价值,我会包括我的想法:
如果服务操作可以存在于某个级别,我发现它是最好的 他们有商业意义。
这意味着如果一个商人被告知操作 名字,他们会大致了解调用该操作的内容 做,并可以猜测它需要传递什么数据 它。
为此,您的操作应全部或部分完成 一些业务流程。
例如,以下操作签名具有商业含义:
void SolicitQuote(int brokerId, int userId, DateTime quoteRequiredBy); int BindPolicyDocument(byte[] document, SomeType documentMetadata); Guid BeginOnboardEmployee(string employeeName, DateTime employeeDateOfBirth);
如果您在考虑服务组合时使用此主体,那么 好处是你很少偏离最佳路径; 你知道每个操作的作用,你知道什么时候操作没有 需要更久。
另一个好处是因为业务流程公平变化 很少你不需要改变你的服务合同。
答案 3 :(得分:1)
我没有WCF的经验,但我认为上帝类和重载接口似乎是一般的OOD问题。
在设计系统时,您应该寻找行为(或业务逻辑)而不是数据结构和操作。不要看看你将如何实现它,但客户将如何使用它以及如何命名它。根据我的经验,拥有正确的方法名称通常会提供关于对象的大量线索。
对我而言,令人大开眼界的是the design of the Mark IV coffee maker,摘自&#34; UML for Java Programmers&#34;罗伯特C.马丁。对于有意义的名字,我推荐他的书&#34;清洁代码&#34;。
因此,不是建立离散操作的界面,而是:
GetClientByName(string name);
AddOrder(PartNumber p, ContactInformation i);
SendOrder(Order o);
做类似的事情:
PrepareNewOrderForApproval(PartNumber p, string clientName);
完成此操作后,您还可以重构为单独的对象。