Я довольно новичок в использовании отдельных уровней для логики бизнес-логики (Domain) и доступа к базам данных, но в процессе работы я столкнулся с проблемой, о которой я все еще чувствую, что не нашел отличного решения.
Уточнение. В моем существующем решении используются Data Mappers для непосредственного взаимодействия с базами данных. Однако, поскольку я еще больше исследовал эту проблему, многие люди предположили, что уровень домена не должен напрямую связываться с данными и не содержать в себе Data Mappers, которые фактически выполняют взаимодействие с базой данных. Вот почему я поместил объекты репозитория между Доменом и необходимыми Data Mappers, но это не кажется вполне естественным или правильным. Итак, реальный вопрос заключается в том, какой слой естественным образом существует для обработки связи между Доменом и Data Mappers? Любые примеры того, как его структурировать, будут оценены.
Например:
Габриэль, это называется « проблемой согласования импеданса ». Существует множество решений: от супертяжелых, таких как J2EE-сущности, до Ruby ActiveRecord, просто кодируя соединение с рукой.
Хорошо, хорошо, что трудно понять, как атаковать это без дополнительной информации, но вот базовый подход.
Любой из этих видов архитектурных проблем обусловлен нефункциональными требованиями, такими как производительность; кроме того, здесь есть проблема правильности, поскольку вы хотите убедиться, что обновления выполнены в правильном порядке. Таким образом, вам нужно будет подумать о рабочей нагрузке , то есть о шаблоне использования в реальном приложении. Имея это в виду, в основном у вас есть несколько проблем: во-первых, базовые типы данных в вашем приложении могут неправильно отображаться в базе данных (например, что такое свойство VARCHAR, представленное как в вашем коде?), И во-вторых, ваша модель домена не может корректно отображать вашу модель базы данных.
Вы хотите, чтобы база данных и модель dmain работали так, чтобы один экземпляр объекта домена был точно такой же строкой таблицы в вашей модели базы данных; в крупномасштабных приложениях вы редко можете это сделать из-за ограничений производительности или ограничений, налагаемых ранее существующей моделью базы данных.
Теперь, если вы полностью контролируете свою модель базы данных, это несколько упрощает, потому что тогда вы можете сделать модель вашей базы данных более близкой к домену. Это может означать, что модель базы данных несколько денормализована, но если это так, вы можете (в зависимости от вашей базы данных) обрабатывать ее с помощью представлений или просто не иметь полностью нормализованной базы данных. Нормализация – полезная теоретическая конструкция, но это не означает, что вы не можете расслабиться в реальной системе.
Если вы не полностью контролируете свою модель базы данных, вам нужен слой объектов, которые делают сопоставление. У вас есть множество вариантов, которые можно выбрать при реализации этого: вы можете создавать представления или денормализованные таблицы в базе данных, вы можете создавать промежуточные объекты, или вы можете сделать некоторые из них или даже выполнить несколько шагов обоих (т. Е. промежуточный объект, который обращается к денормализованной таблице.)
В этот момент, однако, вы сталкиваетесь с проблемами с «не повторяйте себя» и «делайте простейшую вещь, которая, возможно, сработает». Подумайте о том, что, скорее всего, изменится? Ваша модель домена? Если у вас есть сильная модель домена, это менее вероятно – бизнес меняется относительно редко. Точное представление данных в базе данных? Немного более распространено. Или, чаще всего, точные шаблоны использования (например, обнаружение необходимости обработки параллельных обновлений.) Итак, когда вы об этом думаете, что вам нужно сделать, чтобы сделать как можно проще справляться с наиболее распространенными изменениями.
Я понимаю, что это не дает вам очень точных инструкций, но я не думаю, что мы можем предложить точные инструкции, не зная много о вашем заявлении. Но тогда у меня также возникает впечатление, что вам интересно, какой «правильный» способ справиться с этим был бы, пока вы уже работаете с чем-то более или менее выполняющим эту работу. Итак, я бы в конечном итоге спросил: «Что ты сейчас недоволен?» и «Как бы вы хотели это решить?»
Существует различие между моделью домена и ее реализацией. Просто потому, что ваша модель показывает отношения Person ---> Campaign ---> Event
не означает, что вы должны реализовать его таким образом. IOW, ваша модель показывает ваш анализ и дизайн объектно-ориентированным способом, но вы реализуете эту модель в ООП, которая ограничена тем, насколько хорошо она может реплицировать эту модель в коде.
Рассмотрим следующее.
Person
не определяется собственностью Campaign
, поэтому кампанию можно оставить без внимания. С другой стороны, Campaign
определяется Event
s, которое происходит как часть его выполнения, поэтому справедливо иметь коллекцию событий внутри кампании. То, что я делаю, состоит в том, что у каждого класса должно быть достаточно поведения и знаний, чтобы сделать его целым.
Что касается связи между доменом и уровнями сохранения, рассмотрите их как две очень разные системы, которые не связаны с другой. Каждый из них знает, каковы его обязанности и какие объявления он делает. Например, уровень персистентности знает, как перенести данные, переданные ему, и объявить, что данные были сохранены. Однако уровень персистентности не обязательно должен понимать объекты домена. Аналогично, доменный уровень понимает Person
, Campaign
и Event
но ничего не знает о сохранении.
Следствием этого является то, что доменный слой должен быть целым сам по себе и не должен зависеть от уровня сохранения для его данных. Однако для выполнения своих обязанностей все еще необходимо предоставить данные. Эти данные могут поступать либо из пользовательского интерфейса, либо из базы данных и передаются ему через стороннюю организацию, которая знает как уровни домена, так и уровни сохранения.
Итак, в коде (псевдо-C #) …
namespace DomainLayer { interface IDomainListener { void PersonCreated(Person person); } class Person { private string name; public Person(string name) { this.name = name; } public string Name { get { return name; } } } class Domain { private IDomainListener listener; public Domain(IDomainListener listener) { this.listener = listener; } public void CreatePerson(string name) { Person person = new Person(name); listener.PersonCreated(person); } } } namespace PersistenceLayer { interface IPersistenceListener { void PersonDataSaved(int id, object data); } class Persistence { private IPersistenceListener listener; public Persistence(IPersistenceListener listener) { this.listener = listener; } public void SaveData(object data) { int id = ...; // save data and return identifier listener.DataSaved(id, data); } } } namespace MyApplication { class MyController : IDomainListener, IPersistenceListener { public void CreatePersonButton_Clicked() { Domain domain = new Domain(this); domain.CreatePerson(NameTextbox.Text); } public void PersonCreated(Person person) { Persistence persistence = new Persistence(this); persistence.SavePersonData(person.Name); } public void DataSaved(int id, object data) { // display data on UI } } }
Как вы можете видеть, пространства имен представляют разные уровни. Интерфейсы XYZListener
определяют объявления, которые производятся на уровне XYZ
. Любые другие уровни, которые заинтересованы в этих объявлениях и будут отвечать им, должны реализовать эти интерфейсы, как и наш уровень MyApplication
.
При нажатии кнопки «create button» контроллер создает объект фасада Domain
для уровня домена и регистрируется как слушатель. Затем он вызывает метод CreatePerson
который создает экземпляр Person
затем объявляет, что это было сделано, передавая новый экземпляр. Контроллер отвечает на это объявление в реализации PersonCreated
где он порождает фасад слоя сохранения и снова регистрируется как слушатель. Затем он вызывает метод SaveData, DataSaved
после SaveData
записывает DataSaved
. Затем реализация этого метода отображает данные в пользовательском интерфейсе.
Как вы можете видеть, уровень домена и уровень персистентности осведомлены только о себе и не связаны с обязанностями другого. Это логика приложения, проявленная здесь как контроллер, которая соединяет их вместе.
Вернемся к вашей конкретной проблеме, вы можете иметь метод FindPerson
по сохранению, который объявит PersonFound(int id)
. Ответ контроллера будет состоять в том, чтобы вызвать уровень персистентности для извлечения данных о кампании и событиях, а затем вызвать слой домена с этими данными для создания Person
.
Извините за длинный ответ…
Во многих системах используется независимый уровень данных для управления сохранением базы данных и из нее. Существует несколько моделей для организации такого слоя. Некоторые используют своего рода заводскую реализацию, другие используют сопоставление «один к одному» с одним классом уровня данных для класса домена.
Модель слоя данных часто зависит от стиля и предпочтения. Важно отделить слой сохранения от уровня домена. Я считаю, что есть инструменты, которые помогут вам сгенерировать этот слой, но мои знания PHP тонкие, поэтому я не могу назвать их специально для PHP.
Я бы посмотрел на уровни абстракции данных, используемые PHPCake и Symfony.