2015-06-25 39 views
9

Natknęliśmy się na god object w naszym systemie. System składa się z public service udostępnionego naszym klientom, middle office service i back office service.Refaktoryzacja obiektów Boga w usługach WCF

Przepływ jest następujący: użytkownik zarejestruje jakąś transakcję w public service, a następnie menedżera z middle office service kontroli transakcji i zatwierdza lub odmawia transakcji i wreszcie menedżera z back office service finalizuje transakcję lub maleje.

I'am używając słowa transaction, ale w rzeczywistości są to różne rodzaje działalności, takie jak CRUD on entity1, CRUD on entiny2 ... Nie tylko CRUD operacje, ale wiele innych operacji, jak approve/send/decline entity1, make entity1 parent/child of entity2 etc etc ...

teraz WCF Umowy serwisowe są po prostu rozdzielone zgodnie z tymi częściami systemu. Więc mamy 3 zamówień na usługi:

PublicService.cs 
MiddleOfficeService.cs 
BackOfficeService.cs 

i ogromną ilość umów eksploatacji w każdy:

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

    [OperationContract] 
    void DeleteEntity1(Entity1 item); 

    .... 

    [OperationContract] 
    void SendEntity2(Entity2 item); 

    .... 
} 

liczba tych umów operacyjnych są już 2000 we wszystkich 3 usług i około 600 za każdego zamówienia na usługi . Nie jest to po prostu przełamanie najlepszych praktyk, to ogromny problem, aby zaktualizować referencje usług, ponieważ trwa to całe wieki. System rośnie każdego dnia i coraz więcej operacji jest dodawanych do tych usług w każdej iteracji.

A teraz stajemy przed dylematem, w jaki sposób możemy podzielić te bogate usługi na logiczne części. Mówi się, że usługa nie powinna zawierać więcej niż 12 ~ 20 operacji. Inni mówią różne rzeczy. Zdaję sobie sprawę, że nie ma złotej zasady, ale chciałbym usłyszeć kilka zaleceń na ten temat.

Na przykład, jeśli po prostu podzielę te usługi na typ jednostki, mogę uzyskać około 50 punktów końcowych usług i 50 odwołań do usług w projektach. Co w tym przypadku dotyczy konserwacji?

Jeszcze jedna rzecz do rozważenia. Załóżmy, że wybieram podejście do podziału tych usług na jednostkę. Na przykład:

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

Teraz to, co się dzieje, że powinienem dodać odniesienie do tej usługi, zarówno w public client i back office client. Ale back office wymaga tylko operacji FinalizeEntity1 i DeclineEntity1. Oto klasyczne naruszenie Interface segregation principle w SOLID. Muszę podzielić to, co dalej, na 3 różne usługi, takie jak IEntity1FrontService, IEntity1MiddleService, IEntity1BackService.

+0

Ile czasu zajmuje ponowne wygenerowanie proxy klienta? – ken2k

+0

@ ken2k, cały proces może trwać 15 minut. W przypadku pierwszych prób 1-2-3 po prostu przestaje działać, a następnie aktualizuje się. Aby uzyskać limit czasu, potrzeba 3-4 minut. Czasami przekracza 2 razy, czasem tylko raz. Czasami 3 razy. Ale to nie jest najważniejsze. Priorytet to refaktoryzacja. Nawet jeśli zajmie to 1 sekundę aktualizacji odniesienia, mimo tego zdecydujemy się na refaktor. –

+0

Mogę dostarczyć rozwiązanie z wciąż tylko 3 usługami, ale 3 interfejsy/implementacje podzielone na wiele interfejsów/implementacji z wykorzystaniem dziedziczenia interfejsu i częściowych implementacji. To jednak nie naprawiłoby czasu regeneracji proxy klienta. – ken2k

Odpowiedz

5

Wyzwaniem jest tu zreorganizowanie kodu bez zmieniania dużych jego części, aby uniknąć potencjalnych regresji.

Jednym ze sposobów uniknięcia dużego kodu biznesowego z tysiącami linii byłoby podzielenie interfejsów/implementacji na wiele części, z których każda reprezentuje daną domenę biznesową.

Na przykład, twój interfejs IPublicService można zapisać następująco (przy użyciu dziedziczenie interfejsu, jeden interfejs dla każdej domeny biznesowej):

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

teraz za realizację usług, można podzielić ją na kilka części za pomocą klas częściowe (jeden częściowej klasy dla każdej dziedziny biznesu):

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

Dla konfiguracji serwera, nadal istnieje tylko jeden punkt końcowy:

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

samo dla klienta: nadal odniesienie jedna usługa:

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

Pozwala to na pomnóż swoją stronę serwera, dzieląc swoje ogromne usługi na wiele części logicznych (każda część związana z daną domeną biznesową).

Nie zmienia to faktu, że każda z 3 usług wciąż ma 600 operacji, więc generowanie proxy klienta nadal będzie trwać wieki. Przynajmniej twój kod byłby lepiej zorganizowany po stronie serwera, a refaktoryzacja byłaby tania i mało ryzykowna.

Tu nie ma srebrnej kuli, to tylko reorganizacja kodu dla lepszej czytelności/konserwacji.

200 usług z 10 operacjami dla każdej usługi vs 20 z 100 operacjami dla każdego jest innym tematem, ale na pewno jest to, że refaktoryzacja wymagałaby znacznie więcej czasu, , a ty nadal miałbyś 2000 operacji. Chyba, że ​​zreorganizujesz całą aplikację i zredukujesz tę liczbę (na przykład, zapewniając usługi, które są bardziej "na wysokim poziomie" (nie zawsze jest to możliwe)).

+0

Dziękuję za odpowiedź! Czy możesz rozwinąć ten temat: "200 usług z 10 operacjami dla każdej usługi kontra 20 i 100 operacji dla każdego jest innym tematem"? Właściwie nie jestem ograniczony w czasie. Przygotowujemy iteracje dla tego typu refaktoryzacji. –

+0

Jest to bardzo zbliżone do sposobu, w jaki myślimy, aby to zmienić. Tak, zrobimy kilka interfejsów, ale zamiast częściowych klas, które dzielą klasę na części, stworzymy oddzielne klasy dla różnych domen. Ale nadal chciałbym usłyszeć o pewnym doświadczeniu z maksymalnie 50 referencjami serwisowymi ÷) –

1

Twój problem to nie tyle problem z bogiem, co problem z kompozycją usług. Przedmioty Boże są problematyczne z różnych powodów niż ogromne, a interfejsy serwisowe oparte na crudach są problematyczne.

Z pewnością zgodziłbym się, że 3 umowy serwisowe, które opisałeś, osiągają punkt, w którym są faktycznie niemożliwe do zarządzania. Ból związany z refaktoryzacją będzie nieproporcjonalnie większy, niż gdyby był to kod w procesie, więc bardzo ważne jest, abyś podjął właściwe podejście, stąd twoje pytanie.

Niestety, złożoność usług w soi jest tak ogromnym tematem, że jest mało prawdopodobne, że otrzymasz tutaj bardzo przydatne odpowiedzi; choć oczywiście użyteczne, doświadczenia innych będą mało prawdopodobne w twojej sytuacji.

Pisałem o tym na SO before, więc na co warto będę zawierać myśli moje

Uważam, że najlepiej, jeśli operacje serwisowe mogą istnieć na poziomie gdzie mają biznesowego znaczenia .

Oznacza to, że jeśli dana osoba biznes kazano nazwę operacja , to oni zrozumieć mniej więcej, co wywołanie tej operacji będzie zrobić, a może uczynić przypuszczenie na jakie dane wymagałoby to być przekazywane do niego.

Aby tak się stało, operacje użytkownika powinny w całości lub częściowo wykonać część procesu biznesowego.

Na przykład, następujące podpisy operacja ma sens biznesowy:

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

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

Guid BeginOnboardEmployee(string employeeName, DateTime employeeDateOfBirth); 

Jeśli używasz tego kapitału, kiedy myśli o składzie serwisowego następnie korzyścią jest to, że rzadko odchodzą daleko od optymalnej ścieżki; wiesz, co robi każda operacja i wiesz, kiedy operacja nie jest już potrzebna.

Dodatkową korzyścią jest to, że ponieważ procesy biznesowe zmieniają się dość często , rzadko nie będziecie musieli zmieniać kontraktów serwisowych tak często.

+0

Dziękuję za poświęcenie czasu na odpowiedź. Wyjaśnię nieco: wszystkie trzy projekty to projekty "silverlight". Oczywiście wszystkie ujawnione operacje mają znaczenie dla biznesu. Chciałbym usłyszeć o innych prawdziwych życiowych doświadczeniach i podejściach w sytuacjach takich, jak te opisane w pytaniu. W każdym razie dzięki za odpowiedź. –

+0

@GiorgiNakeuri - rozumiem, że to nie jest to, czego szukałeś, jednak nie zgadzam się z twoim stwierdzeniem, że wszystkie operacje mają znaczenie biznesowe - co oznacza "AddEntity1" z perspektywy biznesowej? –

+0

jest to oczywiście do celów demonstracyjnych. Zmieniłem ich nazwę. W rzeczywistości istnieją znaczące nazwy: AddOrder, ApprooveOrder, SendOrder, LoadOrders, GetOrderByOrderNumber, DeclineAgreement, GetAgreementResource, AddAgreement, etc .... –

1

Nie mam doświadczenia z WCF, ale uważam, że klasy Boga i przeciążone interfejsy wydają się być ogólnym problemem OOD.

Podczas projektowania systemu należy szukać zachowań (lub logiki biznesowej) zamiast struktur danych i operacji. Nie patrz na to, w jaki sposób zamierzasz go wdrożyć, ale w jaki sposób klient go użyje i jak go nazwać. Z mojego doświadczenia wynika, że ​​posiadanie właściwych nazw metod zwykle dostarcza wielu wskazówek dotyczących obiektów i ich sprzężenia.

Dla mnie otwieraczem do oczu był the design of the Mark IV coffee maker, fragment z "UML for Java Programmers" Roberta C. Martina. W przypadku znaczących nazw polecam jego książkę "Czysty kod".

Tak więc, zamiast budowania interfejsu dyskretnych operacji jak:

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

zrobić coś takiego:

PrepareNewOrderForApproval(PartNumber p, string clientName); 

Gdy już to zrobisz, ty też może byłaby w oddzielnych obiektach.

+0

Dziękuję za poświęcenie czasu na odpowiedź. –

2

Posiadanie zbyt wielu umów na operację nie ma sensu w danej usłudze, ponieważ prowadzi do problemów związanych z konserwacją. Powiedziawszy, że jeśli operacje takie jak Add(), Delete, Update(), AddChildItem(), RemoveChildItem() itp. Powinny być razem, to nie martw się, że kontrakt operacyjny będzie wynosił nawet 30-40. Ponieważ rzeczy, które powinny być razem, powinny pochodzić z jednego interfejsu (spójność).

Ale 600 operacji w danej umowie serwisowej jest naprawdę przytłaczającą liczbą. Można rozpocząć identyfikację operacje: -

  1. , które są wymagane, aby być razem
  2. I że nie muszą być ze sobą w danej usługi.

Na tej podstawie można podzielić operacje na różne usługi.

Jeśli niektóre metody nie są używane bezpośrednio przez klienta, należy rozważyć ekspozycję metody na podstawie logiki BUSSINESS (zgodnie z sugestią "Matthias Bäßler").

Załóżmy, że chcesz ujawnić funkcjonalność MoneyTransfer. Wtedy nie trzeba narażać

  • SendEmail()
  • DebitAccount()
  • CreditAccount(), itp w służbie używane przez aplikację internetową.

W tym miejscu można udostępnić jedynie aplikację zbiorczą do swojej aplikacji internetowej. W tym przypadku może to być IAccountService metodami jak tylko

  1. TransferMoney()
  2. GetBalance()

Wewnętrznie w implementacji można utworzyć inną usługę, która zawiera odnośne działania jak: -

  • SendEmail()
  • DebitAccount()
  • CreditAccount(), itp. Wymagane dla usługi IAccountService. Metoda MoneyTransfer().

W ten sposób liczba metod w danej usłudze spadnie do poziomu możliwego do utrzymania.

+0

Dziękuję za poświęcony czas i odpowiedź. –