Архитектура DDD для проекта ASP.NET MVC2

Я пытаюсь использовать Domain Driven Development (DDD) для моего нового проекта ASP.NET MVC2 с Entity Framework 4. После некоторых исследований я придумал следующие соглашения о слоях для каждого уровня в своем собственном проекте класса:

MyCompany.Domain

     public class User
    {
        //Contains all the properties for the user entity
    }

    public interface IRepository<T> where T : class
    {
        IQueryable<T> GetQuery();
        IQueryable<T> GetAll();
        IQueryable<T> Find(Func<T, bool> condition);
        T Single(Func<T, bool> condition);
        T First(Func<T, bool> condition);
        T GetByID(int id);
        void Delete(T entity);
        void Add(T entity);
        void Attach(T entity);
        void SaveChanges();
    }

  public interface IUserRepository: IRepository<User> {}

    public class UserService
    {
        private IUserRepository _userRepository;
        public UserService(IUserRepository userRepository)
        {
            _userRepository = userRepository;
        }
    // This class will hold all the methods related to the User entity
    }

MyCompany.Repositories

public class UserRepository : IRepository<User>
{
    // Repository interface implementations
}

MyCompany.Web -> Это проект MVC2

В настоящее время мой слой Repositories содержит ссылку на слой Domain. Насколько я понимаю, внедрение UserRepository в класс UserService очень хорошо работает с модульным тестированием, поскольку мы можем передать поддельные пользовательские репозитории. Так что с этой архитектурой, похоже, мой веб-проект должен иметь ссылки как на мой домен, так и на уровни репозитория. Но так ли это? Потому что исторически уровень представления имел только ссылку на уровень бизнес-логики.

3 ответа

Решение

Это очень правильно, и на самом деле очень похоже на настройку, которую мы имеем.

Однако в вашем вопросе у вас есть IRepository в проекте домена я бы поместил это в вашу сборку репозиториев.

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

У нас также есть сервисный уровень, который является посредником между пользовательским интерфейсом (контроллерами) и репозиторием. Это позволяет централизованно размещать логику, которая не принадлежит репозиторию - такие как пейджинг и проверка.

Таким образом, пользовательский интерфейс не ссылается на репозиторий, а только на уровень обслуживания.

Мы также используем DI для внедрения EntityFrameworkRepository в наш сервисный уровень и MockRepository в наш тестовый проект.

Похоже, вы на правильном пути, но, поскольку он, похоже, хочет пойти по пути "DDD-All-The-Way", рассматривали ли вы возможность внедрения шаблона " Единица работы" для управления несколькими хранилищами, использующими один и тот же контекст?

Just some notes on @RPM1984 answer...

However in your question, you've got IRepository in the domain project, i would put this in your Repositories assembly.

While it does not matter that much where You put things physically, it's important to remember that abstractions of repositories are part of domain.

We also have a Service layer mediating between the UI (Controllers) and the Repository. Это позволяет централизованно размещать логику, которая не принадлежит репозиторию - такие как пейджинг и проверка.

I believe that it's not good idea to create service just for paging and validation. Even worse if it's created artificially - just because 'everything goes through services' and 'that way You don't need to reference repositories from UI'. Application services should be avoided when possible.

For input validation - there's already nice point for that out of the box. If You use asp.net mvc, consider following MVVM pattern and framework will provide good enough ways to validate Your input view models.

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

Похоже, вы на правильном пути, но, поскольку он, похоже, хочет пойти по пути "DDD-All-The-Way", рассматривали ли вы возможность внедрения шаблона "Единица работы" для управления несколькими хранилищами, использующими один и тот же контекст?

Я думаю, что единицы работы тоже следует избегать. Вот почему:

Другой пример, UoW - это действительно проблема инфраструктуры, зачем вы вносите это в домен? Если у вас есть правильные общие границы, вам не нужна единица работы.


Репозитории не принадлежат домену. Репозитории - это постоянство (то есть инфраструктура), домены - это бизнес.

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

Сервисный уровень предоставляет центральную точку для "проверки бизнеса".

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

Вот плохой фасад:

public int incrementInteger(int val){
  return val++;
}

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

MVC хорош для простой проверки, но не для сложных бизнес-правил (например, шаблон спецификации).

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

RE UoW - я заинтригован этим предложением, но вы можете уточнить? UoW - это действительно проблема, связанная с инфраструктурой, но, опять же, это в моем репозитории / уровне данных, а не в моей области. Все, что у меня есть, это бизнес-объекты / спецификации.

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

Еще одна вещь: слои!= Ярусы.


Еще одно замечание, которое я хотел бы отметить, - это то, что DDD - это руководство, а не то, что нужно всем.

Да, но это не должно служить оправданием.:)

Другая причина нашего сервисного уровня в том, что у нас есть веб-API. Мы НЕ хотим, чтобы наш веб-API вызывал наши репозитории, нам нужен хороший свободный интерфейс, через который могут обращаться как веб-приложение, так и API.

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

Здесь Вы можете увидеть некоторые детали моего текущего подхода.

Еще одна БОЛЬШАЯ причина для нашего уровня обслуживания - наши репозитории возвращают IQueryable, поэтому они не имеют никакой логики. Наш сервисный уровень проецирует выражения linq в бетоны

Это не звучит правильно. Та же проблема - отсутствие изоляции.

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

Другое дело - если репозитории вернутся в сыром виде IQueryable, который автоматически связывает уровень обслуживания с неизвестным (!) поставщиком LINQ.

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

Не забывайте, что речь идет о технических проблемах. Эти вещи не имеют ничего общего с самим дизайном, они просто делают это возможным.


Если вы не используете сервисный слой, как бы вы (например) получили список заказов на продукт? Вам понадобится метод в вашем репозитории под названием "GetOrdersForProduct" - который я не считаю хорошим дизайном. Ваш интерфейс репозитория становится огромным, его невозможно поддерживать. Мы используем очень общий репозиторий (Найти, Добавить, Удалить и т. Д.). Эти методы отрабатывают объект, установленный в нашей модели. Универсальность / мощность запросов передаются на уровень обслуживания

Этот хитрый. Я просто оставлю хорошую ссылку.

С неизвестным провайдером я имею в виду - Вы не можете сейчас узнать, что находится под уровнем сервиса. Это может быть Linq для объектов, это может быть Linq для xml, Linq для sql, NHIbernate.Linq и множество других реализаций провайдера. Плохая вещь - вы не можете знать, что поддерживается.

Это будет хорошо работать, если это Linq для объектов, и не будет работать, если его нужно перевести в sql:

customers.Where(c=>{var pickItUp=c.IsWhatever; return pickItUp;});

Возможно, вы захотите изучить, как инфраструктура http://www.sharparchitecture.net/ решает эту проблему, так как похоже, что вы в основном воплощаете одни и те же идеи. Учебное пособие по Northwind имеет хорошее обсуждение этих концепций.

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