Здравствуйте, Andrey Lastochkin, Вы писали:
IB>>А для чего ты это делаешь? AL>Для написания еще одного DAL.
Для этого тоже не всегда обязательно выносить DAL в отдельную сборку. Как уже сказал Иван, при разделении на сборки нужно руководствоваться соображениями развёртывания.
AL>Вот так и было, мы делали отдельные бизнес-сущности, у нас они назывались "Info-классы", т.е. классы, содержащие только данные, но не обладающие поведением. Только в этом случае не всегда было понятно, какие методы делать в Info-классе, а какие в BLL.
Если логика не цепляет что-то извне, например тот же BLL, то можно оставлять такую логику в объекте. Иначе всё в BLL.
AL>Например, если есть ProductCost и DeliveryCost, и нужно добавить вычисляемое свойство TotalCost, не понятно в какой слой его надо помещать, теоретически это нужно делать в BLL статическим методом static OrderManager.GetTotalSum(Order order) { return order.RegionSum + order.PersonSum; }, но это же криво выглядит.
Нормально выглядит. Гораздо хуже выглядят проблемы изначального вопроса.
AL>И в результате того, что некоторые функции определены в Model, а некоторые в BLL — логика приложения размазывается.
То что размазывается — это конечно плохо. Но давай вот такой пример. Что если для вычсления total нужно сходит за дополнительными данными в БД. В этом случае где лучше размещать такую логику: только на клиенте (не получится), только в BLL (тоже не получится), в DAL (как к ней обратиться минуя BLL) или вообще в сохранённой процедуре? На практике получается, что бизнес-логика по-любому размазывается по всему приложению, по-этому бороться с этим не нужно. Нужно это возглавить и организовать.
AL>Причина номер 2: Model.Person и BLL.Person это абсолютно разные классы, т.е. фактически нарушается инкапсуляция, т.к. защищенные члены просто отсутствуют — в Model.Person они просто не нужны, т.к. он не может содержать логики, а BLL.Person не содержит состояния.
И в чём проблема?
AL>Причина номер 3: наследование (см. ниже)
Наследование вполне можно использовать в объектной модели. Тоже не пойму проблему.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
А баба яга против ... IB>1. Не выносят DAL в отдельную сборку. Сборка — это единица развертывания, а на практике развертывать DAL отдельно от BLL приходится редко. (Специально для педантов и зануд замечу, что это вовсе не означает отказ от разделения на DAL и BLL ).
Выносят, выносят..., и обычно это связано с нежеланием и не примиримостью архитектора развертывать код с обращением к БД на стороне клиента. Мотивируется обычно это соображениями безопасности, типа "нечего клиенту знать об объектах БД".
Теперь о решении проблемы circular reference как таковой:
1.Это создание прокси-классов, реализующих общий Facade-интерфейс.
Так существует ProxyDAL, ProxyService.
У меня примерно так: Клиент обращается к Agents, которые в зависимости от контекста выполнения приложения (клиент/сервер), инстанцируют тот или иной Singleton-Proxy (ProxyDAL, ProxyService). Какой прокси инстанцировать, задается в конфиге комбинацией IRemoteFacade+ProxyAssemblity+ProxyClass.
Proxy в свою очередь, уже напрямую, обращаются к DAL или к сервисам.
Может с небольшими кусочками примеров, чуть более ясно будет:
namespace BF.Core
{
[ServiceContract]
public interface ICoreFacade
{
[OperationContract]
void Test(System.String ticketSID);
[OperationContract]
void Create_SysObject(SessionContext context, SysObject obj);
}
}
namespace BF.Core
{
public class BaseAgent<T>
{
private static T m_Proxy;
public static T Proxy
{
get
{
if (m_Proxy == null)
{
m_Proxy = (T)BF.Loader.LoadProxy(System.String.Format("Proxy:{0}.{1}",typeof(T).Namespace, typeof(T).Name));
//Loader просто через рефлекшин инстанцирует объект по переданным параметрам, основной кусок кода:
//asm = System.Reflection.Assembly.Load(assemblityName);
//result = asm.CreateInstance(className);
}
return m_Proxy;
}
}
}
}
namespace BF.Core
{
public class CoreAgent:BaseAgent<ICoreFacade>
{
public static void Test() {
Proxy.Test(SessionContext context);
}
public static void Create_SysObject(SessionContext context, SysObject obj) {
Proxy.Create_SysObject(context, obj);
}
}
}
namespace BF.Core
{
public class ProxyDAL:ICoreFacade
{
public void Test(SessionContext context)
{
BF.Core.DAL.SysObject.Test(context);
}
public void Create_SysObject(SessionContext context, SysObject obj)
{
BF.Core.DAL.SysObject.Create(context, obj);
}
}
}
На самом деле решается еще одна проблема: допустим какое-либо приложение не требует таких глубоких и масштабируемых решений на сервисах и т.д
Тогда приложение просто переконфигурируется на интанцирование DALProxy и никаких сервисов разворачивать не нужно. "Клиент" работает напрямую с DAL. Характерно когда есть некоторый общий фреймворк, используемый в разных типах приложений. Тут как раз легко "прортировать" его, например, для простейшего веб-приложения.
Еще пример полезности прокси-классов — DAL для разных СУБД или даже для xml-storage.
Подобный же подход используется в знакомой, наверное, многим CSLA.NET (C# Bussiness Objects) (см. DataPortal-классы)
Думаю, обсуждение этой библиотеки выходит за рамки топика, однако идей там предостаточно как хороших, так и весьма спорных и плохих.
Кстати DAL от BO там не отделен, ни в классах, ни в сборках, что я считаю весьма плохим.
2. Еще вариант по сабжу, да простит меня IT, и другие нелюбители DTO , но тут то он как раз ПОПУТНО решает данную проблему, т.е. BO мапится на DTO, с которым в свою очередь и работает DAL. Но подчеркиваю (во избежании флеймогона), что это всего лишь попутная задача решаемая DTO.
Здравствуйте, alastochkin, Вы писали:
A> Если я выделяю DAL в отдельную сборку,
А для чего ты это делаешь?
A>Вопрос 1: Как обычно решается подобная проблема?
По разному...
1. Не выносят DAL в отдельную сборку. Сборка — это единица развертывания, а на практике развертывать DAL отдельно от BLL приходится редко. (Специально для педантов и зануд замечу, что это вовсе не означает отказ от разделения на DAL и BLL ).
2. Делают бизнес-сущности не зависимыми ни от чего, и вынеся их в отдельную сборку, можно использовать их и в BLL и в DAL сборках.
3. (Правильный =) ) Использовать одновременно первый и второй подход..
A>Недавно просматривал книгу Applying Domain-Driven Design and Patterns, и как я понял автор предлагает разделять классы Domain (состояние) и Business Logic (поведение). Мало того, говорит что классы Business Logic можно делать статическими, содержащими одни только методы, которые принимают экземпляр класса как аргумент, например static OrderManager.Save(Order order), а не Order.Save().
Умница, толковая должно быть книга... =)
A>Не могу понять такой подход. Вопрос 4: Проясните пожалуйста как подобную вещь с наследованием (см. ниже) сделать на Domain-Driven Design?:
Зачем Register(), OnRegister(), GetInstance и Kind нужны в Person?
A>> Если я выделяю DAL в отдельную сборку, IB>А для чего ты это делаешь?
Для написания еще одного DAL. Борьба мнений. Лично я не люблю особо закладываться наперед, т.к. это усложняет логику и порождает лишний код (но не исключаю держать это в уме). Но в команде есть мнение — что это облегчит перенос системы на другие базы данных, теоретически, чтобы добиться этого нужно будет написать еще один DAL и изменить в config'е имя DAL сборки .
A>>Вопрос 1: Как обычно решается подобная проблема? IB>По разному... IB>1. Не выносят DAL в отдельную сборку. Сборка — это единица развертывания, а на практике развертывать DAL отдельно от BLL приходится редко. (Специально для педантов и зануд замечу, что это вовсе не означает отказ от разделения на DAL и BLL ). IB>2. Делают бизнес-сущности не зависимыми ни от чего, и вынеся их в отдельную сборку, можно использовать их и в BLL и в DAL сборках. IB>3. (Правильный =) ) Использовать одновременно первый и второй подход..
Вот так и было, мы делали отдельные бизнес-сущности, у нас они назывались "Info-классы", т.е. классы, содержащие только данные, но не обладающие поведением. Только в этом случае не всегда было понятно, какие методы делать в Info-классе, а какие в BLL. Например, если есть ProductCost и DeliveryCost, и нужно добавить вычисляемое свойство TotalCost, не понятно в какой слой его надо помещать, теоретически это нужно делать в BLL статическим методом static OrderManager.GetTotalSum(Order order) { return order.RegionSum + order.PersonSum; }, но это же криво выглядит. И в результате того, что некоторые функции определены в Model, а некоторые в BLL — логика приложения размазывается.
Причина номер 2: Model.Person и BLL.Person это абсолютно разные классы, т.е. фактически нарушается инкапсуляция, т.к. защищенные члены просто отсутствуют — в Model.Person они просто не нужны, т.к. он не может содержать логики, а BLL.Person не содержит состояния.
Причина номер 3: наследование (см. ниже)
После того, как я переписал свою часть с существующей тогда структуры на ту, которая есть сейчас, объем кода уменьшился раза в полтора-два, а его логичность и наглядность увеличилась.
A>>Не могу понять такой подход. Вопрос 4: Проясните пожалуйста как подобную вещь с наследованием (см. ниже) сделать на Domain-Driven Design?: IB>Зачем Register(), OnRegister(), GetInstance и Kind нужны в Person?
А как можно сделать иначе, если OnRegister для каждого типа Person может быть свой?
По идее можно вводить параллельную псевдо-иерархию (без наследования) вида:
static class BLL.Person,
static class BLL.ProfessortPerson
static class BLL.StudentPerson
Но как тогда вызывать виртуальный метод OnRegister?
С>1.Это создание прокси-классов, реализующих общий Facade-интерфейс. С>Так существует ProxyDAL, ProxyService. С>У меня примерно так: Клиент обращается к Agents, которые в зависимости от контекста выполнения приложения (клиент/сервер), инстанцируют тот или иной Singleton-Proxy (ProxyDAL, ProxyService). Какой прокси инстанцировать, задается в конфиге комбинацией IRemoteFacade+ProxyAssemblity+ProxyClass. С>Proxy в свою очередь, уже напрямую, обращаются к DAL или к сервисам. С>Может с небольшими кусочками примеров, чуть более ясно будет:
Эх... Задумываешься иногда, насколько все-таки разнообразный народ на белом свете живет.
Кто-то пишет хитрые прокси-классы. Кто-то, бизнес-логику на DataSet'ах и называет это best approach.
Другие глазом не моргнув из Business Entity обращаются к DAL'у. Четвертые пишут мегатурбофреймворки, один навороченнее другого. Пятые просто генерят мегабайты кода с помощью различных тулзов. Ну, а кто-то просто тупо копипастит.
Здравствуйте, Andrey Lastochkin, Вы писали:
AL>Фактически весь проект, со всеми слоями можно засунуть в одну сборку, В отдельных сборках только поместить расширения для системы — plugins.
Не исключено.
AL>Только нормально ли это если проект не маленький?
Ещё раз. Маленькость проекта не должна являться критерием разбиения проекта на сборки, т.к. здесь сложно выразить саму меру измерения. Какой ты хочешь получиь размер сборок? 128kb, 1M, 2Gb? Критерием может быть, например, деплоймент, или упомянутое Иваном желание получить гарантию независимости одного слоя от другого (т.к. это сложно получить с гарантнией другими способами), что-то ещё. Я, например, сталкивался с разделением сборок по тимам и даже девелоперам. Это всё имеет хоть какой-то смысл.
AL>Ну, в своем видении системы, я бы сделал это свойством определенно в одном классе Order, в этом случае, не приходилось бы думать содержит оно внутри логику для работы с БД или нет и где оно в OrderManager или в OrderInfo
Этот подход чреват боком. Ты получишь замкнутое в себе и, в результате, нерасширяемое решение. Решение с простой и отделённой от безнес логики объектной моделью позволит тебе повторно использовать объектную модель. Например, твою UI модель можно было во многом наследовать, а то и вообще заменить общей объектной моделью.
IT>>И в чём проблема? AL>Ну некрасиво как-то, нестройно
Видимо у нас разные критерии стройности. Мои критерии базируются из соображений сопровождаемости. Т.е. например, такую ерунду как obj.Save() vs. Save(obj) я легко приношу в жертву удобству сопровождения. Для меня качественная программа не та, которая правильно выглядит с точки зрения нравится/не нравится, а та, которая легко изменяется и при этом не теряет своих качеств к изменяемости. Такая своеобразная рекурсия. Если твой подход соответсвует этим критериям, то мы, как говоряю буржую, on the same page. Если нет, то скорее всего we are reading different books.
AL>>>Причина номер 3: наследование (см. ниже) IT>>Наследование вполне можно использовать в объектной модели. Тоже не пойму проблему. AL>Очень интересует, как можно метод сделать виртуальным (тот же Register)? Можно на примере кода, с которого я начинал тему.
AL>Я это обходил так, вводил параллельную иерархию и методы, работающие с самим экземпляром, делал нестатическими. Получалась параллельная иерархия вида AL>PersonManager, ProfessorPersonManager : PersonManager, StudentPersonManager : PersonManager. AL>И делал экземлярное поле private xxxPersonManager _info.
Я бы скорее всего завёл всего один manager.
AL>Вызывал методы примерно так: PersonManager.GetInstance(person).Register(). AL>В PersonManager.GetInstance в зависимости от типа person создавался соответствующий ему xxxPersonManager. Т.е. фактически, что можно было делать тремя классами приходилось делать шестью. И мне это очень не нравилось, как только я отошел от этой архитектуры, мне показалось что все стало на места.
Тут такое дело. Если у нас сильно разные объекты, то скорее всего нет смысла в наследовании, можно следать разные объекты. Если у нас сильно похожие объекты, то нет смысла в наследовании, можно сделать один объект.
AL>Не могу понять, почему бизнес логику и бизнес сущность нужно разделять? Для чего и в чем это помогает?
Всё просто. Это есть процесс отделения результата реализации чего-либо от самого процесса реализации. Бизнес логика весьма чувствительная к реализации вешь. Скажем так, это и есть сама реализация. Грубо говоря — BL есть хардкодинг бизнес правил. Бизнес сущность — это представление реализации, её следсвие, результат работы.
В результате, (простите за тафтологию) результат может быть использован независимо. Например, ГОСТ на подшибник, определяет сам подшибник, на не процесс его изготовления. Следовательно, такой подшибник может использоваться большинством производителей без привязки к тому, на каком станке был произведён данный подшибник. А раз нет привязки к станку, то есть возможность независимо развивать как сам станок, так и те процессы, которые используют результат работы данного станка.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
Помогите, пожалуйста, понять как решить проблему взаимодействия между DAL и BLL.
Я использую custom entities (т.е. не DataSet'ы, а свои классы, см. пример кода ниже). Если я выделяю DAL в отдельную сборку, я не могу поместить ссылку на BLL, т.к. circular reference. Поэтому для того, чтобы я мог получить доступ из DAL к объектам бизнес логики мне нужно еще создать IPerson (куда вытащить все property Person, которые используются из DAL) и сделать Factory для Person, чтобы в DAL можно было создавать экземпляр Person.
Поэтому получается, для одного класса Person, мне нужно сделать как минимум 4 класса: BLL.Person, IDAL.Person, Model.IPerson, DAL.Person. Что получается слишком громоздко. И тем более, если в BLL.Person я получаю список, мне нужно будет каждый IPerson кастить в Person, что не совсем удобно.
Недавно просматривал книгу Applying Domain-Driven Design and Patterns, и как я понял автор предлагает разделять классы Domain (состояние) и Business Logic (поведение). Мало того, говорит что классы Business Logic можно делать статическими, содержащими одни только методы, которые принимают экземпляр класса как аргумент, например static OrderManager.Save(Order order), а не Order.Save().
Вопрос 3: Правильно ли я думаю, что Business Entity это синоним Domain?
Не могу понять такой подход. Вопрос 4: Проясните пожалуйста как подобную вещь с наследованием (см. ниже) сделать на Domain-Driven Design?:
/// BLLenum PersonKind
{
Student,
Professor
}
abstract class Person
{
public Guid Id;
public string UserName;
public abstract PersonKind Kind { get; } // хранится в базе, при вытаскивании записи - создается нужный классpublic void Register()
{
IDAL.Save(this); // IDAL - для простоты. Объект реализующий IDAL.Person и содержащий методы DAL
OnRegister();
}
protected abstract OnRegister();
// вызывается из базыpublic static GetInstance(PersonKind kind)
{
if (kind == PersonKind.Professor) return new ProfessorPerson();
else return new StudentPerson();
}
}
class ProfessorPerson : Person
{
protected override PersonKind Kind { get { return PersonKind.Professor; } }
public string Subject; // некое дополнительное проперти только для Professor'а
// просто кастомная логика, показать, что ProfessorPerson и StudentPerson ведуть себя по-разному
// таких функций может быть много и у них логика будет сложнее.protected override void OnRegister
{
RegisterNewEmployee();
ScheduleManager.RecalculateSubject(Subject);
}
private void RegisterNewEmployee() { ... }
}
А почему бы не подсмотреть как это сделано в ORM средствах ((N)Hibernate, JDO)? Там люди уже продумали многие тонкие моменты.
Если нельзя использовать ORM, то лично я выбираю DAO + POJO (Data Objects). DAO состоит из интерфейса и реализации методов доступа к DB, и POJO класс+интерфейс, если мне нужно гарантировать, что класс не будет изменяться за пределами того места где он инициализируется (т.е. только accessors методы в интерфейсе). От приведения типа не спасет конечно, но его как раз можно отследить средствами IDE. Можно и без интерфейса обойтись.
AL>>Для написания еще одного DAL. IT>Для этого тоже не всегда обязательно выносить DAL в отдельную сборку. Как уже сказал Иван, при разделении на сборки нужно руководствоваться соображениями развёртывания.
Да, понимаю. Ну т.е. получается что BLL и DAL нужно объединить в одну сборку.
Фактически весь проект, со всеми слоями можно засунуть в одну сборку, В отдельных сборках только поместить расширения для системы — plugins. Только нормально ли это если проект не маленький?
AL>>И в результате того, что некоторые функции определены в Model, а некоторые в BLL — логика приложения размазывается. IT>То что размазывается — это конечно плохо. Но давай вот такой пример. Что если для вычсления total нужно сходит за дополнительными данными в БД. В этом случае где лучше размещать такую логику: только на клиенте (не получится), только в BLL (тоже не получится), в DAL (как к ней обратиться минуя BLL) или вообще в сохранённой процедуре? На практике получается, что бизнес-логика по-любому размазывается по всему приложению, по-этому бороться с этим не нужно. Нужно это возглавить и организовать.
Ну, в своем видении системы, я бы сделал это свойством определенно в одном классе Order, в этом случае, не приходилось бы думать содержит оно внутри логику для работы с БД или нет и где оно в OrderManager или в OrderInfo
AL>>Причина номер 2: Model.Person и BLL.Person это абсолютно разные классы, т.е. фактически нарушается инкапсуляция, т.к. защищенные члены просто отсутствуют — в Model.Person они просто не нужны, т.к. он не может содержать логики, а BLL.Person не содержит состояния. IT>И в чём проблема?
Ну некрасиво как-то, нестройно
AL>>Причина номер 3: наследование (см. ниже) IT>Наследование вполне можно использовать в объектной модели. Тоже не пойму проблему.
Очень интересует, как можно метод сделать виртуальным (тот же Register)? Можно на примере кода, с которого я начинал тему.
Я это обходил так, вводил параллельную иерархию и методы, работающие с самим экземпляром, делал нестатическими. Получалась параллельная иерархия вида
PersonManager, ProfessorPersonManager : PersonManager, StudentPersonManager : PersonManager.
И делал экземлярное поле private xxxPersonManager _info.
Вызывал методы примерно так: PersonManager.GetInstance(person).Register().
В PersonManager.GetInstance в зависимости от типа person создавался соответствующий ему xxxPersonManager. Т.е. фактически, что можно было делать тремя классами приходилось делать шестью. И мне это очень не нравилось, как только я отошел от этой архитектуры, мне показалось что все стало на места.
Не могу понять, почему бизнес логику и бизнес сущность нужно разделять? Для чего и в чем это помогает?
T>А почему бы не подсмотреть как это сделано в ORM средствах ((N)Hibernate, JDO)? Там люди уже продумали многие тонкие моменты. T>Если нельзя использовать ORM, то лично я выбираю DAO + POJO (Data Objects). T>DAO состоит из интерфейса и реализации методов доступа к DB
Как я понял это IDAL, о котором я говорил выше?
T> и POJO класс+интерфейс
Т.е. тот же BLL.Person и Model.IPerson?
T> если мне нужно гарантировать, что класс не будет изменяться за пределами того места где он инициализируется (т.е. только accessors методы в интерфейсе). От приведения типа не спасет конечно, но его как раз можно отследить средствами IDE. Можно и без интерфейса обойтись.
А, фабрику как делаешь в этом случае? Для каждого класса в конфиге прописываешь, или может быть пишешь некий алгоритм формирования имя типа и придерживаешься жесткого правила именования.
PS: я использую BLToolkit, но от DALа полностью не уйти, т.к. из хранимок возвращаются порой сложные структуры, которые без дополнительного маппинга не вернешь.
Еще, специально для интерфейсных нужд я выделяю часть DALUI который находится в DAL и вызывается напрямую из UI. Например, для получения постраничных выборок, сортировок, подсчета количества документов, возвращение малого количества полей из связанных сущностей, вместо создания множества непроинициализированных объектов.
Это либо конкретные UIEntity (стараюсь чтобы их было как можно меньше), либо DataTable/DataSet.
Здравствуйте, Andrey Lastochkin, Вы писали:
AL>Для написания еще одного DAL.
На практике такое случается крайне редко, обычно DAL просто рефакторят...
AL> Но в команде есть мнение — что это облегчит перенос системы на другие базы данных, теоретически, чтобы добиться этого нужно будет написать еще один DAL и изменить в config'е имя DAL сборки .
Сейчас опять в апломб сорвусь.. Работа с разными БД — это утопия, если поддерживать их в серьез.
Опять-таки, если вдруг (упаси байт) вы в это впряжетесь, то при использовании подхода N3 вынести DAL в отдельную сборку будет задачей на пол-дня, включая тестирование.
AL> Только в этом случае не всегда было понятно, какие методы делать в Info-классе, а какие в BLL.
Все очень просто. В "Info" классе вообще не должно быть методов.
AL> теоретически это нужно делать в BLL статическим методом static OrderManager.GetTotalSum(Order order) { return order.RegionSum + order.PersonSum; }, но это же криво выглядит.
Кто сказал?
AL>Причина номер 2: Model.Person и BLL.Person это абсолютно разные классы,
Угу.
AL> т.е. фактически нарушается инкапсуляция,
Нет, не нарушается. Что там инкапсулировать в данных? Вот логику имеет смысл но там она и так инкапсулирована в полный рост.
AL> т.к. защищенные члены просто отсутствуют — в Model.Person они просто не нужны, т.к. он не может содержать логики, а BLL.Person не содержит состояния.
Именно.
AL>После того, как я переписал свою часть с существующей тогда структуры на ту, которая есть сейчас, объем кода уменьшился раза в полтора-два, а его логичность и наглядность увеличилась.
Сомневаюсь, обычно получается наоборот.
AL>А как можно сделать иначе, если OnRegister для каждого типа Person может быть свой?
А перегрузка уже не работает?
AL>>Для написания еще одного DAL. IB>На практике такое случается крайне редко, обычно DAL просто рефакторят...
У нас в проекте есть несколько мест, где составляется динамический запрос и от него не уйти.
По идее можно наверное это засунуть в промежуточный XML, чтобы потом в хранимке его как-то хитро распарсить, но это весьма геморно.
AL>> Но в команде есть мнение — что это облегчит перенос системы на другие базы данных, теоретически, чтобы добиться этого нужно будет написать еще один DAL и изменить в config'е имя DAL сборки . IB>Сейчас опять в апломб сорвусь.. Работа с разными БД — это утопия, если поддерживать их в серьез. IB>Опять-таки, если вдруг (упаси байт) вы в это впряжетесь, то при использовании подхода N3 вынести DAL в отдельную сборку будет задачей на пол-дня, включая тестирование.
Не тянет на полдня, нужно сгенерить IDAL.Person, создать интерфейсы Model.IPerson и забабахать еще фабрику для создания Person.
AL>>А как можно сделать иначе, если OnRegister для каждого типа Person может быть свой? IB>А перегрузка уже не работает?
Я просто не понимаю как вы обычно реализовываете это, тем более если речь идет о статическом методе OnRegister.
Не поленись, дружище, покажи, плиз, то как бы ты это сделал на примере кода, который у меня был в начальном посте. Или дай ссылку, где можно посмотреть на такую реализацию.
PS: любые толковые ссылки относящиеся к этой теме приветствуются.
Здравствуйте, Andrey Lastochkin, Вы писали:
AL>Фактически весь проект, со всеми слоями можно засунуть в одну сборку, В отдельных сборках только поместить расширения для системы — plugins. Только нормально ли это если проект не маленький?
В принципе нормально. Но я в своих проектах обычно делю на сборки "вертикально", по компонентам. Например, отдельная сборка — работа с пользователями, включая DAL, BLL и BizEntity, отдельная сборка — продукты и их иерархия (эти две сборки ничего друг о друге не знают и полностью независимы), и отдельно сборка с заказами, где происходит весь workflow, последняя сборка пользуется предыдущими двумя.
Такое деление помогает себя контролировать и избегать лишних связей между компонентами.
AL>Ну, в своем видении системы, я бы сделал это свойством определенно в одном классе Order, в этом случае, не приходилось бы думать содержит оно внутри логику для работы с БД или нет и где оно в OrderManager или в OrderInfo
И Order оказался бы гвоздями прибитым к DAL, больше ты его нигде использовать не сможешь. Более того, при изменении контракта с DAL, тебе придется перетряхивать весь проект, на предмет — не сломалось ли что, поскольку ты не знаешь где именно были обращения к DAL.
AL>Очень интересует, как можно метод сделать виртуальным (тот же Register)? Можно на примере кода, с которого я начинал тему.
public static class PersonManager
{
public static void Register(ProfessorPerson person)
{
IDAL.Save(person);
// Логика специфичная для профессора
// ....
}
public static void Register(StudentPerson person)
{
IDAL.Save(person);
// Логика специфичная для студента
// ....
}
}
Или я не очень понимаю, чего ты хочешь добиться....
AL>Не могу понять, почему бизнес логику и бизнес сущность нужно разделять? Для чего и в чем это помогает?
Это помогает переиспользовать и бизнес логику, и сущности, и легко менять одно независимо от другого.
Можно покрутить их по отдельности, что-то переделать и приставить обратно, при этом есть уверенность, что нигде ничего не сломается. Есть такой термин "уменьшение связности", вот это вот оно.
Здравствуйте, alastochkin, Вы писали:
A>Я использую custom entities (т.е. не DataSet'ы, а свои классы, см. пример кода ниже). Если я выделяю DAL в отдельную сборку, я не могу поместить ссылку на BLL, т.к. circular reference. Поэтому для того, чтобы я мог получить доступ из DAL к объектам бизнес логики мне нужно еще создать IPerson (куда вытащить все property Person, которые используются из DAL) и сделать Factory для Person, чтобы в DAL можно было создавать экземпляр Person.
A>Поэтому получается, для одного класса Person, мне нужно сделать как минимум 4 класса: BLL.Person, IDAL.Person, Model.IPerson, DAL.Person. Что получается слишком громоздко. И тем более, если в BLL.Person я получаю список, мне нужно будет каждый IPerson кастить в Person, что не совсем удобно.
Просто вынеси Entity-классы в отдельную сборку и все будет хорошо.
IB>В принципе нормально. Но я в своих проектах обычно делю на сборки "вертикально", по компонентам. Например, отдельная сборка — работа с пользователями, включая DAL, BLL и BizEntity, отдельная сборка — продукты и их иерархия (эти две сборки ничего друг о друге не знают и полностью независимы), и отдельно сборка с заказами, где происходит весь workflow, последняя сборка пользуется предыдущими двумя. IB>Такое деление помогает себя контролировать и избегать лишних связей между компонентами.
Да, полностью согласен. У нас дополнительно к такому делению "вертикаль" разбита еще на сборки DAL и BLL, а отсюда рождается гемор.
Наверное, следует объединить, как ты предлагаешь.
AL>>Ну, в своем видении системы, я бы сделал это свойством определенно в одном классе Order, в этом случае, не приходилось бы думать содержит оно внутри логику для работы с БД или нет и где оно в OrderManager или в OrderInfo IB>И Order оказался бы гвоздями прибитым к DAL, больше ты его нигде использовать не сможешь. Более того, при изменении контракта с DAL, тебе придется перетряхивать весь проект, на предмет — не сломалось ли что, поскольку ты не знаешь где именно были обращения к DAL.
Обращение к DAL.Order могут быть только из BLL.Order, больше нигде. По идее DAL.Order должен быть protected class и содержатся в BLL.Order, чтобы другие объекты не могли получить к нему доступ.
Можешь привести пример, где нужен не прибитый гвоздями объект Order. Просто я пока с этим не сталкивался, но учитывать нужно.
AL>>Очень интересует, как можно метод сделать виртуальным (тот же Register)? Можно на примере кода, с которого я начинал тему. IB>
IB>public static class PersonManager
IB>{
IB> public static void Register(ProfessorPerson person)
IB> {
IB> IDAL.Save(person);
IB> // Логика специфичная для профессора
IB> // ....
IB> }
IB> public static void Register(StudentPerson person)
IB> {
IB> IDAL.Save(person);
IB> // Логика специфичная для студента
IB> // ....
IB> }
IB>}
IB>
IB>Или я не очень понимаю, чего ты хочешь добиться....
Вот у тебя тут получилось, что структура вышла плоская. Т.е. не смотря на то, что объекты разные, все лежит в одном классе на одном уровне. Теоретически, я не могу создать новый класс BabyPerson без перекомпиляции всей этой статической бизнес логики. И если бы лэто огически был наследник от, скажем, ProfessorPerson — тогда пришлось бы вызывать родительский метод вручную, хотя в C# уже есть на это дело инструменты ООП — virtual и override.
Помимо этого, что очень важно, тут используется связывание на этапе компиляции, т.е. такая вещь работать не будет:
Person person = PersonManager.GetById(id)
PersonManager.Register(person)
В моем случае это выглядело бы так
Person person = PersonManager.GetById(id)
person.Register()
AL>>Не могу понять, почему бизнес логику и бизнес сущность нужно разделять? Для чего и в чем это помогает? IB>Это помогает переиспользовать и бизнес логику, и сущности, и легко менять одно независимо от другого. IB>Можно покрутить их по отдельности, что-то переделать и приставить обратно, при этом есть уверенность, что нигде ничего не сломается. Есть такой термин "уменьшение связности", вот это вот оно.
Примерно подсознательно представляю о чем ты говоришь, но формализовать не могу. Можешь привести пример какой-нибудь?
И чем ООП подход в этом случае не устроит.
A>>Поэтому получается, для одного класса Person, мне нужно сделать как минимум 4 класса: BLL.Person, IDAL.Person, Model.IPerson, DAL.Person. Что получается слишком громоздко. И тем более, если в BLL.Person я получаю список, мне нужно будет каждый IPerson кастить в Person, что не совсем удобно. L>Просто вынеси Entity-классы в отдельную сборку и все будет хорошо.
А как же наследование, дружище?
Здравствуйте, IT, Вы писали:
IT>На практике получается, что бизнес-логика по-любому размазывается по всему приложению, по-этому бороться с этим не нужно. Нужно это возглавить и организовать.
Help will always be given at Hogwarts to those who ask for it.
Здравствуйте, Andrey Lastochkin, Вы писали:
T>>А почему бы не подсмотреть как это сделано в ORM средствах ((N)Hibernate, JDO)? Там люди уже продумали многие тонкие моменты. T>>Если нельзя использовать ORM, то лично я выбираю DAO + POJO (Data Objects). T>>DAO состоит из интерфейса и реализации методов доступа к DB AL>Как я понял это IDAL, о котором я говорил выше?
T>> и POJO класс+интерфейс AL>Т.е. тот же BLL.Person и Model.IPerson?
T>> если мне нужно гарантировать, что класс не будет изменяться за пределами того места где он инициализируется (т.е. только accessors методы в интерфейсе). От приведения типа не спасет конечно, но его как раз можно отследить средствами IDE. Можно и без интерфейса обойтись. AL>А, фабрику как делаешь в этом случае? Для каждого класса в конфиге прописываешь, или может быть пишешь некий алгоритм формирования имя типа и придерживаешься жесткого правила именования.
AL>PS: я использую BLToolkit, но от DALа полностью не уйти, т.к. из хранимок возвращаются порой сложные структуры, которые без дополнительного маппинга не вернешь. AL>Еще, специально для интерфейсных нужд я выделяю часть DALUI который находится в DAL и вызывается напрямую из UI. Например, для получения постраничных выборок, сортировок, подсчета количества документов, возвращение малого количества полей из связанных сущностей, вместо создания множества непроинициализированных объектов. AL>Это либо конкретные UIEntity (стараюсь чтобы их было как можно меньше), либо DataTable/DataSet.
DAO паттерн вообще platform-agnostic. Недостаток — достаточно много кода. Но если использовать средства автогенерации и динамические прокси для интерфейсов, то можно существенно себе облегчить жизнь. Где-то так.
Здравствуйте, IB, Вы писали:
L>>Просто вынеси Entity-классы в отдельную сборку и все будет хорошо. IB>Не будет, он же хочет из Entity пользоваться DAL.
Тады ой.
С>2. Еще вариант по сабжу, да простит меня IT, и другие нелюбители DTO , но тут то он как раз ПОПУТНО решает данную проблему, т.е. BO мапится на DTO, с которым в свою очередь и работает DAL. Но подчеркиваю (во избежании флеймогона), что это всего лишь попутная задача решаемая DTO.
Простите друзья, а чем DTO отличается от Business Entity? Как я понимаю и то и другое dumb. Т.е. объект, который не содержит ничего кроме данных.
Так в чем между ними разница? Подозреваю что для одной BE много разных DTO?
AL>Простите друзья, а чем DTO отличается от Business Entity? Как я понимаю и то и другое dumb. Т.е. объект, который не содержит ничего кроме данных. AL>Так в чем между ними разница? Подозреваю что для одной BE много разных DTO? AL>Андрей.
УУУУ... лучше не обсуждать . Здесь: DTO внутри BusinessObject
Здравствуйте, снежок, Вы писали:
С>Выносят, выносят..., и обычно это связано с нежеланием и не примиримостью архитектора развертывать код с обращением к БД на стороне клиента. Мотивируется обычно это соображениями безопасности, типа "нечего клиенту знать об объектах БД".
Ну, я то считал, что речь идет исключительно о серверном или исключительно о клиентском коде.
Серверный и клиентский DAL и BLL все равно сильно отличаются это и физически и логически разные компоненты. Что это за приложение такое, где DAL и BLL у клиента и сервера может быть общим и его надо искуственно выдирать?
С>Теперь о решении проблемы circular reference как таковой:
Сходу не разобрался, но слово Singletone напрягло, судя по всему дизайн довольно связный.. =) Потом еще помедитирую.
Здравствуйте, Andrey Lastochkin, Вы писали:
AL>Обращение к DAL.Order могут быть только из BLL.Order, больше нигде.
А сопутствующие классы, какие-нибудь хитрые описания заказа? У них свой отдельный DAL?
AL>Можешь привести пример, где нужен не прибитый гвоздями объект Order. Просто я пока с этим не сталкивался, но учитывать нужно.
Ну например клиенты захотят отслеживать изменения заказа через web-service. Ну или вы какого-нибудь удаленного SmartClient-а создать захотите.. Даже проще, просто через слои приложения сущности передавать.
AL>Вот у тебя тут получилось, что структура вышла плоская. Т.е. не смотря на то, что объекты разные, все лежит в одном классе на одном уровне. Теоретически, я не могу создать новый класс BabyPerson без перекомпиляции всей этой статической бизнес логики. И если бы лэто огически был наследник от, скажем, ProfessorPerson — тогда пришлось бы вызывать родительский метод вручную, хотя в C# уже есть на это дело инструменты ООП — virtual и override.
Тут выбирай... Но осторожно, но выбирай. Если у тебя добавление BabyPerson случается раз в пятилетку, то можно обойтисьи перекомпиляцией, все равно когда дело до добавления дойдет ты и не вспомнишь какую хитрую механику настраивал для удобства. А если задача регулярная (что сильно реже), тогда городи параллельную иерархию или еще как приседай.
AL>И чем ООП подход в этом случае не устроит.
Это и есть ООП. Или ты про наследование? Наследование вообще штука опасная, может боком выйти..
IB>Сходу не разобрался, но слово Singletone напрягло, судя по всему дизайн довольно связный.. =)
Singletone — это прокся у агента, агент ее инициализирует один раз.
Архитектура как раз получается гибкой, повыкидывать и переконфигурить можно практически всё. Единственное — вся работа происходит через прокси-классы, реализующие фасад-ин интерфейсы. Вообщем цепочки такие могут быть: агент->прокси->сервис->агент->прокси->DAL или агент->прокси->сервис->прокси->DAL или агент->прокси->DAL (в случае клиент-сервер).
AL>>Обращение к DAL.Order могут быть только из BLL.Order, больше нигде. IB>А сопутствующие классы, какие-нибудь хитрые описания заказа? У них свой отдельный DAL?
Да, свой отдельный дал, свой префикс и таблицы. Т.к. фактически это другой объект. Например, нам нужен класс который содержит расширенную информацию по Order'у хранит некую логику по созданию репорта, создадим в этом случае объект OrderDescriber, сам Order трогать не будем, т.к. эта функциональность непосредственно к самому объекта не относится.
AL>>Можешь привести пример, где нужен не прибитый гвоздями объект Order. Просто я пока с этим не сталкивался, но учитывать нужно. IB>Ну например клиенты захотят отслеживать изменения заказа через web-service. Ну или вы какого-нибудь удаленного SmartClient-а создать захотите.. Даже проще, просто через слои приложения сущности передавать.
А что мешает передать этот BLL.Order через web-service?
На счет между слоями вот ХЗ, не сталкивался пока, только BLL <> DAL.
Мне видится тут два варианта: 1. создавать объект-фасад и делать мэппинг 2. интерфейсы вытаскивать.
Зато в этом случае ты можешь опубликовать не весь объект, а только его часть, помимо этого, этим объектом можно будет загородить внутреннее хранение, именование, версионирование и даже логику.
AL>>И чем ООП подход в этом случае не устроит. IB>Это и есть ООП. Или ты про наследование? Наследование вообще штука опасная, может боком выйти..
В BCL .NET'а практически все объекты хоть как-то участвуют в наследовании (помимо object ), и это же очень облегчает их понимание! На счет опасности, не знаю, смотря что под этим понимать.
Зато получается наглядно при меньшем объеме кода.
AL>>Простите друзья, а чем DTO отличается от Business Entity? Как я понимаю и то и другое dumb. Т.е. объект, который не содержит ничего кроме данных. AL>>Так в чем между ними разница? Подозреваю что для одной BE много разных DTO? AL>>Андрей. С>УУУУ... лучше не обсуждать . Здесь: DTO внутри BusinessObject
Да, читал я эту увлекательную беседу. И так не понял в итоге чем Business Entity отличается от DTO. Оба dumb, в обоих хранится одна и то же — схема.
Понял из переписки что Игорь ярый противник DTO, но за Business Entity. А чем отличаются я так и не понял.
AL>Да, читал я эту увлекательную беседу. И так не понял в итоге чем Business Entity отличается от DTO. Оба dumb, в обоих хранится одна и то же — схема. AL>Понял из переписки что Игорь ярый противник DTO, но за Business Entity. А чем отличаются я так и не понял.
Вообщем, попробую..., наверное, основной смысл который можно вынести из этого и не только этого обсуждения, это то, что DTO нужен для адаптации данных BO для передачи сторонним сервисам (свои обычно оперируют напрямую BO) и сокращения объема передаваемых данных. В этом случае DTO — это решение "влоб", плоский код, простой и понятный, не требующий разработки специфичной сериализации или схем сериализации. Обойтись без DTO конечно же можно (в .net благодаря гибким механизмам сериализации), и чаще всего это более правильно, но иногда он (DTO) помогает избежать некоторого тупика (идеалогического если хотите) и не выйти за пределы времени контрольных точек проекта.
Здравствуйте, Andrey Lastochkin, Вы писали:
AL>Да, читал я эту увлекательную беседу. И так не понял в итоге чем Business Entity отличается от DTO. Оба dumb, в обоих хранится одна и то же — схема. AL>Понял из переписки что Игорь ярый противник DTO, но за Business Entity. А чем отличаются я так и не понял.
Проблема в том, что ты ищешь различия, а надо искать сходства. По большому счёту, BE это теже DTO, только без догм. Т.е. если часть логики может содержаться в BE, то нет никаих проблем.
... << RSDN@Home 1.2.0 alpha rev. 0>>
Если нам не помогут, то мы тоже никого не пощадим.
Здравствуйте, Andrey Lastochkin, Вы писали:
AL>Да, свой отдельный дал, свой префикс и таблицы. Т.к. фактически это другой объект. Например, нам нужен класс который содержит расширенную информацию по Order'у хранит некую логику по созданию репорта, создадим в этом случае объект OrderDescriber, сам Order трогать не будем, т.к. эта функциональность непосредственно к самому объекта не относится.
А если из соображений эффективности нужно делать джойны этих объектов на уровне БД, а не в выше?
AL>А что мешает передать этот BLL.Order через web-service?
И как ты его на клиенте использовать будешь, все же в методах...
AL>Мне видится тут два варианта: 1. создавать объект-фасад и делать мэппинг 2. интерфейсы вытаскивать. AL>Зато в этом случае ты можешь опубликовать не весь объект, а только его часть, помимо этого, этим объектом можно будет загородить внутреннее хранение, именование, версионирование и даже логику.
То есть сделать DTO? А если в разных случаях потребуются разные части объекта — разные DTO? Ок, выделяем состояние объекта в один большой DTO и передаем его куда надо (по сути паттерн memento). Это приводит к тому, что у нас есть BL-class и State-class(наш DTO).
Теперь вся разница между твоим и моим подходом заключается в том, что у тебя BL-class обладает состоянием, а у меня нет. Но зачем возиться с состоянием BL-class-а, когда ничего не стоит этого не делать?
AL>В BCL .NET'а практически все объекты хоть как-то участвуют в наследовании (помимо object ), и это же очень облегчает их понимание!
Ага, а ты обратил внимание, что подавляющее большинство из них Sealed?
AL>На счет опасности, не знаю, смотря что под этим понимать.
под этим понимать сильную связность, что приводит к высокой вероятности что-то сломать при изменении объекта, причем там, где совсем не ожидаешь..
"alastochkin" <63963@users.rsdn.ru> wrote in message news:2386481@news.rsdn.ru... > Добрый день, друзья. > > Помогите, пожалуйста, понять как решить проблему взаимодействия между DAL и BLL. > > Я использую custom entities (т.е. не DataSet'ы, а свои классы, см. пример кода ниже). Если я выделяю DAL в отдельную сборку, я не могу поместить ссылку на BLL, т.к. circular reference. Поэтому для того, чтобы я мог получить доступ из DAL к объектам бизнес логики мне нужно еще создать IPerson (куда вытащить все property Person, которые используются из DAL) и сделать Factory для Person, чтобы в DAL можно было создавать экземпляр Person.
Скажите, а зачем Вам из BLL ссылаться на DAL? отделите интерфейс маппинга от реализации.
BLL: Person
IDAL: IPersonMapper(Load, Save)
DAL: DbPersonMapper : IPersonMapper(Load, Save)