2015-06-25 26 views
9

Nel nostro sistema ci siamo imbattuti in god object. Il sistema è costituito da public service esposto ai nostri clienti, middle office service e back office service.Refactoring oggetti di Dio nei servizi WCF

Il flusso è la seguente: utente registra qualche operazione in public service, poi direttore dal middle office service controlli la transazione e approva o rifiuta la transazione e infine direttore dal back office service finalizza o rifiuta la transazione.

I'am usando la parola transaction, ma in realtà questi sono i diversi tipi di operazioni come CRUD on entity1, CRUD on entiny2 ... non solo CRUD operazioni, ma molte altre operazioni come approve/send/decline entity1, make entity1 parent/child of entity2 etc etc ...

Ora I contratti di servizio WCF vengono semplicemente separati in base a quelle parti del sistema. Quindi abbiamo 3 contratti di servizio:

PublicService.cs 
MiddleOfficeService.cs 
BackOfficeService.cs 

e quantità enorme di contratti di gestione in ogni:

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

    [OperationContract] 
    void DeleteEntity1(Entity1 item); 

    .... 

    [OperationContract] 
    void SendEntity2(Entity2 item); 

    .... 
} 

Il numero di tali contratti di gestione sono già 2000 in tutti i 3 servizi e circa 600 per ogni contratto di servizio . Non è solo rompere le migliori pratiche, è un enorme problema aggiornare semplicemente i riferimenti di servizio in quanto richiede anni. E il sistema cresce ogni giorno e sempre più operazioni vengono aggiunte a tali servizi in ogni iterazione.

E ora stiamo affrontando il dilemma come possiamo dividere quei servizi divini in parti logiche. Si dice che un servizio non dovrebbe contenere più di 12 ~ 20 operazioni. Altri dicono cose diverse. Mi rendo conto che non esiste una regola d'oro, ma vorrei solo sentire alcune raccomandazioni al riguardo.

Ad esempio, se suddivido questi servizi per tipo di entità, è possibile ottenere circa 50 endpoint di servizio e 50 riferimenti di servizio nei progetti. Che cos'è la manutenibilità in questo caso?

Un'altra cosa da considerare. Supponiamo che io scelga l'approccio per dividere quei servizi per entità. Per esempio:

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

Ora quello che succede è che dovrei aggiungere riferimento a questo servizio sia in public client e back office client. Ma back office richiede solo operazioni FinalizeEntity1 e DeclineEntity1. Quindi, ecco una violazione classica di Interface segregation principle in SOLID. Quindi devo dividere ulteriormente i tre servizi distinti come IEntity1FrontService, IEntity1MiddleService, IEntity1BackService.

+0

Quanto tempo ci vuole per rigenerare il proxy client? – ken2k

+0

@ ken2k, l'intero processo può durare 15 minuti. Per i primi 1-2-3 tentativi è solo timeout e quindi aggiornamenti. Ci vogliono 3-4 minuti per ottenere il timeout. A volte timeout 2 volte, a volte solo una volta. A volte 3 volte Ma questa non è la cosa principale. La priorità è il refactoring. Anche se ci vorrebbero 1 secondo per aggiornare il riferimento, decideremo il refactoring nonostante ciò. –

+0

Potrei fornire una soluzione con ancora solo 3 servizi, ma le 3 interfacce/implementazioni separate in più interfacce/implementazioni usando l'ereditarietà dell'interfaccia e le implementazioni parziali. Questo però non risolverebbe il tempo di rigenerazione del proxy client. – ken2k

risposta

5

La sfida qui è quella di refactoring del codice senza modificare grandi porzioni di esso per evitare potenziali regressioni.

Una soluzione per evitare codice aziendale di grandi dimensioni con migliaia di righe sarebbe quella di suddividere le interfacce/implementazioni in più parti, ciascuna delle quali rappresenta un determinato dominio aziendale.

Per esempio, l'interfaccia IPublicService potrebbe essere scritto come segue (utilizzando l'ereditarietà di interfaccia, un'interfaccia per ogni dominio aziendale):

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

Ora, per l'implementazione del servizio, si potrebbe dividerlo in più parti utilizzando classi parziali (una classe parziale per ogni dominio aziendale):

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

Per la configurazione del server, non v'è ancora solo un punto finale:

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

Lo stesso vale per il cliente: ancora riferimento un unico servizio:

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

Questo permette di ridefinisci il tuo server dividendo i tuoi enormi servizi in più parti logiche (ciascuna parte associata a un determinato dominio aziendale).

Ciò non cambia il fatto che ciascuno dei 3 servizi ha ancora 600 operazioni, quindi la generazione del proxy client richiederà ancora anni. Almeno il tuo codice sarebbe meglio organizzato sul lato server, e il refactoring sarebbe economico e non rischioso.

Qui non è presente un punto d'argento, ovvero una riorganizzazione del codice per una migliore leggibilità/manutenzione.

200 servizi con 10 operazioni per ogni vs 20 servizi con 100 operazioni per ciascuno è un altro argomento, ma ciò che è sicuro è che il refactoring richiederebbe più tempo, e si avrebbero comunque 2000 operazioni. A meno che non si ridistri l'intera applicazione e si riduca questo numero (ad esempio fornendo servizi più "di alto livello" (non sempre possibile)).

+0

Grazie per la risposta! Potete approfondire su questo: '200 servizi con 10 operazioni per ogni vs 20 servizi con 100 operazioni per ciascuno è un altro argomento'? In realtà non sono limitato nel tempo. Stiamo preparando iterazioni per questi tipi di refactoring. –

+0

Questo è abbastanza vicino al modo in cui pensiamo al refactoring. Sì, faremo alcune inharitance di interfaccia, ma invece di classi parziali che dividono solo la classe in parti creeremo classi separate per domini diversi. Ma mi piacerebbe ancora sentire qualche esperienza con un massimo di 50 riferimenti di servizio ÷) –

1

Il tuo problema non è tanto un problema con l'oggetto dio, quanto un problema di composizione del servizio. Gli oggetti di Dio sono problematici per ragioni diverse da quelle enormi, le interfacce di servizio basate su crude sono problematiche.

Sarei certamente d'accordo che i 3 contratti di servizio che hai descritto stanno raggiungendo il punto in cui sono effettivamente ingestibili. Il dolore associato al refactoring sarà sproporzionatamente più alto di quello che si sarebbe verificato se si trattasse di codice in-process, quindi è molto importante che tu prenda l'approccio corretto, da qui la tua domanda.

Sfortunatamente, la componibilità del servizio in soa è un argomento così enorme che è improbabile che riceviate risposte di grande utilità qui; anche se ovviamente utile, le esperienze degli altri saranno improbabili per la tua situazione.

ho scritto su questo su SO before, quindi per quel che vale io includo il mio pensiero:

Trovo che è meglio se possono esistere operazioni di servizio ad un livello in cui hanno significato di business .

Ciò significa che se un uomo d'affari è stato detto il nome dell'operazione , avrebbero capito più o meno quello che chiama questa operazione sarebbe fare, e potrebbe fare un'indovinare quali dati sarebbe necessario da passare ad esso.

Affinché questo accada, le operazioni devono soddisfare completamente o parzialmente il processo aziendale.

Ad esempio, le seguenti firme di funzionamento hanno un significato di business:

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

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

Guid BeginOnboardEmployee(string employeeName, DateTime employeeDateOfBirth); 

Se si utilizza questo principio quando si parla di composizione di servizi poi il vantaggio è che raramente allontanarsi molto dal percorso ottimale; sai cosa fa ogni operazione e sai quando un'operazione non è più necessaria.

Un ulteriore vantaggio è che i processi aziendali cambiano in modo equo raramente non è necessario modificare i contratti di servizio tanto.

+0

Grazie per aver dedicato del tempo a rispondere. Chiarirò un po ': tutti e tre i progetti sono progetti' silverlight'. E naturalmente tutte le operazioni esposte hanno un significato per il business. Mi piacerebbe sentire di altre esperienze e approcci di vita reale in situazioni come quella descritta nella domanda. Comunque grazie per la risposta. –

+0

@GiorgiNakeuri - capire che questo non è quello che stavi cercando, tuttavia non sono d'accordo con la tua affermazione che tutte le operazioni hanno un significato commerciale - cosa significa "AddEntity1" dal punto di vista del business? –

+0

questo è ovviamente a scopo dimostrativo. Ho ribattezzato quelli. In realtà ci sono nomi significativi: AddOrder, ApprooveOrder, SendOrder, LoadOrders, GetOrderByOrderNumber, DeclineAgreement, GetAgreementResource, AddAgreement, ecc .... –

1

Non ho esperienza con WCF, ma penso che le classi di Dio e le interfacce sovraccaricate sembrano essere un problema di OOD generale.

Quando si progetta un sistema, è necessario cercare il comportamento (o la logica aziendale) anziché le strutture e le operazioni dei dati. Non guardate come lo implementerete, ma come lo useranno il cliente e come lo chiamerebbe. Nella mia esperienza, avere i nomi giusti per i metodi di solito fornisce molti indizi sugli oggetti e sul loro accoppiamento.

Per me l'apertura dell'occhio era the design of the Mark IV coffee maker, un estratto da "UML per programmatori Java" di Robert C. Martin. Per i nomi significativi raccomando il suo libro "Codice pulito".

Così, invece di costruire un'interfaccia di operazioni discrete come:

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

fare qualcosa di simile:

PrepareNewOrderForApproval(PartNumber p, string clientName); 

Una volta fatto questo, si potrebbe anche refactoring in oggetti separati.

+0

Grazie per aver dedicato del tempo per rispondere. –

2

Avere troppi contratti operativi non ha senso in un dato servizio in quanto porterà a problemi di manutenzione. Detto questo, se operazioni come Add(), Delete, Update(), AddChildItem(), RemoveChildItem(), ecc. Dovrebbero essere insieme, non preoccupatevi del contratto operativo che arriva fino a 30-40 nel numero. Perché le cose che dovrebbero essere insieme dovrebbero uscire da un'unica interfaccia (coesione).

Ma 600 operazioni in un determinato contratto di assistenza sono un numero davvero schiacciante. È possibile iniziare ad individuare le operazioni di: -

  1. che sono necessari per stare insieme
  2. E che non sono tenuti a stare insieme in un determinato servizio.

In base a ciò è possibile suddividere le operazioni in diversi servizi.

Se alcuni dei metodi non vengono utilizzati direttamente dal client, considerare di esporre il metodo in base alla logica BUSSINESS (come suggerito anche da "Matthias Bäßler").

Dire che si desidera esporre la funzionalità di MoneyTransfer. Allora non sono tenuti a esporre

  • SendEmail()
  • DebitAccount()
  • CreditAccount(), ecc nel servizio utilizzato dall'applicazione web.

Quindi qui è possibile esporre solo un servizio aggregato per la tua applicazione web. In questo caso può essere IAccountService con metodi come solo

  1. TransferMoney()
  2. GetBalance(),

Internamente nell'implementazione è possibile creare un altro servizio che fornisce il funzionamento correlate come: -

  • SendEmail()
  • DebitAccount()
  • CreditAccount(), ecc. Richiesto per IAccountService. Metodo MoneyTransfer().

In questo modo, il numero di metodi in un determinato servizio arriverà a un livello gestibile.

+0

Grazie per il vostro tempo e la vostra risposta. –