Есть такой простейший пример. Пусть предметная область состоит из двух сущностей — Организация и Департамент, связанных между собой отношением "один-ко-многим" (организация состоит из нескольких департаментов). Допустим нам надо реализовать сервисный метод, возвращающий количество департаментов для заданной организации.
Как это сделано сейчас:
Все обьекты предметной области описывается двумя классами (для примера возьмем Organization)
1. Обьект, описывающий данные (некий аналог фаулеровского DataTransferObject). Основные свойства подобных обектов:
— хранит связи на своих ближайших соседей по обьектному графу в виде идентификаторов (но не хранит сами связанные обьекты)
— не имеет никаких связей с внешним миром (другие слои приложения) и им можно оперировать вне сервисного контекста (в частности его можно передавать клиенту)
class OrganizationData
{
public int Id;
public int Name;
}
2. Бизнес-обьект. Класс предметной области, описывающий структуру и поведение обьекта в доменной области. Этим классом можно оперировать только внутри сервисного контекста (секьюрити, коннект к БД, транзакции).
class Organization
{
public Organization(int id)
{
this.id = id;
}
public Organization(OrganizationData data)
: this(data.Id)
{
this.data = data;
}
public OrganizationData Data
{
get
{
if(data == null)
{
// Load data from db here...
data = mapper.GetOrganization(id);
}
return data;
}
}
public DepartmentCollection Departments
{
get
{
if(departments == null)
{
// Load departments from db here
departments = mapper.GetDepartments(id);
}
return departments;
}
}
public void Delete()
{
mapper.Delete(id);
}
public void Create()
{
mapper.Create(Data);
}
private int id;
private OrganizationData data;
private DepartmentCollection departments;
private IDbMapper mapper;
}
Обращаю внимание, что бизнес обьект общается с БД посредством отдельного интерфейса IDbMapper, реализация которого находится в слое доступа к данным. Также важно отметить, что в реализации применяется принцип LazyLoad, то есть например для того чтобы удалить Организацию нам не придется загружать данные о ней, достаточно только знать его идентификатор:
new Organization(5).Delete();
Кроме всего прочего, при необходимости обьявляются специализированные классы коллекций типа:
class DepartmentCollection : Collection<Department>{}
Таким образом, итоговый код получающий количестиво департаментов будет выглядеть примерно так:
return new Organization(5).Departments.Count;
А теперь вопросы:
1. Критика?
2. Что смущает меня в этой архитектуре, так это наличие связи типа
Organization -> IDbMapper, пусть и максимально ослабленной посредством интерфейса. В идеале хотелось бы чтобы обьект предметной области
Organization не имел методов типа
Delete и вообще не занимался (даже опосредованно) общением с БД. Один из вариантов (описанных у того же Фаулера например) это вынести все мапперы уровнем выше (в сервисный метод), то есть иметь нечто вроде:
IDbMapper mapper = ...
return mapper.GetDepartments(5).Count;
Но это, по-моему, еще хуже, поскольку стремительно приближает решение к структурному программированию и гораздо менее наглядно, особенно если логика сервисного метода потребует чего-то большего, нежели просто вычисления количества элементов в коллекции. Что делать?