2012-06-28 5 views
4

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

На данный момент мы внедрили сервисный уровень за пределами домена, с сервисом на каждый агрегатный корень ([EntityName] Service). Все остальные слои используют эти службы для получения ссылок на общий корень с помощью методов GetByThis() и GetByTheOther(). Все наши вызовы в домен из других слоев осуществляются через эти службы.

Услуги содержат ссылки на репозитории (которые больше нигде не упоминаются), а также отвечают за все поведение сохранения/обновления и управление транзакцией. Методы обслуживания растут по сложности и иногда имеют поведение, которое, по-видимому, принадлежит домену, например логика условного создания (если свойство = этот набор дочерних объектов что-то указывает, в противном случае что-то еще). Наши объекты домена имеют в основном простые методы, такие как GetByThis() и HasAThing(). Я чувствую, что мы теряем выразительность нашего домена.

Мои основные вопросы:

  • Если уровень службы содержат так много логики? если нет, то где следует ? если домен должен заполнить корни ссылками на репозиториев? если да, то как они могут быть введены (в фабрики , которые создают совокупные корни?)
  • Как обрабатывать транзакцию?
  • Если сущности (или совокупные корни) содержат ссылки на домен услуг? Если да, то как они должны получить ссылки?
  • Чтобы получить новый идентификатор для объекта, мы должны вызвать хранимую процедуру, которую мы завернули в репозиторий. Где вы бы указали это? Некоторые сложные методы для Entities, которые нуждаются в для создания множества дочерних объектов, должны будут ссылаться на это?

EDIT

Спасибо за Продуманная ответы @ DAVID-мастеров и @ guillaume31.

Вы помогли мне решить, что «вонючий код» чувствует, что я получаю.

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

Для кого-то, кто смотрит на это, оба ответа дали отличный совет, но для меня это был лучший совет:

«С прагматической точки зрения, вот что я хотел бы спросить себя: если я хочу взять часть моего уровня домена и повторное использование его в другом приложении, будет ли он содержать все бизнес-правила и поведение, которые мне нужны для использования домена в этом новом приложении? Если нет, то, возможно, это означает, что некоторые части, которые в настоящее время находятся на стороне приложения, должны быть перемещены на уровень домена.»

Я пересмотрела наш домен и сервисный слой с этим в виду, и теперь считаю, я решил наши проблемы проектирования

ответ

4

Должен ли сервисный уровень содержать столько логики? если нет, где это должно быть go?

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

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

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

Если домен, следует заполнить корни ссылками, связанными с репозиториями?

IMO агрегатной корень не должен содержать ссылку на свой собственный репозиторий и знать, как хранить себя, потому что она разрушает невежество и упорство вводит дополнительную ответственность в объекте домена, который будет запутать его. Однако Aggregate Root может иногда ссылаться на репозиторий другого объекта.

Как обрабатывать транзакцию?

Я бы сказал, что есть 2 вида сделок:

  • «Пользовательская» операции в прикладном, так называемом «единица работы». Это транзакции высокого уровня, которые охватывают прецедент или последний для жизни веб-страницы в веб-приложении («открытый сеанс в представлении»). Структуры ORM часто предоставляют средства для управления этими транзакциями.

  • Сделки в доменном слое. Они могут быть инициированы объектом или службой домена. Например: FundsTransferService.Transfer() может использовать транзакцию внутри страны. Здесь вы можете использовать базовую транзакционную обработку вашей платформы.

Если юридические лица (или агрегатные корни) держать ссылки на домен услуг? Если да, то как они должны получить ссылки?

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

Для того, чтобы получить новый идентификатор для объекта, мы должны вызвать хранимую процедуры, которую мы завернутая в хранилище. Где бы вы взяли ? Некоторые сложные методы для Entities, которые должны создать , должны были бы ссылаться на многие дочерние объекты?

Я бы не рекомендовал создавать идентификаторы для объектов, доступных по запросу. Как указывал Дэвид, часто лучше создавать «Guid» на языковом уровне, когда вы новичок в сущности.

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

11

сервис прикладного уровня не должен содержать никакой логики домена. Цель прикладных услуг - «оркестровать». Он не должен принимать домен решений; все бизнес-решения должны быть в объектах домена или службах домена. Служба приложения получает вызов от потребителя (как правило, пользовательский интерфейс) и вызывает методы в доменных и инфраструктурных службах. У приложений-служб не должно быть названий crud, как вы описали. Они должны иметь значащие глаголы, описывающие прецедент. Вот пример того, что служба приложения может выглядеть для банковского приложения:

public class AccountService : IAccountService 
{ 
    //These are injected via dependency injection on the constructor 
    private readonly IAccountRepository _accountRespository; 
    private readonly IEmailNotificationService _emailNotificationServce; 

    public void FreezeAccount(Guid accountId) 
    { 
     Account account = _accountRespository.GetById(accountId); 

     using (IUnitOfWork unitOfWork = UnitOfWorkFactory.Create()) 
     { 
      account.Freeze(); 
      _accountRespository.Save(account); 
      _emailNotificationServce.Send(CreateFreezeNotification(account));   
     } 
    } 
} 

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

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

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

Что касается идентификаторов: я всегда выбираю идентификаторы Guid, а не идентификаторы базы данных. Я просто считаю, что жизнь намного проще и позволяет избежать проблемы, которую вы описываете. Если вам нужна база данных для ввода идентификаторов (например, столбец INT IDENTITY), возможно, вы можете сделать это свойство вторичного идентификатора и использовать идентификатор Guid для целей домена, чтобы сохранить накладные расходы?

Смежные вопросы