Functional Modules by Spring. Rondo

August 13, 2013

Functional Modules by Spring. Rondo

[ru] [en]

Ладно, дорогие мои. Cкорее всего никто и не заметил, что это не статья, а соната. Новое время - новые ноты. А сонатно-симфонический цикл с времен Гайдна насчитывает четыре части, известные как Мангеймский цикл (если я ничего не напутал), а именно: аллегро, рондо, минуэт (я выбираю в форме скрецо) и финал. Да, первая часть была аллегро - быстро пробежался по главным темам, но из-за их сложности получилась небольшая четырех-частная увертюра (ну просто посчитайте картинки в этом посте). Так что, мой друг, не все так случайно в этом мире как кажется.

Я читаю немой вопрос на ваших лицах. Какая еще "соната"?! Ну если для вас это не музыка, посмотрите, может есть что интересного в иных разделах моего блога. Остальные приглашаются "под cut".

Повестка дня у нас такая
Дальше, как по нотам.


Maven. Выделение компонент

Под выделение компонент в данном случае я понимаю группировку функционала по компонентам размещения. Дело в том, что коль уж мы занимается разработкой multitiered а не claud систем, то у нас неизбежно образуются выделенные сервера - ну хотя бы сервер базы данных или JMS-очередей. Поддержка и кластеризация таких выделенных компонент размещения это совершенно отдельная задача и она не в теме этой публикации. Но можем ли мы сказать что написанный непосредсвенно нами код это монолит? 99% что нет - это тоже набор функциональных сервисов. Их можно использовать как Spring-сервисы (к примеру DAO) непосредственно в нужном месте, а можно задеплоить в приложения на отдельный сервер и вызывать, например, по RMI. Конечный выбор размещения зависит от нагрузки и способа использования и поддержки данного функционала остальными компонентами системы.

Пусть, к примеру, есть система суть которой WEB-портал. Как правило, в такой системе всегда есть некий набор периодических задач, что запускаются по расписанию - они производят удаление устревших пользовательских данных, их архивацию, рассылки уведомлений и т.д. Совершенно очевидно, что в случае большого количества пользоавтелей портала эти задачи займут много ресурсов и следует вынести весь такой функционал на отдельный backend-сервер. Тогда и управлять (перегружать) и кластаризировать компоненты общей системы будет значительно проще, ведь они проинтегрированы самым старым но самым надежным способом интеграции - на уровне данных. Однако при разработка системы программистам запускать два серсива черезвычайно неудобно. То есть, изначально следует озадачиться таким размещеним кода по функциональным компонентам, которое позволит в дальнейшем быстро изменить размещение таких компонент. То есть уже при проектировании должно быть видно, что шедулеры нужно создавать так, чтобы они могли быть запущены как в одной JVM с WEB-порталом (если это маленький внутренний проект), так и как отдельный сервер. Казалось бы - причем же тут Maven, указанный в заголовке, ведь это типичная задача для Spring-клея? Да при том, что в данный момент вам следует правильно создать структуру проекта в Maven и, заметте, что некоторые модули, которые, возможно, будут пререгружены для ваших заказчиков, должны иметь выделенные API-интерфейсы сгруппированные в отдельные Maven JAR-артефакты.

Еслии вы "большая серьезная компания", которая ведет большой проект состоящий из множества функцинальных модулей, то скорее всего у вас будет несколько выделенных групп разработки ответственных каждая за свою компоненту. В этом случае, кстати, выделение API-интерфейсов просто обязательно, чтобы остальные разработчики хотя бы понимали как использовать сервисы такой компоненты. И каждая компонента это фактически отдельнй проект, она будет иметь свой pom.xml, возможно даже свой репозиторий исходных кодов и свой отдельный релиз-план. Но для относительно небольшой группы нецелесообразно разбивать проект на кучу отдельных под-проектов - при одновременном внесении множества мелких изменений в каждй из них вы просто замучаетесь с постоянным выпуском очередных релизов, так как для выпуска версии всей системы вам необходимо сначала выпустить релизы всех используемых компонент. Вы то, как лид разработки, один, а подобное разбиение вызовет у вас лишь чувство раздвоения личности и со стороны будет выглядеть примерно так:
Так что даже не пытайтесь это делать - структура зависимостей в модулях подобных проектов весьма неочевидна и не ясно как ее задокументировать чтобы объяснить остальным разработчикам. Попоробуйте найти описание такой компонентной структуры ActiveMQ или JBoss!
В случае создания "среднего" по масштабам продукта, но для нескольких заказчиков, можно обойтись одним центальным агрегированным pom.xml (для вашего так называемого core-проекта) и по pom.xml для каждого из заказчиков. Как именно они будут организованы я окончательно разъясню в разделе Maven. Сборка дистрибутива. Тут же я поделюсь одним секретом - ваш core-проект должен быть полностью рабочей демонстрационной версией вашей системы, а проекты заказчиков - лишь кастомизировать и расширять ее. Дело в том, что demo-версия системы это такая подлая веСЧь которая никому месяцами не нужна и все просто забивают на ее поддержку, но когда она вдруг понадобится, то это обязательно cлучится "вчера". И при этом в ней уже должно работать то что запланировано к выпуску "на завтра". Так что мой вам добрый совет - всегда имейте стабильный релиз "демки", а самый простой споcоб это сделать был вам только что указан - не разрабатывайте ничего кроме демки, заказчики лишь кастомизируют ее функциолнал.

Таким образом, в простейшем случае, что описан выше, структура базового demo-проекта получается примерно такой (разумеется в корне находится агрегирующий pom.xml в котором проинициализированы все необходимы плагины, зависимости и т.д. - его мы и рассмотрим)

├── assembly
├── components
│   ├── backend
│   └── frontend
├── config
│   ├── main
│   ├── test
│   └── tiles
├── i18n
└── services
├── api
└── dao
  • assembly - опциональный модуль необходимый для создания программы-инсталлятора, как DEB-пакета или IzPack-инсталлятора
  • components - собственно компоненты размещения системы. В "чистом виде" - то есть JAR/WAR/EAR артефакты. Как минимум мы выделили две компоненты - backend и frontend. И если последняя это WAR-файл, то первая может быть и JAR-артефакт с Main-функцией
  • config - модуль для хранения properties-конфигураций. На практике, как будет показано позднее, в репозитории исходных кодов хранить следует не конфигурационные файлы а шаблоны конфигурационных файлов, превращая их в полноценные properties непосредственно перед использованием - то есть перед запуском Unit-тестов или создания DEB-пакета инсталяции для окружения заказчика. Однако давайте обговорим эти сложности позже
  • i18n - модуль интернационализации. Часто бывает трудно найти общее мнение как именно следует реализовывать локализацию приложения. В случае же если у вас несколько заказчиков, то выход только один - не пытайтесь повторно использовать локализированные ресурсы как готовые JAR-артефакты из core-проекта, именно они меняются чаще всего абсолютно непредсказуемо. Просто смитиресь с тем, что несколько ресурс-бандлов будут изначально просто скопированы из core-проекта а потом в них будут внесены изменения. Поверьте, это достойная и одновеменно малая жертва желаемой "нормализации всего" - достаточно того, что в самих исходных кодах вы используете ключи от ресурсов. Подарите заказчику право самостоятельно владеть своими текстовыми ресурсами. Также соберите все ресурсы локализации в одном модуле (но не в одном файле, разумеется!) - это сущетвенно ускорит тупую часть работы про внедрению системы для нового заказчика. Она заключается в правке текстов локализаций - их теперь будет гораздо проще найти, а работу можно будет поручить даже менеджеру проекта (он, как правило, очень нервничает и рвется помогать - вот пусть и согласовывает тексты и надписи)
  • services - собственно "слои" нашего приложения и прочие маленькие "под-проектики". Например, таким "под-проектиком" может быть модуль сервисов по работе с социальными сетями и т.д. Но в целом это, разумеется, как минимум старый добрый DAO-слой и слой использующих их API-сервисов. Думаю, понятно что backend и frontend должны использовать непосредственно API а не DAO напрямую (кстати и DAO может также состоять из двух модулей - интерфейсов и имплементации, на случай если вы точно не уверены что вам не прийдется через некоторое время менять ORM-фраемворк)
Тогда в проекте заказчика в "идеальном случае (когда все отличия заключаются только в локализации и настройках) будет такая структура

├── assembly
├── config
└── i18n
Это просто мечта! Мы создали проект кастомизации в котором (о, наконец!) нет ничего кроме кастомизации.


Spring. Перегрузка сервисов

Собственно, ничего неожиданного. Мы создаем компоненты, теперь уже новомодные слабосвязанные Spring-компоненты, а потом оказывается, что у нового заказчика они должны работать чуть по-другому. Традиционный путь, как правило, таков - в core-функционале реализуется оба алгоритма и добавляется некий конфигкрационный ключ в конфиг-файл о том, какое из "if"-ветвлений должно быть активным. Знакомая ситуация? Заказчики множатся, множатся и конфиг-костыли. Уже точно не понятно и толком никто не помнит зачем они нужны - начинаешь читать код и видишь, что он в некоторых блоках логически противоречит друг-другу и при этом его можно отконфигурировать так, что будут работать оба фрагмента. И ты понимаешь, что это не код, а... смотреть больно.


Так что же делать в этой ситуации? Посмтрите, не слишком ли много функций реализует ваш сервис - может это на семом деле два или даже три сервиса? Не помню где, но где-то я читал, что если код сервиса занимает более 2-х экранов - это плохой код. Это значит, что у вас или несколько глобальных IF-ов в самом начале кода или в данной простыне просто описаны все шаги определенного процесса (workflow). Ну а если это так, то зачем же все писать в один файл? И читать сложно и в памяти все не удержишь. Смотрите какие хорошие паттерны поведения есть для таких случаев - Chain of Responsibility, Template Method и, наконец, Interpreter. В частности последний позволит вам вынести в конфиг не костыль а алгоритм! Это просто сказка - смотриш на конфиг и понимаеш как именно у данного заказчика происходит та или иная обработка.

Но все это в теории. Практика начинается с того, что есть некий сервис и вот нужно заставить его работать чуть по-иному. Реализовывать Interpreter еще очень рано - нам нужно для начала накопить знания по возможным бизнес-алгоритмам данного сервиса для 3-4-х заказчиков. А сейчас нам просто нужно вспомнить, что в Spring есть alias! Смотрите, мы используем Spring, говорим что в нем есть "позднее связывание кода", но на практике почти не используем это. А зря. Spring позволяет подменить один сервис на иной даже не выделяя общий интерфейс для таких сервисов. Достаточно просто отнаследоваться от родительского сервиса и перегрузить определенный protected-метод (читай паттерн Template Method). Массово этот подход используется для настройки Spring Security

Пример. Есть класс "LocaleUtils"- он содержит перечень поддерживаемых локалей и локаль приложения "по-умолчанию". Заметте, это не должна быть локаль операционной системы - вам очень сложно будет рассказать зказчику из Бангалора почему на сервере в операционной системе нужно устанавливать строго определенную локаль (хотя плохой пример - если заказчик из Бангалора, то, навное, этобудет и не так уж сложно, там этим никого не удивишь. Тогда пусть это будет заказчик из Берлина).

private static final Logger logger = LoggerFactory.getLogger(LocaleUtils.class);

private static Locale DEFAUL = null;
private static Holder LOCALE_HOLDER = null;

static {
LOCALE_HOLDER = new Holder() {
@Override
protected Locale createValue(String key) {
try {
return org.apache.commons.lang.LocaleUtils.toLocale(key);
} catch (Exception e) {
logger.error(e.getMessage(), e);
return DEFAUL != null ? DEFAUL : Locale.getDefault();
}
}
};
DEFAUL = toLocale("en");
}

private List supported = Collections.unmodifiableList(new ArrayList(Arrays.asList(getDefaultLocale())));

public static Locale toLocale(String value) {
return LOCALE_HOLDER.getValue(value);
}

public void setSupported(String... locales) {
Set set = new HashSet();
for (String l : locales) {
set.add(toLocale(l));
}
supported = Collections.unmodifiableList(new ArrayList(set));
}

public List getSupported() {
return supported;
}

public Locale getDefaultLocale() {
return DEFAUL;
}

public void setDefaul(String loc) {
DEFAUL = toLocale(loc);
}

public boolean isSupportedLang(Locale locale) {
for (Locale loc : supported) {
if (loc.getLanguage().equals(locale.getLanguage())) {
return true;
}
}
return false;
}

}

При этом в коде приложения мы используем этот сервис через autowiring - то есть в Spring XML у нас этот бин нигде и не прописан, собственно.

И вот у нас новый заказчик - разумеется из другой страны и у него иной набор языков. В данном случае (поскольку у нас в классе есть SET-методы) все достаточно просто и нам не нужно создавать отдельный класс - хотя и это вполне можно и допуситмо. Но суть такая
  • создать новый бин
  • проинициализировать его
  • подставить его везде вместо стандартного
Следите за руками. Создается новый, еще один XML файл, что добавится в Spring-контекст приложения, в котором указываем следующее

<alias alias="localeUtils" name="smartLocaleUtils"/>

<bean id="smartLocaleUtils" class="com.dit.core.utils.LocaleUtils"/>

<bean id="initSmartDefaulLocale"
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" depends-on="smartLocaleUtils">
<property name="targetObject" ref="smartLocaleUtils"/>
<property name="targetMethod" value="setDefaul"/>
<property name="arguments">
<list>
<value>en</value>
</list>
</property>
</bean>

<bean id="initSmartSupportedLocales"
class="org.springframework.beans.factory.config.MethodInvokingFactoryBean" depends-on="initSmartDefaulLocale">
<property name="targetObject" ref="localeUtils"/>
<property name="targetMethod" value="setSupported"/>
<property name="arguments">
<list>
<value>en</value>
</list>
</property>
</bean>



Вот как я советую делать. А бесконечное добавление непонятных ключей в конфиг-файл (одному зказчику нужно это, а другому - то) превратит приложение, его архитектуру и исходные код, в лучшем случае, в вот такую пародию



Повторюсь, напомнив, что помимо указанного использования с пере-инициализацией класса, alias позволят просто создать иной класс, отнаследованный от перегружаемого или реализующий с ним один общий интерфейс. Говорю это потому, что вовсе не нужно выносить в конфигурацию то, что вы не собираетесь конфигурировать. Чем меньше конфигов - тем лучше. Если заказчик сказал, что у него будт только албанский язык - отлично, не нагружайте его службу поддержку необходмостью изучения ваших конфиг-файлов. Просто создайте иной бин "customerLocaleUtils", прошейте в статик-секции этот несчастный язык и создайте alias в спринг конфигурации. Помните - заказчик не платил вам за возможность выбора языков в конфигурации - отлично. Делайте то, что нужно, помня о том, что будет и второй зказчик, дай то Бог. О есть - настройка нужна но для нас а не для заказчика. А мы настроку делаем в Spring



Maven. Зависимости модулей

Самый интересный вопрос. И сложный. Дело в том что тут помимо зависимостей java-библиотек (что, собственно, и подразумевается при использовании maven) важным выступает зависимости Spring-конфигураций в модулях. То есть, если у нас есть некое core-dao, то для зказчика smart может понадобиться использовать дополнительные entity - а занчит возникнет модуль smart-dao который в своей Spring-конфигурации должен расширять core-dao.

Долгое время я мучался, думая как бы так ввернуть свой XML в уже существующие, использовал для этого соглашение о наименовании XML-конфигов - что все они будут начинаться с префикса заказчика, потом содержать название модуля, и использовал вот такие инструкции импорта

<import resource="classpath:/*-dao.xml"/>

В целом - путь неправильный, хотя иногда и срабатывает но большей частью - нет. И очень сложно понять почему иногда работает а иногда - нет. Изначальная идея такая - все JAR-ы в classpath содержат Spring-xml, эти xml подтягиваются и начинают парситься.... так мы найдем все classpath:/*-dao.xml - короче это не работает. Во-первых этот "парсинг" задается и отрабатывает только один раз и если ваш кастомный JAR с перегруженным XML не попал в сканирование (в classpath), то второй раз пере-скаинрование уже не запустить. Во вторых это не хорошо рассчитывать что соглашение о наименовании выполняется. Это хорошо что оно есть - но строить на этом логику старта приложения в корне неправильно. Это все приводит к ошибкам, что кроме как "идиотскими" не назовешь, но на их диагностирование уходит просто невероятиное количество сил и времени.

Более првильноый путь - не пытаться составлять начальную Spring-конфигурацию приложения таким образом, чтобы потом туда "впихивать" новых зкакзчиков, а просто для нового заказчика писать новую Spring-конфигурацию. Это здорово все упрощает и к тому же она будет весьма небольшой и "читабельной". А иначе вы вынуждены смотреть на XML в core конфигурации и гадать - сработает или нет. Вы пишете новый конфиг для DAO, который просто импортирует в себя core-конфиг + инициализация нескольких новых DAO (как правило - добавятся сами через autowiring). Пишете новый конфиг для сервисов, который просто импортирует старые сервисы + новое DAO + пару новых сервисов (аналогично через autowiring) и потом - новый конфиг для WEB-артефакта с новыми сервисами. В самом простом случае, когда у нас нет ни одного нового сервиса и ни одного нового DAO мы все-равно долджны создать отдельные maven-модули только для того, чтобы положить туда Spring-xml специфичные для заказчика, пусть они даже и не содержит ичего кроме импорта core-xml. Вы скажете "плохо"? А я скажу - отлично! У меня получилась совершенно прозрачная структура, в ней нет ни одного не нужного мне сейчас класса, но вот если он мне таки понадобится, то я не буду ломать голову в какой модуль и в какую папку мне его добавлять, так как вся инфраструктура для модульного расширения не просто готова но уже используется в моем проекте. И это здорово.


JPA. Расширение контекста

Это техническая сложность. Как заставить персистентнй контекст работать по-новому, как туда добавить еще одно DAO? Ведь мы не ходим иметь два таких контектста - это два пула, сложное управление транзакциями и т.д. Что значит "добавить еще одно DAO"? Ну вот есть core-система, наша демонстрационная версия. А заказчик - он склонен все усложнять. Он захочет в стандартного для нас "клинета" еще парочку полей добавить - хорошо если поля "полезные", такие не жалко и в core-модуль добавить. Но в оснвном - ерунда какая-то, почти у всх будет не заполнено - вот вам и дополнительная табличка со всязью "1 к 1" - а нового "клинета" нужно просто "отнаследовать" от старого и все core-сервисы работы с клиентами перевести на дженерики (жденерики, скажу я вам, это сила! Вот на таких примерах и понимаещь для чего они нужны). Или вообще зказчик парочку новых сущностей подбросит. Значит нужно научиться расширять персистентный контекст.

Не знаю как в случае использования EJB, давно не курил, с времен BMP/CMP, но с "чистым JPA и Spring" эта проблема разрешима. Долго рассказывать теорию - проще показать на практике и все станет понятно.

Итак, имеем некий code persistent.xml. Назовем его persistence.dit.xml

<persistence xmlns="http://java.sun.com/xml/ns/persistence"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0">
<persistence-unit name="dit" transaction-type="RESOURCE_LOCAL">

<description>DIT JPA Unit</description>

<provider>org.apache.openjpa.persistence.PersistenceProviderImpl</provider>

<class>com.dit.core.model.bss.Activity</class>
<class>com.dit.core.model.bss.Report</class>
<class>com.dit.core.model.bss.UserState</class>
<class>com.dit.core.model.sms.Message</class>
<class>com.dit.core.model.FetchModel</class>
<class>com.dit.core.model.mailbox.Mailbox</class>
<class>com.dit.core.model.mailbox.MailboxStat</class>
......

<exclude-unlisted-classes>true</exclude-unlisted-classes>


<properties>
<property name="openjpa.DynamicEnhancementAgent" value="false"/>
<property name="openjpa.RuntimeUnenhancedClasses" value="unsupported"/>

<!--
Controls whether OpenJPA will attempt to run the mapping tool on all persistent classes
to synchronize their mappings and schema at runtime. Useful for rapid test/debug cycles
-->
<!--<property name="openjpa.jdbc.SynchronizeMappings" value="buildSchema"/>-->

<!--
The default schema name to prepend to unqualified table names.
Also, the schema in which OpenJPA will create new tables
-->
<!--<property name="openjpa.openjpa.jdbc.Schema" value="megafon"/>-->
<!-- used only to generate SQL-queries to create chema in the database by 'mvn openjpa:sql' -->
<!-- see http://openjpa.apache.org/docs/latest/manual.html#ref_guide_dbsetup_dbdict -->
<!--
SupportsXMLColumn: When true, the database supports an XML column type.
See Section 7.10, “ XML Column Mapping ” for information on using this capability. Defaults to false.
<property name="openjpa.jdbc.DBDictionary" value="postgres(SupportsXMLColumn=false)"/>
-->
<property name="openjpa.jdbc.DBDictionary" value="postgres(SearchStringEscape=\)"/>
<!-- TO DISABLE all OpenJPA logging -->
<!-- <property name="openjpa.Log" value="none"/> -->

<!-- TO DELEGATE all OpenJPA logging to Jog4J -->
<property name="openjpa.Log" value="log4j"/>

<!--<property name="openjpa.jdbc.Schema" value="${db_schema}" />-->
</properties>

<!-- Validation modes: AUTO, CALLBACK, NONE -->
<!--
<validation-mode>AUTO</validation-mode>
-->
</persistence-unit>
</persistence>


В JAR-артефакт он попадает благодаря maven-плагину, что вызывается таким образом

<plugins>
<plugin>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa-maven-plugin</artifactId>
<configuration>
<persistenceXmlFile>${project.basedir}/src/main/resources/META-INF/persistence.dit.xml</persistenceXmlFile>
<includes>com/dit/core/model/**/*.class</includes>
<excludes>
**/model/**/*Fields.class,
**/model/**/*Type.class,
**/model/**/*Status.class,
.....
**/model/support/*.class
</excludes>
<addDefaultConstructor>true</addDefaultConstructor>
<enforcePropertyRestrictions>true</enforcePropertyRestrictions>
</configuration>
<executions>
<execution>
<id>enhancer</id>
<phase>process-classes</phase>
<goals>
<goal>enhance</goal>
<goal>sql</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>postgresql</groupId>
<artifactId>postgresql</artifactId>
<version>${postgresql.version}</version>
</dependency>
<dependency>
<groupId>org.apache.openjpa</groupId>
<artifactId>openjpa</artifactId>
<version>${openjpa.version}</version>
</dependency>
</dependencies>
</plugin>
</plugins>



А в Spring мы инициализируем хитрый persistenceUnitManager котороый потом передаем в entityManagerFactory


<bean id="persistenceUnitManager"
class="com.dit.core.dao.persistenceunit.MergingPersistenceUnitManager">
<property name="persistenceXmlLocation" value="classpath*:META-INF/persistence.*.xml"/>
<property name="persistenceUnitPostProcessors">
<!-- This merges all JPA entities from multiple jars -->
<bean id="dit.MergingPersistenceUnitPostProcessor"
class="com.dit.core.dao.persistenceunit.MergingPersistenceUnitPostProcessor"/>
</property>
<property name="defaultDataSource" ref="dataSource"/>
</bean>


<bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="persistenceUnitManager" ref="persistenceUnitManager"/>
<property name="jpaVendorAdapter">
<bean class="org.springframework.orm.jpa.vendor.OpenJpaVendorAdapter">
<property name="databasePlatform" value="${jpa.databasePlatform}"/>
<property name="showSql" value="${jpa.showSql}"/>
<property name="generateDdl" value="${jpa.generateDdl}"/>
</bean>
</property>
<property name="jpaPropertyMap">
<util:map>
<entry key="openjpa.DynamicEnhancementAgent" value="false"/>
<entry key="openjpa.RuntimeUnenhancedClasses" value="unsupported"/>
<entry key="openjpa.Log" value="log4j"/>
</util:map>
</property>
</bean>


И так, все дело, получается в классах com.dit.core.dao.persistenceunit.MergingPersistenceUnitManager и com.dit.core.dao.persistenceunit.MergingPersistenceUnitPostProcessor, а они вот такие:


package com.dit.core.dao.persistenceunit;

import org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager;

/**
* @author romanso
*/
public class MergingPersistenceUnitManager extends DefaultPersistenceUnitManager {

@Override
protected boolean isPersistenceUnitOverrideAllowed() {
return true;
}

}



package com.dit.core.dao.persistenceunit;

import org.springframework.orm.jpa.persistenceunit.MutablePersistenceUnitInfo;
import org.springframework.orm.jpa.persistenceunit.PersistenceUnitPostProcessor;

import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;

/**
* This merges all JPA entities from multiple jars. To use it, all entities must
* be listed in their respective "persistence.xml" files using the class tag.
*
* @author romanso
* @see http://javathoughts.capesugarbird.com/2009/02/jpa-and-multiple-persistence-units.html
* @see http://forum.springsource.org/showthread.php?t=61763
*/
public class MergingPersistenceUnitPostProcessor implements PersistenceUnitPostProcessor {

private Map<String, HashSet<String>> puiClasses = new HashMap<String, HashSet<String>>();

public void postProcessPersistenceUnitInfo(MutablePersistenceUnitInfo pui) {
HashSet<String> classes = puiClasses.get(pui.getPersistenceUnitName());
if (classes == null) {
classes = new HashSet<String>();
puiClasses.put(pui.getPersistenceUnitName(), classes);
}
pui.getManagedClassNames().addAll(classes);
classes.addAll(pui.getManagedClassNames());
}

}


так что я все честно указал - читайте первоисточник1 и первоисточник2



Maven. Сборка дистрибутива

Собственно здесь я хотел сказать, что все в конце-концов должно превратиться в некий WAR/EAR/JAR артифакт, что можно запустить в контейнере или как Java Standalone Application. При этом также есть варианты когда мы WAR артефакт попробуем собрать сразу с Jetty внутри артифакта и поднять этот все из Spring-корнфигурации как Java Standalone Application (так как я сторонник все-таки оттрогаемых конфигураций а не запуск Jetty из специфичных и сложных Main-функций). Но это из екзотики и я расскажу об этом в следующий раз.

Пока же я хотел рассказать, что мы приняли решение все артефакты устанавливать на сервера только через DEB. Этим мы обеспечиваем единообразную процедуру установки всех компонент на все сервера и также единообразную процедуру отката. У нас получилось два типа проектов - WEB проекты что разворачиваются в настроенный Tomcat и Java Standalone проекты, что сушествуют сами по себе, корректно регистрируя себя как сервисы. И помог нам в этом org.vafer.jdeb.

Честно говоря, дальше, конечсно, очень интересно, но весьма не просто. Так как мы не обсудили еще один важный момент - файлы настроек модулей. Ведь когда мы собираем DEB нам нужно включить внутрь все properties-файлы из всех проектов и при том корректно разыменованными под пролакшен среду. Из всех аспектов создания наследуемых мультимодульных проетов на Spring + Maven этот - самый интересный, к тому же он еще и плотно затрагивает Unit-тестирование, которое также требует корректно настроенные properties-конфигурации. Умея управлять конфигурациями разобраться в использовании JDeb уже не так сложно.

Но давайте этот вопрос мы оставим на следующую статью. Мне кажется я вас и так порядком загрузил для одного раза. Спокойной ночи.


Sorry, not translated yet

0 comments:

Post a Comment