Мартином Фаулером в его классическом труде "Шаблоны корпоративных приложений" (Patterns of enterprise application architecture) выделено несколько подходов к организации бизнес-логики.
Если выбор между тем какой шаблон - транзакционный сценарий, табличный модуль или доменную модель - использовать как правило не вызывает проблем, т.к. критерии их применимости четко описаны в первоисточнике, то ответ на вопрос использовать или нет анемичную модель предметной области не так очевиден. С одной стороны теоретики объектно-ориентированного подхода считают данный шаблон "антипаттерном", с другой стороны он завоевал определенную популярность в практике разработки корпоративных приложений, что свидетельствует о наличии некоторых преимуществ. Давайте постараемся разобраться в данном вопросе.
Прежде чем рассуждать о достоинствах и недостатках того или иного варианта, необходимо взглянуть на шаблон в целом. Как было сказано выше, суть данного паттерна в том, что создается сеть связанных объектов, каждый из которых представляет собой модель одного объекта реального мира. При этом данное решение обладает рядом достоинств, упрощающих реализацию сложной логики: программисту доступны все принципы парадигмы ООП, такие как инкапсуляция, наследование и полиморфизм. При этом структура классов отражает структуру реального мира, что упрощает взаимодействие по линии "программист - аналитик". Люди в команде могут общаться на одном языке. Для реализации модели можно применять хорошо знакомые шаблоны проектирования. Так же данный паттерн позволяет реализовать прозрачное для слоя бизнес-логики взаимодействие с хранилищем данным - объекты, моделирующие предметную область ничего не знают о том, что на самом деле они сохраняются, например в базе данных.
Важным преимуществом так же является тот факт, что Модель предметной области позволяет повторно использовать бизнес-логику приложения. В отличие от шаблонов Транзакционный сценарий и Табличный модуль, бизнес-логика не привязана к запросам от слоя представления. Соответственно над ней можно стоить другие варианты слоя представления, например RESTful- или SOAP-сервисы.
К сожалению, за любое достоинство следует платить. В данном случае плата заключается в следующем. Во-первых, повышаются требования к квалификации разработчиков, проектирующих, реализующих и поддерживающих модель предметной области: необходимо уметь строить более сложные абстракции нежели при применении других решений. Во-вторых, повышаются требования к используемым технологиям. В частности нужно иметь довольно мощный слой доступа к данным, который реализовывал бы объектно-реляционное отображение. Реализация шаблона Модель предметной области как правило требует применение мощного ORM-фреймворка, такого как Hibernate, TopLink, EclipseLink или OpenJPA, что с одной стороны требует разработчиков более высокой квалификации, а с другой - снижает производительность разрабатываемой системы и повышает ее хрупкость: любым неосторожным действием, например изменением одной строчки в описании объектно-реляционного отображения можно замедлить работу системы в десятки раз.
Чем анатомически отличаются два подхода к построению модели предметной области: насыщенный и анемичный? При насыщенном подходе бизнес-логика, т.е. поведение системы реализуется внутри объектов предметной области. Данный подход не исключает вынесение части поведения в служебные классы, такие как *Service, *Manager, *Helper, но это оправдано только, когда логика затрагивает несколько доменных объектов и не понятно к какому именно она относится. Примером может являться следующая небольшая часть модели предметной области "Телеком". Пользователю нужно подключить себе услугу. Услуга добавляется в рамках заказа. При этом необходимо проверить, что есть организация-провайдер, которая может предоставить услугу с заданным набором параметров. В случае насыщенной доменной модели логика добавления услуги инкапсулируется в методе addNewService(Service service) класса ServiceOrder:
Анемичная модель предметной области устроена иначе. Классы объектов предметной области лишены поведения. Они имеют только конструкторы и методы доступа к данным. Единственное, что они реализуют - это отношения с другими объектами. Все поведение системы выносится в слой сервисов, реализованный поверх слоя модели предметной области. Продемонстрируем данный подход на том же самом примере, однако теперь класс ServiceOrder будет иметь только методы, осуществляющие доступ к данным. Определение того, имеется ли подходящий провайдер услуги и ее добавление в заказ будет осуществляться в ServiceOrderManager (согласитесь, что ServiceOrderService звучит как-то странно):
В сообществе разработчиков распространены два основных соображения против применения шаблона Анемичная модель предметной области:
Давайте рассмотрим данные соображения подробнее. Анемичная модель предметной области противоречит принципам ООП. В каком-то смысле данное соображение справедливо, т.к. объекты предметной области не имеют поведения. Однако доступ ко внутреннему состоянию объектов можно инкапсулировать за методами данного объекта. Продемонстрируем данную идею на нашем примере с добавлением сервиса в заказа: в класс ServiceOrder добавляется метод addService(), при этом метод setServices() делаем приватным:
Класс ServiceOrderManagerImpl будет осуществлять проверку возможности добавления заказа и вызывать метод addService() класса ServiceOrder:
Проблема заключается в том, что вынесение поведения в другие классы требует чтобы такие методы были публичными. Это существенно ухудшает инкапсуляцию. Любой нерадивый разработчик сможет обратиться к свойству объекта, минуя методы соответствующего сервиса, например добавить сервис в заказ без проверки доступности провайдера.
Решением может быть инкапсулция всей бизнес-логики, например с помощью шаблона POJO-фасад. При этом необходимо обеспечить или неизменяемость передаваемого во вне слоя бизнес-логики объектов, или хотя бы их отцепленность от слоя хранения данных, что не позволит сохранять изменения таких объектов, минуя слой бизнес-логики.
Другие принципы ООП, такие как наследование и полиморфизм, остаются доступными и при использовании анемичной модели предметной области. Большинство современных ORM-фреймворков допускают отображение иерархии классов на базу данных. Например, при построении модели финансового приложения следует учесть различные стратегии расчета лимита овердрафта. Предположим есть две стратегии: нет овердрафта и есть овердрафт с ограниченным лимитом. Построим следующую иерархию классов:
При этом объект класса Account - счет - будет иметь ссылку на конкретную реализацию стратегии расчета овердрафта, применимую для данного счета:
В некотором AccountService мы теперь можем использовать полиморфную реализацию стратегии расчета овердрафта:
Теряются все преимущества, которые дает шаблон Модель предметной области. Как мы рассмотрели выше, преимущества, которые дает моделирование системы в ООП-стиле при использовании анемичной модели предметной области нам по прежнему доступны. Возможно, что получить данные преимущества можно только при грамотной проработке архитектуры системы, в частности - интерфейсов между слоями, но это - соответствующая плата за более простую модель. Способность объектов прозрачно сохраняться в долговременной памяти так же не теряется. Как раньше объект сохранялся соответствующей командой в репозитории (Repository) или участке работы (Unit of Work), так и продолжает сохранятся. Синхронизация изменений внутреннего состояния объекта с хранилищем данных не зависит от того, как именно выполняется данное изменение - методом внутри класса объекта или методом сервиса.
Теперь попытаемся найти ответ на вопрос, почему шаблон Анемичная модель предметной области так популярен. На мой взгляд, основная причина кроется в том, что данный шаблон легче реализуется с помощью современных технологий по сравнению с насыщенной моделью. Наиболее распространенным способом структурирования исходного кода сегодня является шаблон Внедрение зависимостей. При этом в приложении четко прослеживается два источника объектов:
При реализации шаблона Насыщенная модель предметной области возникает проблема связывания объектов, построенных этими двумя способами, в единый граф. Если вернуться к примеру насыщенной модели предметной области, описанному выше, то видно, что в объект класса ServiceOrder нужно каким-то образом передать объект класса, реализующего интерфейс ServiceProviderRepository. Не все ORM-фреймворки позволяют инъектировать в объекты при их построении сторонние зависимости, реализуемые IoC-контейнером. Приходится использовать подходы, обладающие рядом недостатков.
Помимо популярности, вызванной имеющейся инфраструктурой, шаблон Анемичная модель предметной области имеет и ряд собственных преимуществ. Давайте рассмотрим их подробнее.
В заключение стоит сказать о том, что последнее дело как в ведении дискуссии, так и в принятии решения - слепое следование мнению тех или иных авторитетов. Всегда стоит помнить, что в науке существует такое понятие, как плюрализм мнений. Есть разные научные школы, придерживающиеся тех или иных подходов. Вполне естественна конкуренция между такими школами. Любые слова того или иного "гуру" могут быть вызваны конъюнктурной составляющей. Не нужно творить себе кумиров, чтобы потом не пришлось в них разочаровываться. Всегда следует думать своей головой и выбирать решение, исходя из собственного опыта и приоритетов прежде всего своего проекта.
Понравилось сообщение - подпишитесь на блог и Twitter
- Транзакционный сценарий - бизнес-логика разбивается на процедуры, каждая из которых соответствует конкретному запросу, поступающему от слоя представления.
- Табличный модуль - бизнес-логика как и в предыдущем варианте описывается в процедурном стиле, однако манипуляции с каждой таблицей выносятся в отдельный класс, что делает код более структурированным.
- Модель предметной области - самый сложный для реализации подход, однако имеющий ряд преимуществ при описании сложной логики. Суть заключается в том, что выделяются объекты, соответствующие объектам предметной области. Описываются отношения между данными объектами, соответствующие отношениям между объектами реального мира. При этом решение технологических вопросов, таких как хранение, безопасность, управление транзакциями как правило выносится за пределы слоя бизнес-логики.
Выделяют два варианта данного подхода:
- Насыщенная модель предметной области - данные и поведение инкапсулируются внутри объектов предметной области.
- Анемичная модель предметной области - в объектах предметной области инкапсулируются только данные, поведение же выносится в слой сервисов, расположенный поверх слоя предметной области.
- Насыщенная модель предметной области - данные и поведение инкапсулируются внутри объектов предметной области.
Если выбор между тем какой шаблон - транзакционный сценарий, табличный модуль или доменную модель - использовать как правило не вызывает проблем, т.к. критерии их применимости четко описаны в первоисточнике, то ответ на вопрос использовать или нет анемичную модель предметной области не так очевиден. С одной стороны теоретики объектно-ориентированного подхода считают данный шаблон "антипаттерном", с другой стороны он завоевал определенную популярность в практике разработки корпоративных приложений, что свидетельствует о наличии некоторых преимуществ. Давайте постараемся разобраться в данном вопросе.
Шаблон Модель предметной области
Прежде чем рассуждать о достоинствах и недостатках того или иного варианта, необходимо взглянуть на шаблон в целом. Как было сказано выше, суть данного паттерна в том, что создается сеть связанных объектов, каждый из которых представляет собой модель одного объекта реального мира. При этом данное решение обладает рядом достоинств, упрощающих реализацию сложной логики: программисту доступны все принципы парадигмы ООП, такие как инкапсуляция, наследование и полиморфизм. При этом структура классов отражает структуру реального мира, что упрощает взаимодействие по линии "программист - аналитик". Люди в команде могут общаться на одном языке. Для реализации модели можно применять хорошо знакомые шаблоны проектирования. Так же данный паттерн позволяет реализовать прозрачное для слоя бизнес-логики взаимодействие с хранилищем данным - объекты, моделирующие предметную область ничего не знают о том, что на самом деле они сохраняются, например в базе данных.
Важным преимуществом так же является тот факт, что Модель предметной области позволяет повторно использовать бизнес-логику приложения. В отличие от шаблонов Транзакционный сценарий и Табличный модуль, бизнес-логика не привязана к запросам от слоя представления. Соответственно над ней можно стоить другие варианты слоя представления, например RESTful- или SOAP-сервисы.
К сожалению, за любое достоинство следует платить. В данном случае плата заключается в следующем. Во-первых, повышаются требования к квалификации разработчиков, проектирующих, реализующих и поддерживающих модель предметной области: необходимо уметь строить более сложные абстракции нежели при применении других решений. Во-вторых, повышаются требования к используемым технологиям. В частности нужно иметь довольно мощный слой доступа к данным, который реализовывал бы объектно-реляционное отображение. Реализация шаблона Модель предметной области как правило требует применение мощного ORM-фреймворка, такого как Hibernate, TopLink, EclipseLink или OpenJPA, что с одной стороны требует разработчиков более высокой квалификации, а с другой - снижает производительность разрабатываемой системы и повышает ее хрупкость: любым неосторожным действием, например изменением одной строчки в описании объектно-реляционного отображения можно замедлить работу системы в десятки раз.
Анатомия насыщенной и анемичной моделей
Чем анатомически отличаются два подхода к построению модели предметной области: насыщенный и анемичный? При насыщенном подходе бизнес-логика, т.е. поведение системы реализуется внутри объектов предметной области. Данный подход не исключает вынесение части поведения в служебные классы, такие как *Service, *Manager, *Helper, но это оправдано только, когда логика затрагивает несколько доменных объектов и не понятно к какому именно она относится. Примером может являться следующая небольшая часть модели предметной области "Телеком". Пользователю нужно подключить себе услугу. Услуга добавляется в рамках заказа. При этом необходимо проверить, что есть организация-провайдер, которая может предоставить услугу с заданным набором параметров. В случае насыщенной доменной модели логика добавления услуги инкапсулируется в методе addNewService(Service service) класса ServiceOrder:
public class ServiceOrder {
private List<Service> services;
private void addService(Service service) {
if (services == null)
services = new ArrayList<Service>();
services.add(service);
}
public void addNewService(Service service) throws NotFoundValidProviderException {
ServiceProviderRepository providerRepo = ...;
if (providerRepo.hasAvailableProvider(service.getProp1(), service.getProp2(), ...)) {
addService(service);
}
else {
throw new NotFoundValidProviderException(service);
}
}
}
private List<Service> services;
private void addService(Service service) {
if (services == null)
services = new ArrayList<Service>();
services.add(service);
}
public void addNewService(Service service) throws NotFoundValidProviderException {
ServiceProviderRepository providerRepo = ...;
if (providerRepo.hasAvailableProvider(service.getProp1(), service.getProp2(), ...)) {
addService(service);
}
else {
throw new NotFoundValidProviderException(service);
}
}
}
Анемичная модель предметной области устроена иначе. Классы объектов предметной области лишены поведения. Они имеют только конструкторы и методы доступа к данным. Единственное, что они реализуют - это отношения с другими объектами. Все поведение системы выносится в слой сервисов, реализованный поверх слоя модели предметной области. Продемонстрируем данный подход на том же самом примере, однако теперь класс ServiceOrder будет иметь только методы, осуществляющие доступ к данным. Определение того, имеется ли подходящий провайдер услуги и ее добавление в заказ будет осуществляться в ServiceOrderManager (согласитесь, что ServiceOrderService звучит как-то странно):
public class ServiceOrder {
private List<Service> services;
public List<Service> getServices() {
return services;
}
public void setServices(List<Service> services) {
this.services = services;
}
}
private List<Service> services;
public List<Service> getServices() {
return services;
}
public void setServices(List<Service> services) {
this.services = services;
}
}
public class ServiceOrderManagerImpl implements ServiceOrderManager {
private ServiceProviderRepository providerRepo;
public ServiceProviderRepository getProviderRepo() {
return providerRepo;
}
public void setProviderRepo(ServiceProviderRepository providerRepo) {
this.providerRepo = providerRepo;
}
public void addNewService(ServiceOrder order, Service service) throws ProviderNotFoundException {
if (providerRepo.hasAvailableProvider(service.getParam1(), service.getParam2(), ...)) {
if (order.getServices() == null)
order.setServices(new ArrayList<Services>());
order.getServices().add(service);
}
else {
throw new ProviderNotFoundException(service);
}
}
}
private ServiceProviderRepository providerRepo;
public ServiceProviderRepository getProviderRepo() {
return providerRepo;
}
public void setProviderRepo(ServiceProviderRepository providerRepo) {
this.providerRepo = providerRepo;
}
public void addNewService(ServiceOrder order, Service service) throws ProviderNotFoundException {
if (providerRepo.hasAvailableProvider(service.getParam1(), service.getParam2(), ...)) {
if (order.getServices() == null)
order.setServices(new ArrayList<Services>());
order.getServices().add(service);
}
else {
throw new ProviderNotFoundException(service);
}
}
}
Критика Анемичной модели предметной области
В сообществе разработчиков распространены два основных соображения против применения шаблона Анемичная модель предметной области:
- При использовании анемичной модели предметной области мы отклоняемся от принципов ООП. Суть данного критического замечания сводится к тому, что т.к. объекты предметной области в случае анемичной доменной модели не имеют поведения, то мы вступаем в противоречие с базовой идеей ООП - иметь данные и методы их обработки в одном месте.
- При вырождении модели предметной области в анемичную, теряются все преимущества, которые дает данный шаблон, при этом сохраняются его недостатки. В частности, применение данного шаблона по прежнему требует довольно мощного слоя доступа к данным, реализованного, например, с использованием громоздких ORM-фреймворков.
Давайте рассмотрим данные соображения подробнее. Анемичная модель предметной области противоречит принципам ООП. В каком-то смысле данное соображение справедливо, т.к. объекты предметной области не имеют поведения. Однако доступ ко внутреннему состоянию объектов можно инкапсулировать за методами данного объекта. Продемонстрируем данную идею на нашем примере с добавлением сервиса в заказа: в класс ServiceOrder добавляется метод addService(), при этом метод setServices() делаем приватным:
public class ServiceOrder {
private List<Service> services;
public List<Service> getServices() {
return services;
}
private void setServices(List<Service> services) {
this.services = services;
}
public void addService(Service service) {
if (services == null)
services = new ArrayList<Service>();
services.add(service);
}
}
private List<Service> services;
public List<Service> getServices() {
return services;
}
private void setServices(List<Service> services) {
this.services = services;
}
public void addService(Service service) {
if (services == null)
services = new ArrayList<Service>();
services.add(service);
}
}
Класс ServiceOrderManagerImpl будет осуществлять проверку возможности добавления заказа и вызывать метод addService() класса ServiceOrder:
public class ServiceOrderManagerImpl implements ServiceOrderManager {
private ServiceProviderRepository providerRepo;
public ServiceProviderRepository getProviderRepo() {
return providerRepo;
}
public void setProviderRepo(ServiceProviderRepository providerRepo) {
this.providerRepo = providerRepo;
}
public void addNewService(ServiceOrder order, Service service) throws ProviderNotFoundException {
if (providerRepo.hasAvailableProvider(service.getParam1(), service.getParam2(), ...)) {
order.addService(service);
}
else {
throw new ProviderNotFoundException(service);
}
}
}
private ServiceProviderRepository providerRepo;
public ServiceProviderRepository getProviderRepo() {
return providerRepo;
}
public void setProviderRepo(ServiceProviderRepository providerRepo) {
this.providerRepo = providerRepo;
}
public void addNewService(ServiceOrder order, Service service) throws ProviderNotFoundException {
if (providerRepo.hasAvailableProvider(service.getParam1(), service.getParam2(), ...)) {
order.addService(service);
}
else {
throw new ProviderNotFoundException(service);
}
}
}
Проблема заключается в том, что вынесение поведения в другие классы требует чтобы такие методы были публичными. Это существенно ухудшает инкапсуляцию. Любой нерадивый разработчик сможет обратиться к свойству объекта, минуя методы соответствующего сервиса, например добавить сервис в заказ без проверки доступности провайдера.
Решением может быть инкапсулция всей бизнес-логики, например с помощью шаблона POJO-фасад. При этом необходимо обеспечить или неизменяемость передаваемого во вне слоя бизнес-логики объектов, или хотя бы их отцепленность от слоя хранения данных, что не позволит сохранять изменения таких объектов, минуя слой бизнес-логики.
Другие принципы ООП, такие как наследование и полиморфизм, остаются доступными и при использовании анемичной модели предметной области. Большинство современных ORM-фреймворков допускают отображение иерархии классов на базу данных. Например, при построении модели финансового приложения следует учесть различные стратегии расчета лимита овердрафта. Предположим есть две стратегии: нет овердрафта и есть овердрафт с ограниченным лимитом. Построим следующую иерархию классов:
public abstract class AbstractOverdraftLimitStrategy implements OverdraftLimitStrategy {
private Long id;
public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}
@Override
public abstract Double getLimit();
}
private Long id;
public Long getId() {
return id;
}
private void setId(Long id) {
this.id = id;
}
@Override
public abstract Double getLimit();
}
public class NoLimitOverdraftStrategy extends AbstractOverdraftLimitStrategy {
@Override
public Double getLimit() {
return .0;
}
}
@Override
public Double getLimit() {
return .0;
}
}
public class ConstantLimitOverdraftStrategy extends AbstractOverdraftLimitStrategy {
private Double limit;
@Override
public Double getLimit() {
return limit;
}
public void setLimit(Double limit) {
this.limit = limit;
}
}
private Double limit;
@Override
public Double getLimit() {
return limit;
}
public void setLimit(Double limit) {
this.limit = limit;
}
}
При этом объект класса Account - счет - будет иметь ссылку на конкретную реализацию стратегии расчета овердрафта, применимую для данного счета:
public class Account {
private OverdraftLimitStrategy overdraftLimitStrategy;
public OverdraftLimitStrategy getOverdraftLimitStrategy() {
return overdraftLimitStrategy;
}
}
private OverdraftLimitStrategy overdraftLimitStrategy;
public OverdraftLimitStrategy getOverdraftLimitStrategy() {
return overdraftLimitStrategy;
}
}
В некотором AccountService мы теперь можем использовать полиморфную реализацию стратегии расчета овердрафта:
public class AccountService {
...
public void withdraw(Account account, Double amount) {
if (account.getAmount() + account.getOverdraftLimitStrategy().getLimit() >= amount) {
account.setAmount(account.getAmount() - amount);
}
}
}
...
public void withdraw(Account account, Double amount) {
if (account.getAmount() + account.getOverdraftLimitStrategy().getLimit() >= amount) {
account.setAmount(account.getAmount() - amount);
}
}
}
Теряются все преимущества, которые дает шаблон Модель предметной области. Как мы рассмотрели выше, преимущества, которые дает моделирование системы в ООП-стиле при использовании анемичной модели предметной области нам по прежнему доступны. Возможно, что получить данные преимущества можно только при грамотной проработке архитектуры системы, в частности - интерфейсов между слоями, но это - соответствующая плата за более простую модель. Способность объектов прозрачно сохраняться в долговременной памяти так же не теряется. Как раньше объект сохранялся соответствующей командой в репозитории (Repository) или участке работы (Unit of Work), так и продолжает сохранятся. Синхронизация изменений внутреннего состояния объекта с хранилищем данных не зависит от того, как именно выполняется данное изменение - методом внутри класса объекта или методом сервиса.
Причины популярности шаблона Анемичная модель предметной области
Теперь попытаемся найти ответ на вопрос, почему шаблон Анемичная модель предметной области так популярен. На мой взгляд, основная причина кроется в том, что данный шаблон легче реализуется с помощью современных технологий по сравнению с насыщенной моделью. Наиболее распространенным способом структурирования исходного кода сегодня является шаблон Внедрение зависимостей. При этом в приложении четко прослеживается два источника объектов:
- IoC-контейнер, который реализует шаблон Инъекция зависимостей и строит служебные объекты: репозитории, сервисы, фасады;
- ORM-фрейморк, который создает объекты предметной области.
При реализации шаблона Насыщенная модель предметной области возникает проблема связывания объектов, построенных этими двумя способами, в единый граф. Если вернуться к примеру насыщенной модели предметной области, описанному выше, то видно, что в объект класса ServiceOrder нужно каким-то образом передать объект класса, реализующего интерфейс ServiceProviderRepository. Не все ORM-фреймворки позволяют инъектировать в объекты при их построении сторонние зависимости, реализуемые IoC-контейнером. Приходится использовать подходы, обладающие рядом недостатков.
- Инъекция посредством статических переменных и методов. Репозитории и прочие IoC-объекты передаются как одиночки. Недостатки такого подхода: сложнее подменить объект, например, заменив его на заглушку при тестировании. Так же данный подход вносит скрытые зависимости.
- Передача зависимостей через метод (не путать с инъекцией через сеттер). Если мы включаем в объект предметной области метод, содержащий бизнес-логику, то в данный метод нужно передавать все требуемые ему объекты: репозитории, фабрики, соединения и т.д. Недостатки такого подхода: усложняется сигнатура методов бизнес-логики, все зависимости выставляются наружу, при усложнении логики метода, ему могут потребоваться новые зависимости, что приведет к изменению сигнатуры метода и необходимости как исправлять вызов метода везде, где он используется, так и как-то передавать новые зависимости в точки вызова.
- Использовать шаблон Локатор сервисов и передавать каким-то образом реализацию данного шаблона в метод объекта предметной области. Недостатками данного метода являются все недостатки шаблона Локатор сервисов.
- Вместо зависимостей передавать в метод результат их работы. Но данный подход убирает логику из объекта предметной области и по сути является первым шагом к анемичной модели.
Преимущества шаблона Анемичная модель предметной области
Помимо популярности, вызванной имеющейся инфраструктурой, шаблон Анемичная модель предметной области имеет и ряд собственных преимуществ. Давайте рассмотрим их подробнее.
- Простота проектирования и разработки. Как правило Анемичная модель предметной области требует меньше усилий и квалификации для своей разработки. Так как объекты предметной области лишены поведения, которое икапсулируется в сервисах, то снимается вопрос в какой объект предметной области поместить тот или иной метод. Конечно, вместо этого появляются вопросы в какой сервис его поместить, создавать новый сервис или нет, но данные вопросы проще для решения.
- Простота генерации на основе хранилища данных: базе данных, WSDL-описанию сервиса, файлу настройки объектно-реляционного отображения и т.д. Данное преимущество особенно ярко проявляется тогда, когда мы строим интерфейс к унаследованной системе, реализованной не на ООП-языке или выставляющей интерфейс в стиле удаленного вызова процедур. При современной нацеленности информационных систем на использование сервисно-ориентированной архитектуры данная особенность подхода играет все большую и большую роль. Это, кстати, отличает текущее положение вещей от времен написания критической статьи Фаулера, в конце концов прошло почти 10 лет. Мне как разработчику клиента к существующей службе предприятия гораздо проще сгенерировать модель данных по ее - службы - контракту (например по WSDL-описанию) и разработать слой классов-менеджеров над данной моделью, нежели строить насыщенную модель предметной области и реализовывать ее интеграцию с удаленной службой.
- Простота повторного использования. Если мы имеем приложение, построенное на основе шаблона Анемичная модель предметной области и нам нужно реализовать приложение, работающее с этими же данными, но реализующее другую бизнес-логику, то мы можем переиспользовать классы существующей модели. В случае насыщенной модели предметной области такое переиспользование будет затруднено, т.к. бизнес-логика жестко зашита в классы, реализующие модель предметной области. С данной точки зрения излишняя инкапсуляция скорее вредна нежели полезна.
Заключение
В заключение стоит сказать о том, что последнее дело как в ведении дискуссии, так и в принятии решения - слепое следование мнению тех или иных авторитетов. Всегда стоит помнить, что в науке существует такое понятие, как плюрализм мнений. Есть разные научные школы, придерживающиеся тех или иных подходов. Вполне естественна конкуренция между такими школами. Любые слова того или иного "гуру" могут быть вызваны конъюнктурной составляющей. Не нужно творить себе кумиров, чтобы потом не пришлось в них разочаровываться. Всегда следует думать своей головой и выбирать решение, исходя из собственного опыта и приоритетов прежде всего своего проекта.
Ресурсы
- Patterns of Enterprise Application Architecture;
- Domain Model;
- Anemic Domain Model;
- Anemic Domain Model: Pros/Cons
Понравилось сообщение - подпишитесь на блог и Twitter
13 комментариев:
Привет, Павел!
Спасибо за статью. Хотелось бы отметить, что простота проектирования и разработки анемичной доменной модели может быть отмечена как плюс только на ранних этапах разработки - при расширении доменной модели сложность начинает расти экспоненциально. Фрагментация богатой доменной модели не столь стремительна (PEAA, Fowler).
Так же хотелось бы отметить простоту повторного использования. Переиспользование "голых" классов, теоретически, является преимуществом. Если же говорить о повторном использовании поведения или компонентов, то повтороное использование наиболее свойственно функционально связной богатой доменной модели, так как гранулярность доменных объектов значительно выше, чем у сервисов (Java Application Architecture, Kirk Knoernschild).
Спасибо за то что в очередной раз потратили время!
Давно слежу за тем что пишет "Суровый Челябинский Программист" в рунете :)
Ясно излагаете! :)
С мыслями в статье согласен до отрыва головы при кивании.
Использовал отрывок как замечательную иллюстрацию у себя в блоге
"Два подхода мышления"
http://skynin.blogspot.com/2012/10/blog-post.html
@Eduards, все же давайте мыслить самостоятельно, а не ссылаться на авторитетов. А то получается, что крокодилы летают, ибо так сказал товарищ прапорщик.
Я PEAA давно читал, но по-моему там сравнение с анемичной моделью не делается, если конечно не считать таковой "Транзакционный сценарий", который действительно похож. В общем, мне не понятно откуда следует, что сложность анемичной модели растет экспоненциально, а у богатой не так. Если вам не трудно, изложите свои аргументы.
По поводу повторного использования. Согласен, если поведение жестко инкапсулировано в доменных объектах, то при любом повторном использовании данных объектов используется и их поведение. Вопрос в статье же стоял о том, что делать если требуется другое поведение.
Прошу заметить, что я не агитирую за то или иное решение, просто считаю, что анемичную модель незаслуженно считают антипаттерном, просто потому что так сказал Фаулер.
@Skynin спасибо за теплый отзыв. У вас изложен интересный взгляд на проблему, не думал о таком. Хотя мне, как человеку работающему с SOA и бизнес-процессами, именно второй подход ближе.
@Pavel Samolisov
Как программист, я все больше прихожу к "процессно-ориентированному" видению. К anemic. Особенно ввиду того что не год лопатил конфы 1С - там тотальный rich. Ужас-нах при росте масштабов системы.
в идеях ФП - оно выражено в крайней форме, нечто вроде - "а давайте выбросим все существительные!". Крайности как известно хороши только для разговоров о чистых абстракциях, а не для жизни. И тут как раз и вижу силу ОО языков, они вообще говоря не навязывают ни rich ни anemic подхода. Хочешь так дели данные и действия, а хочешь эдак.
Ну и свежее на памяти, в гиковском radio-t, umputun (он разработчик чего-то там трейдерско-брокерского-биржевого. на Java ессно) как-то сказал (по смыслу)
с годами у меня зрело ощущение что с ОО парадигмой что-то не совсем так. И вот я стал пробовать смотреть на структуру приложения как на data-flow и так и писать, и точно говорю, волосы становятся шелковистие!
@Skynin Ну, кстати, мы в таком подходе - data-flow - писали платформу для документооборота. Решения применяли простые как топор, например у нас было много HibernateHandler'ов по-сути являющимися лишь библиотеками запросов, реализованными с помощью статических методов. И это работало, работает, развивалось и развивается.
@Pavel Samolisov
да, они как-то сами при таком подходе делаются простые как топор. И не надо потом мучительно рефакторить быстро жирнеющие классы, думать а корова доится, или корову доят, выковыривать из них функционал для другого проекта, и прочая, и так далее.
но конечно вы в статье и плюсы rich описали:
как слышится так и пишется!
в проектах с относительно статическим ТЗ, вернее с устойчивыми свойствами сущностей предметной области, например система проектирования интегральных схем - rich будет самое то.
@Pavel Samolisov
Согласен, анти-паттерном анемичную доменную модель назвать нельзя, всему свое применение.
Касательно эволюции доменной модели:
Вопрос о том, в какой сервис поместить поведение по определению не может быть решен правильно, если поведение принадлежит некоторой сущности или VO. Мы получаем не эволюционирующую модель, а растущий набор сервисов, управляющих псевдо-моделью. Здоровая эволюция доменной модели возможна только при понимании взаимосвязей между ее составляющими, где поведение играет ключевую роль. Мы избегаем больных вопросов, требующих понимания принципов функционирования предметной области и сопутствующих сущностей создавая все больше новых сервисов, что, в конце-концов, ставит нас на колени. Мы не получаем модели предметной области, а получаем модель "собери сам" - суть сущностей и связи между ними утеряна, разбросана по сервисам, хелперам.
@Eduards Sizovs, я согласен с Вашим замечанием. Но справедливости ради хочу сказать, что при разработке и развитии богатой модели приходится решать задачу, которая нисколько не проще поиска нужного сервиса в большой "помойке": определять какой именно доменный объект должен быть наделен тем или иным поведением. Грубо говоря, это экскаватор копает землю или та копается экскаватором? Вполне возможно, что разработка в анемичном стиле для конкретной команды, имеющей конкретный опыт причем не только программистов, но и аналитиков, будет предпочтительнее. И результат им будет легче поддерживать.
Я тут задумался. Для сервисно-ориентированной архитектуры предприятия разработали такое понятие, как SOA Governance и такие инструменты, как реестр сервисов и репозиторий предприятия. В принципе ничего не мешает применить данные наработки для поддержки анемичной модели предметной области в программе.
@Eduards Sizovs
-- суть сущностей и связи между ними утеряна, разбросана по сервисам, хелперам.
Суть, в общем случае - это произвол ее искателя. Прямо или косвенно поиски ее задаются условиями задачи. Используя Гради Буча: Для кого-то "зеленые холмы Англии" символ патриотизма. Для другого - "площади для кормления овец".
Поэтому бороться за нее абсолютно, безусловно - нет смысла. Только ради академической красоты :)
Второй проблемой rich является известное - "абстракции текут". И, не настаиваю, потому что интуитивно:
ввиду разрастания размера самих сущностей, текут быстрее, с увеличением их количества и связей, чем в anemic. В anemic ИМХО, нам не так сильно приходится накладывать наши абстракции, на то от чего мы абстрагировались). Да, в anemic мы получаем проблему управления нашим "конструктором". Очень грубо говоря,
если rich - это описание на ЯП модели предметной области
то anemic - создание инструментов и кирпичиков, чтобы потом "инфраструктурно" уже собрать работающую систему, которая соответствует модели описанной другими средствами.
Или еще так
rich - мы сразу пишем программу
anemic - мы пишем библиотеку примитивов, а потом будем собирать программу.
идеи anemic для меня вообще созвучны c идеями DSL. даже по недостаткам, сложностям.
Похоже вопрос Rich vs Anemic свелся к тому, является ли подход "собери сам" возможностью или обузой.
@Pavel Samolisov
Это да.
Но насколько я могу судить, в кулуарах аутсорса на старые проекты "жалуются" не столько из-за того что на древней версии фреймворка, сколько из-за того что изначальное, старательное следование rich и фиксинг с эволюцией привели к "клубку ниток".
В то же время anemic, имхо, это более новый тренд (может даже и старый, пусть функциональщики забирают себе медаль). причем в общем тренде - компонентного, композитного подходов. Или, как в одной из статей кажется Гетца - "пора говорить о Java не как об объектно-ориентированном языке, а как об инфраструктурно-ориентированном".
В этом же тренде находятся и OSGi.
и вот где мне видится будет направление развития, в ответе на вопрос: Eduards Sizovs: Мы получаем не эволюционирующую модель, а растущий набор сервисов, управляющих псевдо-моделью
весьма вероятно появление паттернов, методик, фреймворков а то и спец языка для более удобно связывания сервисов и данных. Неких "Apache Camel"ов, но более низкого уровня.
Вплоть до появления отдельной специализации программистов "кодеры связывающего кода" :) или еще какие сборщики.
И что вообще губит мечту о "повторно используемом коде" как не rich? А вот с RAD, c генерацией по UML, Eclipse Modeling Framework - надеждами rich - как-то не сложилось.
Нашел хороший спор на тему какую доменную модель реализовывать в приложениях: богатую или анемичную. Приведены аргументы для обоих вариантов. Рекомендую к прочтению.
Отправить комментарий
Любой Ваш комментарий важен для меня, однако, помните, что действует предмодерация. Давайте уважать друг друга!