2012-02-27 8 views
9

Jestem początkującym w SOA, chociaż mam pewne doświadczenie w OOAD."Nie używaj klasy Abstract Base w projekcie; ale w Modelowaniu/Analizie "

Jedną z wytycznych dla projektu SOA jest "Użyj klas abstrakcyjnych tylko do modelowania. Pomiń je z Design ". Wykorzystanie abstrakcji może być pomocne w modelowaniu (fazie analizy).

Podczas fazy analizy wymyśliłem podstawową klasę BankAccount. Wydzielone z niej klasy specjalistyczne to "Stały Konto" i "Konto oszczędnościowe". Potrzebuję utworzyć usługę, która zwróci wszystkie konta (listę kont) dla użytkownika. Jaka powinna być struktura usług, aby spełnić wymóg?

Uwaga: Byłoby wspaniale, gdyby można było przedstawić kod demonstracyjny za pomocą WCF.

+0

Jeśli to zadanie domowe, oznacz to jako takie. –

+0

@HenkHolterman To nie jest praca domowa. Zostałem przydzielony do nowego projektu, który wykorzystuje SOA. Chociaż wcześniej korzystałem z WCF, to nie korzystałem z architektury SOA. Próbuję poznać koncepcje SOA. – Lijo

Odpowiedz

8

Wygląda na to, że próbujesz użyć architektury SOA, aby uzyskać zdalny dostęp do modelu obiektu. Lepiej byłoby przyjrzeć się interakcjom i możliwościom, które ma ujawnić Twoja usługa, i unikać ujawniania szczegółów dotyczących dziedziczenia realizacji usług.

Więc w tym przypadku, gdzie trzeba listę kont użytkowników twój interfejs będzie wyglądać następująco

[ServiceContract] 
interface ISomeService 
{ 
    [OperationContract] 
    Collection<AccountSummary> ListAccountsForUser(
     User user /*This information could be out of band in a claim*/); 
} 

[DataContract] 
class AccountSummary 
{ 
    [DataMember] 
    public string AccountNumber {get;set;} 
    [DataMember] 
    public string AccountType {get;set;} 
    //Other account summary information 
} 

jeśli zdecydujesz się pójść w dół trasą dziedziczenia, można użyć KnownType attribute, ale należy pamiętać, że spowoduje to dodanie pewnych informacji o typie do wiadomości przesyłanej przez kabel, co może w niektórych przypadkach ograniczyć interoperacyjność.

Aktualizacja:

byłem nieco ograniczony do czasu wcześniejszego, kiedy odpowiedział, więc będę próbować rozwinąć dlaczego wolę ten styl.

Nie poleciłbym ujawnienia Twojej OOAD przez DTO w oddzielnej warstwie, co zazwyczaj prowadzi do nadęty interfejs, gdzie przekazuje się wiele danych, które nie są używane i religijnie mapować go do iz tego, co jest zasadniczo kopią modelu domeny z usuniętą logiką, a ja po prostu nie widzę wartości. Zwykle tworzę warstwę usług wokół operacji, które ona eksponuje i używam DTO do definiowania interakcji usług.

Używanie DTO opartych na narażonych operacjach, a nie w modelu domeny, pomaga utrzymać enkapsulację usługi i zmniejsza sprzężenie z modelem domeny. Nie ujawniając mojego modelu domeny, nie muszę robić żadnych kompromisów w zakresie widoczności pola lub dziedziczenia w celu serializacji.

np jakbym narażając metodę przenoszenia z jednego konta na drugie interfejs usługi będzie wyglądać mniej więcej tak:

[ServiceContract] 
interface ISomeService 
{ 
    [OperationContract] 
    TransferResult Transfer(TransferRequest request); 
} 

[DataContract] 
class TransferRequest 
{ 
    [DataMember] 
    public string FromAccountNumber {get;set;} 
    [DataMember] 
    public string ToAccountNumber {get;set;} 
    [DataMember] 
    public Money Amount {get;set;} 
} 

class SomeService : ISomeService 
{ 
    TransferResult Transfer(TransferRequest request) 
    { 
     //Check parameters...omitted for clarity 
     var from = repository.Load<Account>(request.FromAccountNumber); 
     //Assert that the caller is authorised to request transfer on this account 
     var to = repository.Load<Account>(request.ToAccountNumber); 
     from.Transfer(to, request.Amount); 
     //Build an appropriate response (or fault) 
    } 
} 

teraz z tego interfejsu to do conusmer bardzo jasne, co wymagane dane do zadzwoń do tej operacji. Gdybym to realizowane jako

[ServiceContract] 
interface ISomeService 
{ 
    [OperationContract] 
    TransferResult Transfer(AccountDto from, AccountDto to, MoneyDto dto); 
} 

i AccountDto jest kopią pól w koncie, jako konsumenta, które pola należy wypełnić ja? Wszyscy? Jeśli dodano nową właściwość do obsługi nowej operacji, wszyscy użytkownicy wszystkich operacji mogą teraz zobaczyć tę właściwość. WCF pozwala mi oznaczyć tę właściwość jako nieobowiązkową, tak aby nie złamać wszystkich moich innych klientów, ale jeśli jest ona obowiązkowa dla nowej operacji, klient dowie się dopiero, gdy wywoła operację.

Co gorsza, jako realizator usług, co się stanie, jeśli zapewnią mi bieżące saldo? powinienem mu ufać?

Ogólna zasada polega na zapytaniu, kto jest właścicielem danych, klienta lub usługi? Jeśli klient jest jej właścicielem, może przekazać go do usługi i po wykonaniu podstawowych kontroli usługa może z niego skorzystać. Jeśli usługa jest jego właścicielem, klient powinien przekazać tylko tyle informacji, aby usługa mogła pobrać to, czego potrzebuje. Dzięki temu usługa może zachować spójność danych, które posiada.

W tym przykładzie usługa jest właścicielem informacji o koncie, a kluczem do zlokalizowania jest numer konta. Chociaż usługa może potwierdzić kwotę (jest dodatnia, obsługiwana waluta itp.), Jest własnością klienta i dlatego oczekujemy, że wszystkie pola w DTO zostaną wypełnione.

Podsumowując, widziałem to wszystko na trzy sposoby, ale projektowanie DTO wokół konkretnych operacji było zdecydowanie najbardziej udane, zarówno z wdrożenia usługowego, jak i konsumenckiego. Pozwala to na niezależną ewolucję operacji i bardzo jasno określa, czego oczekuje się od usługi i co zostanie zwrócone klientowi.

+1

Zgadzam się z koncepcją ujawniania DTO (obiektów przenoszenia danych) zamiast wewnętrznego modelu domeny. Oznacza to, że DTO może dotyczyć odpowiedzi wysyłanej za pośrednictwem usługi, a obiekt domeny dotyczy reprezentowania podmiotu gospodarczego. – Fenton

+0

@Sohnee Dzięki. Załóżmy, że mam projekt oparty na OOAD, czy musimy przekonwertować go na DTO (jak zasugerowano powyżej) przy użyciu oddzielnej warstwy przed zużyciem w serwisie? Czy to dobra praktyka? Czy mamy jakieś dobre artykuły wyjaśniające PROS i CONS tego podejścia? – Lijo

+0

@jageall. Dzięki za jasne wyjaśnienie. Trzy pytania - 1) Czy AutoMapper produkuje "DETo specyficzne dla danej operacji" lub "DTO z domeny bez zachowania"? 2) Jakie są narzędzia używane w podejściu "specyficzne dla danej operacji DTO"? 3) Czy mamy jakiś artykuł/samouczek, który wyjaśnia użycie generycznego repozytorium.Load ? – Lijo

1

Myślę, że najlepszą praktyką jest, aby nie używać dziedziczenia w danych-zamówień:

[DataContract] 
class SavingsAccount 
{ 
    public string AccountNr { ... } 
    .... 
} 

[DataContract] 
class FixedAccount 
{ 
    public string AccountNr { ... } 
    .... 
} 

Który działa WEL dla większości scenariuszy, ale nie na uzyskanie listy kont mieszanych.
Jeśli tak jest naprawdę niezbędne, należy rozważyć:

[DataContract] 
class AccountDTO 
{ 
    public string AccountType { ... } 
    public string AccountNr { ... } 
    .... 
} 

która jest zasadniczo użytkownika @ jageall odpowiedź.

+0

Dzięki. Czy uważasz, że usługa, która zwraca wynik MIESZANY, jest pomysłem NIE TAK DOBRE? Jaka jest standardowa praktyka tutaj? – Lijo

1

pójdę dość dużo z tego, co mówili inni tutaj, ale prawdopodobnie trzeba dodać te:

  • Większość systemów SOA korzystać z usług sieciowych do komunikacji. Usługi WWW udostępniają swój interfejs za pośrednictwem WSDL. WSDL nie ma pojęcia o dziedziczeniu.
  • Wszystko zachowanie w swoim DTOs będzie stracił kiedy krzyż drut
  • Wszystkie private/protected pola zostaną utracone, gdy krzyż drut

wyobrazić to scenariusz (sprawa głupie ale ilustracyjne):

public abstract class BankAccount 
{ 
    private DateTime _creationDate = DateTime.Now; 

    public DateTime CreationDate 
    { 
     get { return _creationDate; } 
     set { _creationDate = value; } 
    } 

    public virtual string CreationDateUniversal 
    { 
     get { return _creationDate.ToUniversalTime().ToString(); } 
    } 
} 

public class SavingAccount : BankAccount 
{ 
    public override string CreationDateUniversal 
    { 
     get 
     { 
      return base.CreationDateUniversal + " UTC"; 
     } 
    } 
} 

A teraz masz u sed "Dodaj numer serwisowy" lub "Dodaj numer referencyjny" na swoim kliencie (i zamiast ponowne użycie złożeń), aby uzyskać dostęp do konta oszczędnościowego.

SavingAccount account = serviceProxy.GetSavingAccountById(id); 
account.CreationDate = DateTime.Now; 
var creationDateUniversal = account.CreationDateUniversal; // out of sync!! 

Co się wydarzy to zmiany do CreationDate nie zostanie odwzajemniona do CreationDateUniversal ponieważ nie ma realizacja przekroczyła drut, tylko wartość CreationDateUniversalw momencie serializacji na serwerze.

+0

Dzięki. Czy mógłbyś jeszcze bardziej rozwinąć ten przykład? Co masz na myśli mówiąc, że "zmiany w CreationDate nie zostaną odwzajemnione". Czy to oznacza, że ​​zmieniłem logikę w klasie bazowej? – Lijo

+1

@Lijo oznacza, że ​​gdy na ** serwerze ** zmieniam 'CreationDate', wartość' CreationDateUniversal' również ulega zmianie, ponieważ używa pola chronionego do obliczenia wartości. Ale na ** kliencie ** te wartości pojawiły się jako prosta właściwość auto, więc jeśli zmienisz 'CreationDate', to' CreationDateUniversal' nie zostanie zmieniony. – Aliostad

Powiązane problemy