Привет.
Может у кого будут дельные советы по следующей проблеме:
Есть BusinessObject
public class TaxEntity : ITaxEntity
{
private ITaxRow taxRow;
public TaxEntity(ITaxRow taxRow)
{
this.taxRow = taxRow;
}
public decimal? Amount
{
get
{
return taxRow.Amount;
}
set
{
taxRow.Amount = value;
}
}
#endregion
public ITaxRow TaxRow
{
get { return taxRow; }
}
}
и маппер на эту ентити:
public class TaxMapper : ITaxMapper
{
private ITaxGateway gateway;
public TaxMapper(ITaxGateway gateway)
{
this.gateway = gateway;
}
public ITaxEntity GetPurchaseOrderTax(int orderId)
{
return new TaxEntity(gateway.GetPurchaseOrderTax(orderId));
}
public void UpdatePurchaseOrderTax(ITaxEntity tax)
{
gateway.UpdatePurchaseOrdertax(tax.TaxRow);
}
}
Идея — маппер знает все о том как поднимать и сохранять BusinessEntity, а она в свою очередь ничего о нем не знает:
ITaxEntity tax = taxMapper.GetPurchaseOrderTax(orderId);
// Do smth with tax
taxMapper.UpdatePurchaseOrderTax(tax);
Проблема:
Из BusinessEntity "торчит" объект, который представляет собой одну запись в таблице (TaxRow), для того чтобы маппер мог достать его и передать в Gateway. Это естественно нарушает инкапсуляцию и му4ает мою совесть — так как я хочу исклю4ить любую возможность доступа из бизнеслогики к датааксесс классам.
Вопрос:
Как избавиться от этого поля?
Все фигня кроме п4ел... П4елы впринципе тоже фигня, но их много.
Здравствуйте, sLMoloch, Вы писали:
LM>Из BusinessEntity "торчит" объект, который представляет собой одну запись в таблице (TaxRow), для того чтобы маппер мог достать его и передать в Gateway. Это естественно нарушает инкапсуляцию и му4ает мою совесть — так как я хочу исклю4ить любую возможность доступа из бизнеслогики к датааксесс классам.
Я не совсем понял, что такое TaxRow?
LM>Вопрос: LM>Как избавиться от этого поля?
Просто его не использовать. Тебе не кажется, что класс TaxRow — просто излишен?
TaxRow это представление одной записи в базе данных. Грубо говоря RecordSet типизированный. BusinessEntity делегирует некоторые поля из него. Для того чтобы сохранить BusinessEntity в базу данных надо иметь доступ к этому рекордсету, вот для этого и сделана эта проперти.
Все фигня кроме п4ел... П4елы впринципе тоже фигня, но их много.
Здравствуйте, Saruwatari, Вы писали:
S>Здравствуйте, sLMoloch.
S>На сколько понял, Вы в C# все это проектируете? S>Все что дальше: относительно ADO.NET
S>И так, идея понятна. Мне не понятно единственное: зачем дублировать написанное?
S>Есть DataTable, DataRow и это в принципе достаточно.
S>Делаем на сущности, унаследовавшись от DataTable и DataRow:
S>
S>class abstract MyBaseEntityList: DataTable
S>{
S> public IList<MyBaseEntity> Items {
S> get
S> {
S> return this.GetItems(this);
S> }
S> }
S> // конвертация строк
S> public abstract IList<MyBaseEntity> GetItems(MyBaseEntityList);
S>}
S>class abstract MyBaseEntity: DataRow
S>{
S> publci int Id
S> {
S> get { return DBConverter.ToInt(this["ENTITY_ID"]);
S> }
S>}
S>
S>Ну и так далее. Идея ясна? Можно и интерфейсы к этому привязать... S>Если так подойти к решению проблемы, думаю, решать куда деть row не придется.
Привет,
идея предельно ясна. Только наверное вы немного недопоняли мое описание. Тот класс, что вы описали (MyBaseEntity) и есть мой ITaxRow. Это просто переносчик информации. TaxEntity это BusinessObject. В приведенном примере он просто делегирует одно поле из ITaxRow, это наверное и вызвало вопрос — за4ем дублировать код. Просто в реальной жизни этот TaxEntity будет содержать в себе некоторое количество бизнес-функций (пересчитать таксу например) и даже другие бизнес объекты.
Ну и соответственно чтобы сохранить BusinessEntity нужно достать из него наш переносчик информации MyBaseEntity в вашем слу4ае и ITaxRow в моем. Можно вывести паблик проперти, но это нарушает инкапсуляцию. Можно нау4ить BusinessEntity сохранять себя через mapper, но это нарушает модель (ентити не должно знать о своей фабрике). Можно ввести посетителя для того 4тобы достать Row, но это излишне усложняет все.
Вопрос по прежнему остается открытым.
Все фигня кроме п4ел... П4елы впринципе тоже фигня, но их много.
Здравствуйте, sLMoloch, Вы писали:
LM>TaxRow это представление одной записи в базе данных. Грубо говоря RecordSet типизированный. BusinessEntity делегирует некоторые поля из него.
А для чего он тебе нужен? Если ты решил использовать BusinessEntities, то почему результат запроса в базу не меппить сразу на твой Tax минуя типизированные рекордсеты? И, кстати, что остается с полями, которые TaxRow не делегирует?
LM>Для того чтобы сохранить BusinessEntity в базу данных надо иметь доступ к этому рекордсету, вот для этого и сделана эта проперти.
Опять же, почему не сохранять в базу сам Tax минуя типизированный рекордсет?
P.S. Тебе нужно просто определиться: или ты используешь типизированные датасеты или используешь бизнес сущности. Смешение подходов ни к чему кроме путанницы не приведет.
Здравствуйте, sLMoloch, Вы писали:
LM>Привет, LM>идея предельно ясна. Только наверное вы немного недопоняли мое описание. Тот класс, что вы описали (MyBaseEntity) и есть мой ITaxRow. Это просто переносчик информации. TaxEntity это BusinessObject. В приведенном примере он просто делегирует одно поле из ITaxRow, это наверное и вызвало вопрос — за4ем дублировать код. Просто в реальной жизни этот TaxEntity будет содержать в себе некоторое количество бизнес-функций (пересчитать таксу например) и даже другие бизнес объекты.
1. Не советую содержать функции в Business Entity. И вообще слишком связывать бизнес-функции с состоянием конкретного бизнес-entity. Это только усложняет систему и усложняет внесение изменений. Смысл business entity в том, что это простой объект с состоянием который мы можем использовать в разных позах и в различных местах.
2. Не стоит стараться зашифровать внутренние функции. Для этого есть другие, более действенные, способы. Задача в том, что сделать соглашение по использованию объекта. Если ты предоставляешь некоторый интерфейс, то в принципе легко обговорить что данный интерфейс не может быть использован за пределами mapper всеми участниками команды.
3. Есть InternalsVisibleToAttribute. С помощью данного аттрибута можно раскрыть внутренние поля для определенной сборки.
Здравствуйте, MaximVK, Вы писали:
MVK>Здравствуйте, sLMoloch, Вы писали:
LM>>TaxRow это представление одной записи в базе данных. Грубо говоря RecordSet типизированный. BusinessEntity делегирует некоторые поля из него. MVK>А для чего он тебе нужен? Если ты решил использовать BusinessEntities, то почему результат запроса в базу не меппить сразу на твой Tax минуя типизированные рекордсеты? И, кстати, что остается с полями, которые TaxRow не делегирует?
BisinessEntity это очень сложная сущность, которая может содержать кучу других сущностей. Например Заказ может содержать список Товаров. Маппер знает как взять строку заказа и строки Товаров из базы данных, и соединить их в единую BusinessEntity Заказ.
Поля которые BusinessEntity не делегирует, просто не изменяются и сохраняются в базу как были.
LM>>Для того чтобы сохранить BusinessEntity в базу данных надо иметь доступ к этому рекордсету, вот для этого и сделана эта проперти. MVK>Опять же, почему не сохранять в базу сам Tax минуя типизированный рекордсет?
Смотри выше. TaxEntity на примере о4ень простой, из-за того, что это просто пример. В реальной жизни он будет содержать к примеру еще процентную ставку как бизнесСущность. Напрямую это замапить на DB дело неблагодарное.
MVK>P.S. Тебе нужно просто определиться: или ты используешь типизированные датасеты или используешь бизнес сущности. Смешение подходов ни к чему кроме путанницы не приведет.
Дело в том, что вся эта кухня варится в рамках legacy проекта, кде использовался подход типизированного рекордсета. Ни к чему кроме матов это не привело (проекту два года).
Все фигня кроме п4ел... П4елы впринципе тоже фигня, но их много.
Здравствуйте, GlebZ, Вы писали:
GZ>1. Не советую содержать функции в Business Entity. И вообще слишком связывать бизнес-функции с состоянием конкретного бизнес-entity. Это только усложняет систему и усложняет внесение изменений. Смысл business entity в том, что это простой объект с состоянием который мы можем использовать в разных позах и в различных местах.
Все таки хочется сделать что-то похожее на доменную модель по дядьке Фаулеру, чем по сервисной архитектуре от Микрософта... В доменной можели опыта не было — вот потому и советуюсь.
GZ>2. Не стоит стараться зашифровать внутренние функции. Для этого есть другие, более действенные, способы. Задача в том, что сделать соглашение по использованию объекта. Если ты предоставляешь некоторый интерфейс, то в принципе легко обговорить что данный интерфейс не может быть использован за пределами mapper всеми участниками команды.
А вот это вы зря. В реальных проектах, в которых большая теку4ка среди персонала невозможно поддерживать какие-то традиции. Лу4ше жестко закрыть какие-то ни было возможности некорректного использования вашего интерфейса.
GZ>3. Есть InternalsVisibleToAttribute. С помощью данного аттрибута можно раскрыть внутренние поля для определенной сборки.
Очень дельное предложение, но опять-же полностью поле оно не закрывает. В рамках одной ассембли BusinessEntity могут использовать другие BusinessEntity — и для них поле с Row будет открыто. GZ>С уважением, Gleb
Все фигня кроме п4ел... П4елы впринципе тоже фигня, но их много.
Здравствуйте, sLMoloch, Вы писали:
LM>Все таки хочется сделать что-то похожее на доменную модель по дядьке Фаулеру, чем по сервисной архитектуре от Микрософта... В доменной можели опыта не было — вот потому и советуюсь.
Это не совсем верное и узкое понимание доменной модели. Доменная модель не определяет наличие бизнес-логики в БО. У нее одно правило — это аквариум в котором находятся ООП объекты.
LM>А вот это вы зря. В реальных проектах, в которых большая теку4ка среди персонала невозможно поддерживать какие-то традиции. Лу4ше жестко закрыть какие-то ни было возможности некорректного использования вашего интерфейса.
Для этого существует FxCop/Code Analize. Специализированное средство с которым можно устанавливать правила единого стандарта разработки.
LM>Очень дельное предложение, но опять-же полностью поле оно не закрывает. В рамках одной ассембли BusinessEntity могут использовать другие BusinessEntity — и для них поле с Row будет открыто.
Зачем? Ты открываешь доступ к внутреннему состоянию для других сущностей, делаешь неконтролируемые зависимости. Лучше определить явный публичный интерфейс, и юзать только его, всеми кто не попадя. Как собственно и делают большинство.
Здравствуйте, sLMoloch, Вы писали:
LM>BisinessEntity это очень сложная сущность, которая может содержать кучу других сущностей. Например Заказ может содержать список Товаров. Маппер знает как взять строку заказа и строки Товаров из базы данных, и соединить их в единую BusinessEntity Заказ.
Пусть содержит. Пусть у маппера есть метод, который по id заказа возращает тебе полноценную BE с вместе с товарами. В чем трудности то?
LM>Поля которые BusinessEntity не делегирует, просто не изменяются и сохраняются в базу как были.
Опять таки, я не вижу проблем. Если это, например, служебное поле в базе заведенное там с целью оптимизации каких нибудь внутрибазовых операций — то просто его не вытаскиваешь и все. Если это поле не может изменяться, ну просто не делаешь у него сеттер и все.
LM>Смотри выше. TaxEntity на примере о4ень простой, из-за того, что это просто пример. В реальной жизни он будет содержать к примеру еще процентную ставку как бизнесСущность. Напрямую это замапить на DB дело неблагодарное.
Вот я непонимаю, почему? У всех все меппиться и без особых проблем. Ты можешь привести пример реальной BE и табличек в базе и посмотрим в чем трудности меппинга.
LM>Дело в том, что вся эта кухня варится в рамках legacy проекта, кде использовался подход типизированного рекордсета. Ни к чему кроме матов это не привело (проекту два года).
Довольно трудно будет вам сменить подход. Идеальный вариант — это найти способ изолировать всю вашу кухню от нового кода. Т.е. не мешать BE и датасеты в одном месте, т.к. вы фактически свяжете вместе два подхода.Если такой возможности нет, я бы десять раз подумал о смене подхода.
Здравствуйте, sLMoloch.
LM>Привет, LM>идея предельно ясна. Только наверное вы немного недопоняли мое описание. Тот класс, что вы описали (MyBaseEntity) и есть мой ITaxRow. Это просто переносчик информации. TaxEntity это BusinessObject. В приведенном примере он просто делегирует одно поле из ITaxRow, это наверное и вызвало вопрос — за4ем дублировать код. Просто в реальной жизни этот TaxEntity будет содержать в себе некоторое количество бизнес-функций (пересчитать таксу например) и даже другие бизнес объекты.
Есть два подхода:
— создать в необходимой сущности нужное(ый) свейство/метод;
— использовать другую сущность, которая использует экземпляр БО как параметр.
Выгоднее второй вариант.
Не вижу причины, по которой данный из DataRow нужно перекидывать еще в один другой объект. (Не забываем о GC.)
LM>Ну и соответственно чтобы сохранить BusinessEntity нужно достать из него наш переносчик информации MyBaseEntity в вашем слу4ае и ITaxRow в моем. Можно вывести паблик проперти, но это нарушает инкапсуляцию. Можно нау4ить BusinessEntity сохранять себя через mapper, но это нарушает модель (ентити не должно знать о своей фабрике). Можно ввести посетителя для того 4тобы достать Row, но это излишне усложняет все.
Зачем? В моем подходе БО удобен для работы с DataTable, и, как следствие, с DataAdapter.
Вам видимо надо применить разновидность фаулеровского паттерна Identity Map.
Суть его состоит в том, чтобы хранить соответствие между DomainObject и DataRow внутри маппера в виде коллекции.
примерно вот так (набросок, чтобы понять идею):
public abstract class MappingObject
{
protected IDictionary<Key, ModelObject> MappedObjects = new Dictionary<Key, ModelObject>();
protected IDictionary<Key, DataRow> ObjectRows = new Dictionary<Key, DataRow>();protected ModelObject Find(Key key)
{
//TODO: сначала поищем в MappedObjects
}
protected void Save(ModelObject modelObject)
{
DataRow objectRow = ObjectRows[modelObject.key];
//TODO
}
тогда ссылку на DataRow внутри BO можно сделать protected