Search code examples
wcfsoaservicecontract

Refactoring God objects in WCF services


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.


Solution

  • 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)).