Разрешение анти-паттерна цепочки вызовов

Я начал замечать что-то вроде анти-паттерна в моей разработке ASP.NET. Это беспокоит меня, потому что я чувствую, что это правильно, чтобы поддерживать хороший дизайн, но в то же время он пахнет неправильно.

Проблема в следующем: у нас многоуровневое приложение, нижний уровень - это класс, обрабатывающий вызовы службы, которая предоставляет нам данные. Над этим находится слой классов, которые могут преобразовывать, манипулировать и проверять данные. Над этим находятся страницы ASP.NET.

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

public List<IData> GetData(int id, string filter, bool check)
{
    return DataService.GetData(id, filter, check);
}

Это не неправильно и не обязательно ужасно работать, но это создает странную зависимость от копирования / вставки. Я также работаю над основным сервисом, и он также много копирует этот паттер, и есть интерфейсы повсюду. Итак, что происходит, "Мне нужно добавить int someotherID в GetData"Поэтому я добавляю его к модели, вызывающей стороне службы, самой службе и интерфейсам. GetData фактически представляет несколько методов, которые используют одну и ту же сигнатуру, но возвращают разную информацию. Интерфейсы немного помогают с этим повторением, но оно все еще появляется здесь и там.

Есть ли название для этого анти-паттерна? Есть ли исправление или серьезное изменение архитектуры - единственный реальный путь? Похоже, мне нужно сгладить мою объектную модель, но иногда слой данных выполняет преобразования, поэтому он имеет ценность. Мне также нравится разделять код между "вызовами внешней службы" и "поставкой данных страницы".

7 ответов

Решение

Я бы предложил вам использовать шаблон объекта запроса для решения этой проблемы. По сути, ваш сервис может иметь такую ​​подпись:

IEnumerable<IData> GetData(IQuery<IData> query);

Внутри интерфейса IQuery у вас может быть метод, который принимает единицу работы в качестве входных данных, например контекст транзакции или что-то вроде ISession, если вы используете ORM, такой как NHibernate, и возвращает список объектов IData.

public interface IQuery<T> 
{
 IEnumerable<T> DoQuery(IUnitOfWork unitOfWork);
}

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

Похоже, вам нужен другой интерфейс, так что метод становится примерно таким:

public List<IData> GetData(IDataRequest request)

Важно определить, какая ответственность лежит на том или ином слое, и поместить такую ​​логику только в тот слой, к которому она принадлежит.

Абсолютно нормально просто пройти, если вам не нужно добавлять какую-либо логику в конкретный метод. В какой-то момент вам может понадобиться сделать это, и уровень абстракции окупится в этот момент.

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

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

IData GetData(IQuery<IData> query)

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

IQuery<IData> BindRequest(IHttpRequest request)

С помощью шаблона Automapping и Query Specification вы можете уменьшить дублирование до минимума.

Из того, что вы описали, это просто звучит так, как будто вы столкнулись с одним из "компромиссов" абстракции в вашем приложении.

Рассмотрим случай, когда эти "цепочки вызовов" больше не "пропускают" данные, а требуют некоторой трансформации. Это может быть не нужно сейчас, и, конечно, можно привести доводы в пользу YAGNI.

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

На самом деле, путь, который вы выбрали, является причиной того, что у вас есть (я не говорю, что это плохо).

Во-первых, позвольте мне сказать, что ваш подход вполне нормальный.

Теперь позвольте мне подумать о ваших слоях:

  • Ваш сервис - предоставляет довольно строгую модель доступа. Это означает, что он имеет несколько типов аргументов, использует их в некоторых специальных типах методов, которые снова возвращают некоторые особые типы результатов.
  • Ваш уровень доступа к услугам - также предоставляет такую ​​же модель. Таким образом, он принимает специальные виды аргументов для специальных видов методов, возвращая специальные виды результатов.
  • так далее...

Чтобы не путать, вот что я называю особой разновидностью:

public UserEntity GetUserByID(int userEntityID);

В этом примере вам нужно передать именно In t, при этом вызывая именно GetUserByID, и он вернет точно UserEntity объект.

Теперь другой подход:

Помни как SqlDataReader работает? не очень сильно напечатано, верно? На мой взгляд, здесь вы вызываете то, что вам не хватает не сильно типизированного слоя.

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

Example:

public Entity SelectByID(IEntityID id);
public Entity SelectAll();

Итак, если у вас есть что-то подобное вместо уровня доступа к сервису, вы можете вызывать его для любых аргументов, которые вы хотите.

Но это почти создание собственного ORM, поэтому я не думаю, что это лучший способ.

Я также использую этот шаблон. Однако я использовал его с целью отделения объектов моей доменной модели от объектов данных.

В моем случае вместо "прохождения" объекта, поступающего со слоя данных, как вы делаете в своем примере, я "сопоставляю" его с другим объектом, который живет в слое моего домена. Я использую AutoMapper, чтобы избавиться от боли ручного выполнения этого.

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

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

Вот описание auto-mapper, которое, по-моему, пытается достичь этот шаблон проектирования:

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

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

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

У вас может быть слишком много слоев, но я бы так не сказал, просто увидев это, больше не увидев ничего другого.

Другие вопросы по тегам