«Не используйте абстрактный базовый класс в дизайне; но в моделировании / анализе »

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

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

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

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


person LCJ    schedule 27.02.2012    source источник
comment
Если это домашнее задание, отметьте его как таковое.   -  person Henk Holterman    schedule 28.02.2012
comment
@HenkHolterman Это не домашнее задание. Меня выделяют в новый проект, использующий SOA. Хотя я раньше использовал WCF, он не использовал SOA. Я пытаюсь изучить концепции SOA.   -  person LCJ    schedule 28.02.2012


Ответы (2)


Похоже, вы пытаетесь использовать 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 вокруг конкретных операций было, безусловно, наиболее успешным как с точки зрения сервисов, так и с точки зрения потребительских реализаций. Он позволяет операциям развиваться независимо и очень четко определяет, что ожидается от службы и что будет возвращено клиенту.

person jageall    schedule 28.02.2012
comment
Я согласен с концепцией раскрытия DTO (объектов передачи данных), а не вашей внутренней модели предметной области. Это означает, что DTO может иметь дело с ответом, который вы отправляете через службу, а объект домена связан с представлением бизнес-объекта. - person Fenton; 28.02.2012
comment
@Sohnee Спасибо. Предположим, у меня есть дизайн на основе OOAD, нужно ли нам преобразовать его в DTO (как предложено выше) с использованием отдельного слоя перед использованием в сервисе? Это хорошая практика? Есть ли у нас какие-нибудь хорошие статьи, объясняющие ЗА и ПРОТИВ этого подхода? - person LCJ; 28.02.2012
comment
@jageall. Спасибо за четкое объяснение. Три вопроса: 1) Создает ли AutoMapper определенный для операции DTO или DTO из домена без поведения? 2) Какие инструменты вы используете для подхода DTO, специфичного для операции? 3) Есть ли у нас статья / учебник, объясняющий использование общего репозитория. Загрузить ‹Account›? - person LCJ; 29.02.2012
comment
Я лично больше не использую automapper (AM), но он может делать и то, и другое. Он поощряет DTO из домена, потому что он копирует на основе имен полей. Если у вас есть проверка, что это карты, вы должны явно указать ему, что вы не хотите отображать, что может быть больше работы, чем отображение вручную (поэтому я больше не использую его). Я считаю, что такой стиль интерфейса снижает входящее отображение до уровня, который я могу сделать вручную. Для исходящих DTO вы можете найти AM полезными, но я все еще делаю их вручную, поскольку они часто очень плоские по отношению к домену, чтобы улучшить интерфейс. Для репозитория: goo.gl/0fGXX - person jageall; 29.02.2012

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

  • Большинство систем SOA используют для связи веб-службы. Веб-службы предоставляют свой интерфейс через 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 во время сериализации на сервере.

person Aliostad    schedule 28.02.2012
comment
Спасибо. Не могли бы вы подробнее рассказать о примере? Что вы имеете в виду под изменениями CreationDate, взаимностью не будет. Означает ли это, что я изменил логику в базовом классе? - person LCJ; 28.02.2012
comment
@Lijo означает, что когда на сервере я изменяю CreationDate, значение CreationDateUniversal также изменяется, поскольку для вычисления значения используется защищенное поле. Но в клиенте эти значения представлены как простое свойство auto, поэтому, если вы измените CreationDate, то CreationDateUniversal оно не изменится. - person Aliostad; 28.02.2012