Рефакторинг объектов God в службах WCF

на 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 etc etc...

теперь 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);

    ....
}

число этих контрактов на эксплуатацию уже 2000 по всем 3 услугам и примерно 600 по каждому контракту на обслуживание. Это не просто нарушение лучших практик, это огромная боль, чтобы просто обновлять ссылки на услуги, как это занимает возрастов. И система растет с каждым днем и все больше и больше операций добавляются к этим услугам в каждой итерации.

например, если я просто разделю эти службы на тип сущности, я могу получить около 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.

4 ответов


проблема в том, что рефакторинг кода без изменения большой части, чтобы избежать возможных регрессий.

одним из решений, чтобы избежать большого бизнес-кода с тысячами строк, было бы разделить ваши интерфейсы / реализации на несколько частей, каждая из которых представляет данный бизнес-домен.

например, может быть записан следующим образом (используя наследование интерфейса, один интерфейс для каждого бизнеса домен):

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 операциями для каждого vs 20 сервисов с 100 операциями для каждого-это еще одна тема, но что точно, так это то, что рефакторинг потребует гораздо больше времени,и у вас все равно будет 2000 операций. Если вы рефакторинг всего приложения и уменьшить это число (для экземпляр, предоставляя услуги более "высокого уровня" (не всегда возможно)).


иметь слишком много контрактов на эксплуатацию не имеет смысла в данной службе, поскольку это приведет к проблемам обслуживания. Сказав, что если такие операции, как Add (), Delete, Update (), AddChildItem (), RemoveChildItem () и т. д. должны быть вместе, то не беспокойтесь о том, что контракт на операцию будет до 30-40. Потому что вещи, которые должны быть вместе, должны выходить из одного интерфейса (сплоченности).

но 600 операций в данном сервисном контракте действительно подавляющее число. Вы можете начать идентификацию операций: -

  1. которые должны быть вместе
  2. и это не требуется, чтобы быть вместе в данной службе.

на основе этого вы можете разделить операции на различные службы.

Если некоторые из методов не используются клиентом напрямую, то рассмотрите возможность разоблачения метода, основанного на логике бизнеса (Как также предложено "Matthias Bäßler").

говорите вы хотите предоставить функциональность MoneyTransfer. Тогда вы не обязаны выставлять

  • SendEmail ()
  • DebitAccount ()
  • CreditAccount () и т. д. В службе, используемой вашим веб-приложением.

таким образом, здесь вы можете предоставить только агрегатный сервис для вашего веб-приложения. В этом случае это может быть IAccountService с такими методами, как just

  1. TransferMoney ()
  2. метода getbalance(),

внутренне в вашей реализации вы можете создать другую службу, которая обеспечивает связанную операцию, такую как: -

  • SendEmail ()
  • DebitAccount ()
  • CreditAccount () и т. д. требуется для IAccountService. Платежные поручения() метод.

таким образом, количество методов в данной службе снизится до поддерживаемого уровня.


у меня нет опыта работы с WCF, но я думаю, что классы Бога и перегруженные интерфейсы, похоже, являются общей проблемой OOD.

при проектировании системы вы должны искать поведение (или бизнес-логику) вместо структур данных и операций. Не смотрите на то, как вы собираетесь его реализовать, а как клиент будет его использовать и как он его назовет. По моему опыту, наличие правильных имен для методов обычно дает много подсказок об объектах и их связь.

для меня откровением было дизайн кофеварки Mark IV, отрывок из" UML для Java-программистов " Роберта К. Мартина. Для значимых имен рекомендую его книгу "Чистый код".

Итак, вместо построения интерфейса дискретных операций, таких как:

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

сделать что-то вроде:

PrepareNewOrderForApproval(PartNumber p, string clientName);

после того как вы сделали это, вы могли бы рефакторинг на отдельные объекты.


ваша проблема - это не столько проблема объекта Бога, сколько проблема композиции службы. Объекты Бога проблематичны по разным причинам, чем огромные интерфейсы обслуживания на основе crud.

Я бы, конечно, согласился, что 3 контракта на обслуживание, которые вы описали, достигают точки, где они фактически неуправляемы. Боль, связанная с рефакторингом, будет непропорционально выше, чем если бы это был код в процессе, поэтому очень важно, чтобы вы взяли правильный подход, отсюда и ваш вопрос.

к сожалению, композиционность услуг в soa-такая огромная тема, что вряд ли вы получите здесь массово полезные ответы; хотя, очевидно, полезный опыт других вряд ли будет применяться к вашей ситуации.

Я написал об этом на SO до, поэтому для чего это стоит, я включу свои мысли:

Я считаю, что лучше всего, если операции службы могут существовать на уровне где они имеют деловое значение.

это означает, что если деловому человеку сообщили об операции название, они бы примерно поняли, что вызов этой операции будет сделайте, и могли бы догадаться, какие данные потребуется передать к нему.

для этого ваши операции должны выполняться полностью или частично какой-то бизнес-процесс.

например, следующие сигнатуры операций имеют бизнес смысл:

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

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

Guid BeginOnboardEmployee(string employeeName, DateTime employeeDateOfBirth);

Если вы используете этот Принципал, думая о составе службы, то преимущество в том, что вы редко будете отклоняться далеко от оптимального пути; вы знаете, что делает каждая операция, и вы знаете, когда операция нет нужно больше времени.

дополнительным преимуществом является то, что бизнес-процессы меняются справедливо редко вам не понадобится много сервисных контрактов.