«Не используйте абстрактный базовый класс в дизайне;но в моделировании / анализе » - PullRequest
9 голосов
/ 27 февраля 2012

Я новичок в SOA, хотя у меня есть некоторый опыт работы в OOAD.

Одним из руководящих принципов разработки SOA является «Используйте только абстрактные классы для моделирования. Опустить их из дизайна ». Использование абстракции может быть полезным при моделировании (фаза анализа).

На этапе анализа я разработал базовый класс BankAccount. Производные от него специализированные классы - «FixedAccount» и «SavingsAccount». Мне нужно создать сервис, который будет возвращать все учетные записи (список учетных записей) для пользователя. Какой должна быть структура услуг для удовлетворения требований?

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

Ответы [ 3 ]

8 голосов
/ 28 февраля 2012

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

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

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

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

Обновление :

Я немного ограничился временем, когда отвечал, поэтому постараюсь уточнить, почему я предпочитаю этот стиль.

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

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

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

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

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

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

и AccountDto - это копия полей в учетной записи, как потребитель, какие поля мне следует заполнить? Все они? Если новое свойство добавлено для поддержки новой операции, все пользователи всех операций теперь могут видеть это свойство. WCF позволяет мне пометить это свойство как необязательное, чтобы не прерывать работу всех остальных моих клиентов, но если оно является обязательным для новой операции, клиент узнает об этом только при вызове операции.

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

Общее правило здесь - спрашивать, кому принадлежат данные, клиенту или услуге? Если клиент владеет им, он может передать его службе, и после выполнения некоторых базовых проверок служба может использовать его. Если сервис принадлежит ему, клиент должен только передать достаточно информации для сервиса, чтобы получить то, что ему нужно. Это позволяет службе поддерживать согласованность данных, которыми она владеет.

В этом примере службе принадлежит информация об учетной записи, а ключом для ее поиска является номер учетной записи. Хотя служба может проверять сумму (является положительной, поддерживаемой валютой и т. Д.), Она принадлежит клиенту, и поэтому мы ожидаем, что все поля в DTO будут заполнены.

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

1 голос
/ 28 февраля 2012

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

  • Большинство систем SOA используют Web-сервисы для связи.Веб-сервисы предоставляют свой интерфейс через WSDL.WSDL не имеет никакого понимания наследования.
  • Все поведение в ваших DTO будет потеряно при пересечении провода
  • Все частные / защищенные поля будутпотерян, когда они пересекают провод

Представьте себе этот сценарий (случай глупый , но иллюстративный):

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

И теперь вы использовали Добавьте сервисную ссылку " или " Добавить веб-ссылку " на своем клиенте (и не повторное использование сборок) для доступа к учетной записи сохранения.

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

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

1 голос
/ 28 февраля 2012

Я думаю, что лучшая практика здесь - не использовать наследование в ваших контрактах на данные:

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

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

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

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

, что по сути является ответом @ jageall.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...