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
.
Five steps to refactor a God objectIdentify the most relied upon God object components. Move static methods into a utility class. Group common methods and properties but maintain a loosely coupled system. Delete the God object from the system design.
God classes are the literal opposite of the single-responsibility principle, taking on too many responsibilities and being highly unfocused. This causes engineers to reinvent the wheel, duplicate code, and incur technical debt.
In object-oriented programming, a god object (sometimes also called an omniscient or all-knowing object) is an object that references a large number of distinct types, has too many unrelated or uncategorized methods, or some combination of both. The god object is an example of an anti-pattern and a code smell.
The God object is a part of the code smell group and it is a kind of object that knows too much or does too much. That means a huge class in terms of number of lines of code. It is very famous in bad programming because it creates tight coupling and increases the challenges in the code maintainability.
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)).
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:-
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
So here you can expose just an aggregate service to your web application. In this case it may be IAccountService with methods like just
Internally in your implementation you can create other service which provides related operation like:-
This way, the number of methods in a given service will come down to a maintainable level.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With