Какой вариант, кроме BLL, создающего экземпляр DAL, позволяет проводить модульное тестирование в n-уровневом решении, не подвергая DAL пользовательскому интерфейсу или BLL DAL?
У меня есть многоуровневое решение следующим образом:
- UI (пользовательский интерфейс)
- BLL (уровень бизнес-логики)
- DAL (Уровень доступа к данным)
- SharedEntities (проект VS с только POCO объекта)
Я бы хотел, чтобы в BLL была служба GetProductList(), которая реализована на моем уровне DAL. Я думал об определении интерфейса в реализации BLL и DAL следующим образом:
Вариант А:
// Interface defined in BLL
public interface IDataServices
{
List<Product> GetProductList();
}
// Interface implemented in DAL
public class DataServices : IDataServices
{
Public List<Product> GetProductList()
{
return //do some database work here and return List<Product>;
}
}
Если я хочу, чтобы это было реализовано в DAL, то мне нужно было бы, чтобы проект DAL ссылался на проект BLL, чтобы увидеть определение интерфейса IDataServices. Или я мог бы скопировать определение интерфейса в DAL, но затем я получу дублирующий код для поддержки (то же самое определение интерфейса в BLL и DAL).
Вариант Б: Другой способ сделать это - забыть идею интерфейса и просто выполнить следующий конкретный класс и вызов метода в BLL, который может использовать пользовательский интерфейс:
// Concrete class defined in the BLL
public class DataServices
{
Public List<Product> GetProductList()
{
DAL aDAL = new DAL();
Return (aDAL.GetProductList());
}
}
Это достаточно просто, но тогда BLL видит DAL и ссылается на него, но действительно ли это плохо здесь? Пока BLL не использует объекты базы данных (т.е. источники данных, строки подключения и т. Д.) Для удовлетворения запроса, а DAL соответствует именам служб, которые я определяю в классе BLL DataServices, не достаточно? Все разговоры, которые я слышу о замене в другом механизме базы данных, все еще можно сделать, просто убедившись, что следующий DAL предоставляет те же сервисы, которые BLL идентифицирует в классе DataServices, такие как GetProductList(). В этой настройке пользовательский интерфейс до сих пор ничего не знает о DAL, а DAL ничего не знает о BLL. Если бы я придерживался идеи использования внедрения зависимостей, чтобы избежать создания экземпляра DAL в BLL, это означало бы создание экземпляра в пользовательском интерфейсе для передачи в BLL. Я не хотел бы делать это, что бы дать UI доступ к методам DAL.
Вариант C: я кратко посмотрел на контейнер Unity, но этот инструмент предложил зарегистрировать все интерфейсы и конкретный класс заранее в точке входа, которая была бы пользовательским интерфейсом, который, в свою очередь, в конечном итоге дал видимость пользовательского интерфейса как для BLL, так и для DAL, что казалось еще хуже. Я видел ссылку на использование MEF с Unity, чтобы обойти проблему точки входа, видя все слои, но также видел, что если вы сделаете это, вы не сможете действительно протестировать такую конфигурацию. Похоже, много работы и сложности по сравнению с вариантом B.
Вариант D: И еще один вариант, о котором я подумал, - создать фасадный слой (другой проект VS) между BLL и DAL. Это, кажется, не имеет большого смысла, если бы я не закончил с большим количеством методов DAL, которые не имели никакого отношения к BLL, поэтому их пришлось бы скрывать; позволяя фасаду DAL показывать только то, что нужно BLL. Если бы я поменялся местами в другой базе данных, мне все равно пришлось бы создавать методы, которые требовались для фасада, исходя из потребностей BLL, как я упоминал в варианте B.
Исходя из всего этого, я думаю о выборе варианта B, но мне хотелось бы, чтобы сообщество внесло свой вклад. Что еще я могу сделать, что соответствует следующему:
- 1) не позволяет UI видеть DAL
- 2) Не позволяет DAL видеть BLL
- 3) Решение все еще позволяет всем слоям быть проверенными модулем
2 ответа
IDataServices
должны быть определены в DAL. but then the BLL sees the DAL and has a reference to it
это естественный способ сделать это. Проект может ссылаться на слой под ним. Если вы не разрешаете ссылаться вниз, ссылок вообще не может быть.
Обратите внимание, что ссылка Reflection по-прежнему является ссылкой, потому что вы не можете изменить слой ниже, не изменив слой выше. Зависимости не являются концепцией только во время компиляции. Вариант Б не добавляет ничего хорошего. Он удаляет зависимость времени компиляции, но не удаляет зависимость времени выполнения.
Смысл удаления зависимости от A до B состоит в том, что вы можете изменить B без изменения A. В этом весь смысл. Зависимости времени выполнения все еще учитываются.
Что касается C: вы можете сделать так, чтобы пользовательский интерфейс попросил BLL зарегистрировать свои зависимости. Затем BLL может попросить DAL зарегистрироваться. Таким образом, пользовательский интерфейс защищен от DAL.
Д.: Я не знаю, что это могло бы сделать.
Ваши ограничения могут быть легко удовлетворены, если определить DAL IDataServices
,
Ваш вариант A (интерфейс в BLL, реализация в DAL) + и контейнер IoC - лучший подход.
При разработке сложного программного решения вы должны разделить его на более мелкие части.
Некоторые из этих частей будут иметь решающее значение для решения. Они будут причиной, по которой вы разрабатываете программное обеспечение, а не просто покупаете существующее решение. Вы должны сосредоточиться на них. Эти части будут сложными и сложными для реализации. Вы ничего не можете с этим поделать. Они должны быть реализованы как можно лучше.
Будут и простые кусочки. Функциональные возможности, которые легко реализовать, возможно, даже можно купить. Постарайтесь сделать это с минимальными усилиями. Они необходимы, но это не то, для чего вы были наняты.
Сосредоточиться в первую очередь на сложных деталях. Бизнес-логика там будет сложной. Бизнес-логика не будет зависеть от выбранного вами решения для хранения данных, поэтому не делайте его зависимым от проекта DAL в вашей системе. Определите интерфейсы для хранилищ ваших сущностей (агрегатов, если вы следуете DDD) на уровне бизнес-логики.
Вы должны быть в состоянии тщательно проверить бизнес-логику. Вы не можете сделать это, если BLL зависит от DAL.
Пользовательский интерфейс должен быть полностью отделен от бизнес-логики. Пользовательский интерфейс - это просто набор вариантов использования, которые может выполнять пользователь. Как таковой, он должен представлять ограниченную информацию пользователю и принимать только ограниченную информацию от пользователя.
Чтобы отделить пользовательский интерфейс от BLL, используйте отдельные классы ViewModel и Command для отображения и принятия информации от пользователя. Используйте дополнительный прикладной уровень для управления BLL в каждом случае использования.
Такой подход хорошо известен как шестиугольная архитектура, луковая архитектура или чистая архитектура. На него также ссылаются в книгах, посвященных доменному дизайну.
Конечно, вам понадобится место, где вы соберете все эти зависимости. Это место является корнем композиции и должно быть как можно ближе к точке входа приложения. Если вы не хотите ссылаться на все слои в проекте пользовательского интерфейса, переместите корень композиции в другой проект.