Размещение моделей представления /DTO в луковой архитектуре
В настоящее время я занимаюсь рефакторингом проекта ASP.NET MVC, чтобы использовать луковую архитектуру, поскольку кажется, что она соответствует потребностям будущего развития.
Я настроил слои, которые, как мне кажется, мне нужно использовать, и мое решение теперь выглядит так:
Итак, в основном, как я понял, ClientName.Core
Проект не должен иметь никаких ссылок на другие проекты вообще. ClientName.Infrastructure
должна иметь ссылку на ClientName.Core
, Папка Интерфейсы на ClientName.Core
определяет услуги, которые находятся в ClientName.Infrastructure
project и my DbContext и доменные сущности разделены, поэтому только сущности находятся в основном проекте.
Где я провел головой о стену, что ClientName.Infrastructure
Проект не должен возвращать сущности домена какому-либо клиенту, который его вызывает. Это создаст ссылку между основным проектом и любым пользовательским интерфейсом, который "нарушает" принцип действия лука. Как я уже читал, способ обойти это - заставить инфраструктурные сервисы возвращать DTO. Однако, если я вернусь, т.е. PersonDto
от PersonService
класс, PersonDto
объект должен быть известен ClientName.Core
проект, так как это где интерфейс.
Таким образом, вопрос заключается в следующем: куда именно я помещаю DTO/ViewModel/ другие модели, используемые для пользовательского интерфейса / клиента? Создаю ли я отдельную библиотеку классов, которая содержит эти модели и позволяет ссылаться на нее как пользовательскому интерфейсу, инфраструктуре, так и основным проектам?
Любая помощь / подсказка очень ценится, так как я немного запутался в этом;-)
Заранее спасибо.
РЕДАКТИРОВАТЬ
Основываясь на ответе Euphorics, я пишу пример кода здесь, чтобы проверить, правильно ли я понял, и, возможно, с некоторыми дополнительными вопросами.
Так в принципе, по моему ClientName.Core
слой, у меня есть свои сущности, которые содержат бизнес-логику, то есть Person
и Firm
сущность может выглядеть так:
(Exists in ClientName.Core/Entities)
public class Person
{
public int Id { get; set; }
public string Name { get; set; }
public Firm Firm { get; set; }
}
(Exists in ClientName.Core/Entities)
Public class Firm
{
public int Id { get; set; }
public string Name { get; set; }
public IEnumerable<Person> Employees { get; set; }
public IEnumerable<Person> GetEmployeesByName(string name)
{
return Employees.Where(x => x.Name.Equals(name));
}
public void AddEmployee(Person employee)
{
Employees.Add(employee);
}
public void CreateFirm(Firm firm)
{
// what should happen here? Using entity framework, I have no reference to the DbContext here...
}
}
(Exists in ClientName.Infrastructure/Services)
public class FirmService : IFirmService
{
private readonly IDbContext _context;
public FirmService(IDbContext context)
{
_context = context;
}
public Firm GetById(int firmId)
{
return _context.Firms.Find(firmId);
}
// So basically, this should not call any domain business logic?
public void CreateFirm(CreateFirmFormViewModel formViewModel)
{
Firm firm = new Firm()
{
Name = formViewModel.Name;
}
_context.Firms.Add(firm);
_context.SaveChanges();
}
public IEnumerable<Person> GetEmployeesByName(int firmId, string name)
{
Firm firm = _context.Firms.Find(firmId);
return firm.GetEmployeesByName(name);
}
}
Правильно ли я понимаю, что каждый запрос на чтение должен быть определен непосредственно на объекте (возможно, как расширение объекта, так как я использую Entity Framework), и любое создание / обновление / удаление будет происходить только в службах на уровне инфраструктуры?
Если читать (то есть IEnumerable<Person> GetEmployeesByName(int firmId, string name)
метод) методы также должны быть на FirmService
интерфейс / класс?
Еще раз спасибо:-)
1 ответ
Во-первых, вы не возвращаетесь Person
от PersonService
, но из IPersonService
, Это огромная концептуальная разница. PersonService только реализует то, что IPersonService
определяет. Неважно, какие интерфейсы или объекты он использует.
Короче говоря, основной проект должен включать всю бизнес-логику в бизнес-объектах. Что, если у вас нет никакой реальной логики, было бы просто DTO. Затем Infrastructure
будет нести ответственность за сохранение / загрузку этих объектов из базы данных. Если он создает свою собственную модель со своими сущностями или использует те, которые определены ядром, Infrastructure
Реализация подробно. В то же время UI
(или веб) проект будет работать с объектами в Core
проект, но его не волнует, как происходит постоянство. Он просто хочет "заниматься бизнесом".
Ключевым моментом здесь является то, что ядро (или бизнес-логика) автоматически тестируется без участия UI
или код базы данных. Пока вы можете этого достичь, не имеет значения, где находятся некоторые DTO.
Редактировать:
Это начинает становиться все труднее. Вы столкнулись с противоречивыми требованиями к дизайну. С одной стороны, у вас есть четкий дизайн домена. С другой стороны, вы хотите, чтобы ваша модель была устойчивой с помощью EF. Ясно одно: вы собираетесь скомпрометировать и исказить модель своего домена, чтобы ее можно было использовать в EF.
Теперь по конкретным вопросам. Первое - это создание фирмы. Здесь у вас есть конфликт между "новой фирмой", как понимается доменом. Например. убедившись, что новая фирма имеет правильное имя. С другой стороны, вы хотите создать экземпляр и сообщить об этом EF. Я бы сделал так, чтобы удалить CreateFirm
от Firm
класс и изменение CreateFirm
в IFirmService
так что он принимает только параметры, необходимые для создания нового Firm
(например, только название) и вернуть вновь созданную фирму. Кроме того, идея, что хранилище не должно вызывать домен, неверна. Например, хранилище может вызвать домен для проверки, если созданная им фирма действительна, а если нет, то не добавлять ее в EF.
Второй вопрос, который я вижу, - это сохранение коллекции сотрудников в фирме. Хотя это хорошо для домена, это катастрофа для постоянства (если только вы не думаете, что в этом сценарии хороша ленивая загрузка). Снова. Не существует простого способа удовлетворить требование домена "Фирма имеет коллекцию сотрудников" и возможность сохранить объект в базе данных. Первый вариант - не иметь эту коллекцию и переместить ее в IFirmService
где это может быть правильно реализовано. Третий вариант самый экстремальный. И это сделать Firm
абстрактный класс, в то время как все get становятся абстрактными методами, создают его реализацию в инфраструктурном проекте и реализуют методы get с использованием контекста EF, который будет иметь конкретный экземпляр. Кроме того, это может быть возможно, только если конкретный случай Firm
создается в инфраструктуре, о чем я и говорил выше.
Мне лично нравится третий вариант, потому что он делает методы красивыми и связными. Но некоторые пуристы EF могут не согласиться со мной.