Размещение моделей представления /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 могут не согласиться со мной.

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