Refactoring God objects in WCF services
Asked Answered
L

4

9

We came across a god object in our system. The system consists of public service exposed to our clients, middle office service and back office service.

The flow is the following: user registers some transaction in public service, then manager from middle office service checks the transaction and approves or declines the transaction and finally manager from back office service finalizes or declines the transaction.

I'am using the word transaction, but in reality those are different types of operations like CRUD on entity1, CRUD on entiny2... Not only CRUD operations but many other operations like approve/send/decline entity1, make entity1 parent/child of entity2 etc etc...

Now WCF service contracts are just separated according to those parts of the system. So we have 3 service contracts:

PublicService.cs
MiddleOfficeService.cs
BackOfficeService.cs

and huge amount of operation contracts in each:

public interface IBackOfficeService
{
    [OperationContract]
    void AddEntity1(Entity1 item);

    [OperationContract]
    void DeleteEntity1(Entity1 item);

    ....

    [OperationContract]
    void SendEntity2(Entity2 item);

    ....
}

The number of those operation contracts are already 2000 across all 3 services and approximately 600 per each service contract. It is not just breaking the best practices, it is a huge pain to just update service references as it takes ages. And the system is growing each day and more and more operations are added to those services in each iteration.

And now we are facing dilemma as how can we split those god services into logical parts. One says that a service should not contain more then 12~20 operations. Others say some different things. I realize that there is no golden rule, but I just would wish to hear some recommendations about this.

For example if I just split those services per entity type then I can get about 50 service endpoints and 50 service reference in projects. What is about maintainability in this case?

One more thing to consider. Suppose I choose the approach to split those services per entity. For example:

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);
}

Now what happens is that I should add reference to this service both in public client and back office client. But back office needs only FinalizeEntity1 and DeclineEntity1 operations. So here is a classic violation of Interface segregation principle in SOLID. So I have to split that further may be to 3 distinct services like IEntity1FrontService, IEntity1MiddleService, IEntity1BackService.

Loosen answered 25/6, 2015 at 10:32 Comment(6)
How long does it take to re-generate the client proxy?Montague
@ken2k, whole process may last 15 minutes. For first 1-2-3 attempts it just timeouts and then updates. It takes 3-4 minutes to get timeout. Sometimes it timeouts 2 times, sometimes only once. Sometimes 3 times. But this is not the main thing. Priority is refactoring. Even if it would take 1 second to update reference we would decide to refactor despite of that fact.Loosen
I could provide a solution with still only 3 services, but the 3 interfaces/implementations separated into multiple interfaces/implementations using interface inheritance and partial implementations. That wouldn't fix the client proxy regeneration time, though.Montague
@ken2k, it is already separated into 3 different ServiceContracts and 3 different implementations.Loosen
I meant, each of your 3 different services could be separated into multiple parts. I'll write an answer so it's more clear with your example.Montague
I posted the idea as an answer, hopefully it's more clearMontague
M
5

The challenge here is to refactor your code without changing large portions of it to avoid potential regressions.

One solution to avoid large business code with thousands of lines would be to split your interfaces/implementations into multiple parts, each part representing a given business domain.

For instance, your IPublicService interface could be written as follows (using interface inheritance, one interface for each business domain):

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);
}

Now for the service implementation, you could split it into multiple parts using partial classes (one partial class for each business domain):

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
    }
}

For the server configuration, there is still only one endpoint:

<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>

Same for the client: still one service reference:

<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>

This allows to refactor your server side by splitting your huge services into multiple logical parts (each part associated with a given business domain).

This doesn't change the fact each of your 3 services still has 600 operations, so the client proxy generation would still take ages. At least your code would be better organized server-side, and the refactoring would be cheap and not-so-risky.

There is no silver-bullet here, that is just code reorganization for better readability/maintenance.

200 services with 10 operations for each vs 20 services with 100 operations for each is another topic, but what is sure is that the refactoring would require way more time, and you would still have 2000 operations. Unless you refactor your whole application and reduce this number (for instance by providing services that are more "high-level" (not always possible)).

Montague answered 29/6, 2015 at 16:22 Comment(2)
Thank you for the answer! Can you elaborate on this one: 200 services with 10 operations for each vs 20 services with 100 operations for each is another topic? Actually I am not limited in time. We are preparing iterations for those types of refactoring.Loosen
This is pretty close to the way we think to refactor. Yes we will do some interface inharitance but instead of partial classes that just splits class into parts we will create separate classes for different domains. But I still would like to hears about some experience with up to 50 service references ÷)Loosen
J
2

Having too many operation contracts doesn't make sense in a given service as it will leads to maintenance issues. Having said that if operations like Add(), Delete, Update(), AddChildItem(), RemoveChildItem(), etc are supposed to be together, then don't worry about operation contract going up to 30-40 in number. Because things that should be together should come out from a single interface (cohesion).

But 600 operations in a given service contract is really overwhelming number. You can start identifying the operations:-

  1. That are required to be together
  2. And that are not required to be together in a given service.

Based on this you can split the operations into different services.

If some of the methods are not used by client directly, then consider exposing the method based on the BUSSINESS logic (as also suggested by "Matthias Bäßler").

Say you want to expose the MoneyTransfer functionality. Then you are not required to expose

  • SendEmail()
  • DebitAccount()
  • CreditAccount(), etc in the service used by your web application.

So here you can expose just an aggregate service to your web application. In this case it may be IAccountService with methods like just

  1. TransferMoney()
  2. GetBalance(),

Internally in your implementation you can create other service which provides related operation like:-

  • SendEmail()
  • DebitAccount()
  • CreditAccount(), etc. required for IAccountService. MoneyTransfer() method.

This way, the number of methods in a given service will come down to a maintainable level.

Jewry answered 6/7, 2015 at 13:38 Comment(0)
K
1

Your problem is not so much a god object problem, as it is a service composition problem. God objects are problematic for different reasons than huge, crud-based service interfaces are problematic.

I would certainly agree that the 3 service contracts you have described are reaching the point where they are effectively unmanageable. The pain associated with refactoring will be disproportionately higher than if this was in-process code, so it's very important you take the correct approach, hence your question.

Unfortunately, service composability in soa is such a huge topic it's unlikely you'll receive massively useful answers here; though obviously useful, the experiences of others will unlikely apply to your situation.

I have written about this on SO before, so for what it's worth I'll include my thoughts:

I find that it's best if service operations can exist at a level where they have business meaning.

What this means is that if a business person was told the operation name, they would understand roughly what calling that operation would do, and could make a guess at what data it would require to be passed to it.

For this to happen your operations should fulfill in full or in part some business process.

For example, the following operation signatures have business meaning:

void SolicitQuote(int brokerId, int userId, DateTime quoteRequiredBy);

int BindPolicyDocument(byte[] document, SomeType documentMetadata);

Guid BeginOnboardEmployee(string employeeName, DateTime employeeDateOfBirth);

If you use this principal when thinking about service composition then the benefit is that you will rarely stray far from the optimal path; you know what each operation does and you know when an operation is no longer needed.

An additional benefit is that because business processes change fairly rarely you will not need to change your service contracts as much.

Kyrakyriako answered 25/6, 2015 at 11:0 Comment(3)
Thank you for taking time to answer. I will clarify a bit: all three projects are silverlight projects. And of course all operations exposed have meaning to business. I would like to hear of other's some real life experience and approaches in situations like that described in the question. Anyway thanks for the answer.Loosen
@GiorgiNakeuri - understand this is not what you were looking for, however I do disagree with your assertion that all operations have business meaning - what does AddEntity1 mean from a business perspective?Kyrakyriako
this is of course for demonstration purpose. I have renamed those. In reality there are meaningful names: AddOrder, ApprooveOrder, SendOrder, LoadOrders, GetOrderByOrderNumber, DeclineAgreement, GetAgreementResource, AddAgreement, etc....Loosen
P
1

I don't have experience with WCF but I think god classes and overloaded interfaces seem to be a general OOD problem.

When designing a system you should look for behaviour (or business logic) instead of data structures and operations. Don't look at how you're going to implement it but how the client would use it and how he would name it. In my experience, having the right names for the methods usually provides a lot of clues about the objects an their coupling.

For me the eye-opener was the design of the Mark IV coffee maker, an excerpt from "UML for Java Programmers" by Robert C. Martin. For meaningful names I recommend his book "Clean Code".

So, instead of building an interface of discrete operations like:

GetClientByName(string name);
AddOrder(PartNumber p, ContactInformation i);
SendOrder(Order o);

Do something like:

PrepareNewOrderForApproval(PartNumber p, string clientName);

Once you've done this, you also might refactor into separate objects.

Protist answered 6/7, 2015 at 12:24 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.