Как использовать Java-компонент Embedded PostgreSQL Server в качестве отдельной службы?
Я пытаюсь создать комплексный набор тестов интеграции для Java-приложения RESTful (Services), которое работает в Tomcat(7.x) и зависит от экземпляра Postgresql (9.x). Кроме того, я хотел бы иметь возможность запускать этот пакет как отдельный процесс, исключительно из maven 3.x, если это возможно, с помощью отказоустойчивого плагина maven. Таким образом, тесты можно будет запускать на 3 основных платформах (Mac OSX, Linux и Windows).
Из того, что я узнал, я считаю, что ключ к тому, чтобы это произошло, состоит в том, чтобы сделать что-то вроде этих шагов (в следующем порядке):
- Как-нибудь запустить "встроенную" версию базы данных postgresql (и настроить схему) на этапе тестирования перед интеграцией жизненного цикла maven, чтобы этот процесс postgres-db работал в фоновом режиме
- Запустите мой контейнер Java, который загружает мое приложение служб: я использую плагин Jetty 9.1.5
- Запустите мои (основанные на JUnit) тесты из плагина Failsafe на этапе тестирования интеграции
- Завершите мой контейнер Jetty на этапе тестирования после интеграции maven
- Наконец, есть какой-то механизм, закрывающий ранее запущенный (фоновый) процесс postgres-db на этапе тестирования после интеграции жизненного цикла (убить / очистить этот процесс)
В моей текущей реализации успешно завершает шаги 1 - 3. На шаге 2 он использует exec-maven-plugin, который вызывает класс Java, который использует Java-компонент, встроенный в postgresql. Выдержка из POM.xml:
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.2.1</version>
<executions>
<execution>
<id>Run Postgres DB start/schema setup</id>
<phase>pre-integration-test</phase>
<goals>
<goal>java</goal>
</goals>
</execution>
</executions>
<configuration>
<mainClass>com.some.package.test.utils.DbSetup</mainClass>
<arguments>
<argument>setup</argument>
</arguments>
</configuration>
</plugin>
А вот выдержка из класса DBSetup, который использует postgresql-embedded для запуска экземпляра postgresql:
try {
DownloadConfigBuilder downloadConfigBuilder = new DownloadConfigBuilder();
downloadConfigBuilder.defaultsForCommand(Command.Postgres);
downloadConfigBuilder.proxyFactory(new HttpProxyFactory(PROXY_ADDRESS, DEFAULT_PROXY_PORT));
IRuntimeConfig runtimeConfig = new RuntimeConfigBuilder()
.defaults(Command.Postgres)
.artifactStore(new ArtifactStoreBuilder()
.defaults(Command.Postgres)
.download(downloadConfigBuilder)).build();
PostgresStarter<PostgresExecutable, PostgresProcess> runtime = PostgresStarter.getInstance(runtimeConfig);
final PostgresConfig config = new PostgresConfig(Version.V9_2_4, new AbstractPostgresConfig.Net(
"localhost", 5432
), new AbstractPostgresConfig.Storage(dbName), new AbstractPostgresConfig.Timeout(),
new AbstractPostgresConfig.Credentials(username, password));
config.getAdditionalInitDbParams().addAll(Arrays.asList(
"-E", "UTF-8",
"--locale=en_US.UTF-8",
"--lc-collate=en_US.UTF-8",
"--lc-ctype=en_US.UTF-8"
));
exec = runtime.prepare(config);
process = exec.start();
System.out.println("embedded Postgres started");
Thread.sleep(1200L);
} catch (IOException e) {
System.out.println("Something Went Wrong initializing embedded Postgres: " + e);
e.printStackTrace();
}
catch (InterruptedException e) {
System.out.println("Something Went Wrong Pausing this thread " + e);
e.printStackTrace();
}
Обратите внимание, что я не звоню process.stop();
в методе, который запускает экземпляр Postgres. Итак, в настоящее время у меня нет возможности завершить процесс БД. Однажды я вышел из этого класса DbSetup
все ссылки на этот существующий процесс будут потеряны. И процесс Postgres никогда не останавливается. На самом деле, похоже, что контейнер пристани тоже не закрывается, и вся работа maven зависает, поэтому я должен вручную ее убить (выдержки из вывода моей консоли maven):
[INFO] --- exec-maven-plugin:1.2.1:java (Run Postgres DB start/schema setup) @ BLAHBLAH ---
***START of Postgres DB Setup Process ***
Extract /Users/myUserName/.embedpostgresql/postgresql-9.2.4-1-osx-binaries.zip START
...................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................................Extract /Users/myUserName/.embedpostgresql/postgresql-9.2.4-1-osx-binaries.zip DONE
INFO:20161021 12:58:00: de.flapdoodle.embed.process.runtime.Executable de.flapdoodle.embed.process.runtime.Executable start AbstractPostgresConfig{storage=Storage{dbDir=/var/folders/8g/69wh31fn7nx3q81phwfdpld00000gn/T/postgresql-embed-66cfc41f-0e16-439f-a24b-6e5b6dbc683d/db-content-3bc4b9cc-dd21-43a7-9058-285767f5c53d, dbName='BLAH', isTmpDir=true}, network=Net{host='localhost', port=5432}, timeout=Timeout{startupTimeout=15000}, credentials=Credentials{BLAH, BLAH}, args=[], additionalInitDbParams=[-E, UTF-8, --locale=en_US.UTF-8, --lc-collate=en_US.UTF-8, --lc-ctype=en_US.UTF-8]}
INFO:20161021 12:58:01: de.flapdoodle.embed.process.runtime.Executable de.flapdoodle.embed.process.runtime.Executable start AbstractPostgresConfig{storage=Storage{dbDir=/var/folders/8g/69wh31fn7nx3q81phwfdpld00000gn/T/postgresql-embed-66cfc41f-0e16-439f-a24b-6e5b6dbc683d/db-content-3bc4b9cc-dd21-43a7-9058-285767f5c53d, dbName='BLAH', isTmpDir=true}, network=Net{host='localhost', port=5432}, timeout=Timeout{startupTimeout=15000}, credentials=Credentials{BLAH, BLAH}, args=[BLAH], additionalInitDbParams=[]}
INFO:20161021 12:58:04: de.flapdoodle.embed.process.runtime.Executable de.flapdoodle.embed.process.runtime.Executable start AbstractPostgresConfig{storage=Storage{dbDir=/var/folders/8g/69wh31fn7nx3q81phwfdpld00000gn/T/postgresql-embed-66cfc41f-0e16-439f-a24b-6e5b6dbc683d/db-content-3bc4b9cc-dd21-43a7-9058-285767f5c53d, dbName='BLAH', isTmpDir=true}, network=Net{host='localhost', port=5432}, timeout=Timeout{startupTimeout=15000}, credentials=Credentials{BLAH, BLAH}, args=[], additionalInitDbParams=[-E, UTF-8, --locale=en_US.UTF-8, --lc-collate=en_US.UTF-8, --lc-ctype=en_US.UTF-8]}
embedded Postgres started
***END of Postgres DB Setup Process ***
...
[INFO] --- jetty-maven-plugin:9.1.2.v20140210:run-war (start-jetty) @ BLAH ---
[INFO] Configuring Jetty for project: BlahProject
[INFO] Context path = /
[INFO] Tmp directory = some/path/to/my/webapp/target/tmp
[INFO] Web defaults = org/eclipse/jetty/webapp/webdefault.xml
[INFO] Web overrides = none
[INFO] jetty-9.1.2.v20140210
[INFO] Scanned 1 container path jars, 133 WEB-INF/lib jars, 1 WEB-INF/classes dirs in 1887ms for context o.e.j.m.p.JettyWebAppContext@444942b0{/,file:/some/path/to/my/webapp/,STARTING}{/some/path/to/my/Application-2.3.33+46be96b464dc5b57b2e2e04ce31718a01360e5fb.war}
[INFO] Initializing Spring root WebApplicationContext
INFO:20161021 12:58:27: org.springframework.web.context.ContextLoader org.springframework.web.context.ContextLoader Root WebApplicationContext: initialization started
INFO:20161021 12:58:27: org.springframework.web.context.support.XmlWebApplicationContext org.springframework.context.support.AbstractApplicationContext Refreshing Root WebApplicationContext: startup date [Fri Oct 21 12:58:27 EDT 2016]; root of context hierarchy
INFO:20161021 12:58:27: org.springframework.beans.factory.xml.XmlBeanDefinitionReader org.springframework.beans.factory.xml.XmlBeanDefinitionReader Loading XML bean definitions from class path resource [spring/app-config.xml]
INFO:20161021 12:58:27: org.springframework.beans.factory.xml.XmlBeanDefinitionReader org.springframework.beans.factory.xml.XmlBeanDefinitionReader Loading XML bean definitions from class path resource [spring/db-config.xml]
INFO:20161021 12:58:28: org.springframework.beans.factory.support.DefaultListableBeanFactory org.springframework.beans.factory.support.DefaultListableBeanFactory Overriding bean definition for bean 'endpointLTERepository': replacing [Root bean: class [org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null] with [Root bean: class [org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean]; scope=; abstract=false; lazyInit=false; autowireMode=0; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=null; factoryMethodName=null; initMethodName=null; destroyMethodName=null]
[INFO] Started ServerConnector@3a8f9130{HTTP/1.1}{0.0.0.0:8080}
[INFO] Started Jetty Server
/ Конец вывода консоли Maven
Я признаю, что компонент, встроенный в postgresql, предназначен для запуска и завершения работы экземпляра базы данных Postgres в одном модульном тесте. Я пытаюсь найти способ использовать его, который идет дальше, чем первоначально предполагалось. По сути, я хотел бы, чтобы какой -то сервис, встроенный в postgresql, мог запускаться из плагина maven и впоследствии использоваться для завершения процесса postgres.
Любые предложения о том, как создать / построить такой сервис и / или плагин?
2 ответа
Основная проблема заключается в том, чтобы иметь возможность разделить некоторое состояние между двумя различными целями плагина: start
цель, которая запустит процесс, а затем stop
цель, которая убьет его. Хороший способ сделать это - использовать ContextEnabled
интерфейс, который реализуют все моджо. Это обеспечивает getPluginContext()
метод, который возвращает (необработанную) карту, в которой вы можете хранить объекты для совместного использования среди моджо.
При таком подходе вы можете сохранить то, что создали в start
цель плагина, и вернуть его в stop
Цель. Вот минималистичный пример, демонстрирующий это в действии, когда простое значение String используется совместно для mojos.
Настройте проект плагина Maven. Это в основном сводится к тому, чтобы иметь проект со следующим POM, который является стандартным POM для плагина Maven, с использованием Java 8 и аннотаций для конфигурации:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>sample.plugin</groupId>
<artifactId>test-maven-plugin</artifactId>
<version>1.0.0</version>
<packaging>maven-plugin</packaging>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>
<build>
<plugins>
<plugin>
<artifactId>maven-plugin-plugin</artifactId>
<version>3.5</version>
</plugin>
</plugins>
</build>
<dependencies>
<dependency>
<groupId>org.apache.maven</groupId>
<artifactId>maven-plugin-api</artifactId>
<version>3.3.9</version>
</dependency>
<!-- dependencies to annotations -->
<dependency>
<groupId>org.apache.maven.plugin-tools</groupId>
<artifactId>maven-plugin-annotations</artifactId>
<version>3.4</version>
<scope>provided</scope>
</dependency>
</dependencies>
</project>
Обратите внимание на упаковку типа maven-plugin
который заявляет Maven, что это проект плагина. В этом новом проекте рассмотрим следующее StartMojo
:
@Mojo(name = "start", defaultPhase = LifecyclePhase.PRE_INTEGRATION_TEST)
public class StartMojo extends AbstractMojo {
@SuppressWarnings("unchecked")
@Override
public void execute() throws MojoExecutionException {
getPluginContext().put("myService", new MyService("foo"));
}
}
Это объявляет новый start
Моджо, который по умолчанию связан с pre-integration-test
фаза. Он извлекает контекст плагина и помещает в него новый объект. В приведенном выше примере это простой пользовательский POJO MyService
который принимает значение в своем конструкторе. Этот объект сопоставлен с ключом "myService"
, который служит для поиска.
Тогда мы можем иметь:
@Mojo(name = "stop", defaultPhase = LifecyclePhase.POST_INTEGRATION_TEST)
public class StopMojo extends AbstractMojo {
@Override
public void execute() throws MojoExecutionException {
MyService service = (MyService) getPluginContext().get("myService");
getLog().info(service.getValue());
}
}
Это объявляет новый stop
Моджо, который по умолчанию связан с post-integration-test
фаза. Получает контекст плагина, извлекает объект под ключ "myService"
и, наконец, получить его значение и записывает его.
После упаковки и установки этого плагина Maven (с mvn clean install
) в локальный репозиторий, вы можете использовать его в примере проекта с
<plugin>
<groupId>sample.plugin</groupId>
<artifactId>test-maven-plugin</artifactId>
<executions>
<execution>
<id>sample</id>
<goals>
<goal>start</goal>
<goal>stop</goal>
</goals>
</execution>
</executions>
</plugin>
Если вы бежите mvn clean verify
в этом примере проекта, вы получите "foo"
напечатано в ваших журналах, в post-integration-test
фаза. Это показывает, что значение было правильно установлено start
моджо, а затем правильно извлекается stop
харизмы.
Конечно, вы можете хранить сложные объекты на этой карте, а не только String
(для которого могут быть более простые решения). Примечательно, что это может быть хост для вашего process
экземпляр, который вы хотите остановить. Вы можете избавиться от exec-maven-plugin
создайте новый плагин Maven, содержащий код, который вам уже нужен для настройки встроенной базы данных в start
цель, сохранить экземпляр процесса в контексте плагина для этой цели и, наконец, остановить этот процесс позже в другом stop
mojo, извлекая его из контекста плагина.
Продолжение: основываясь на предложении @Tunaki, я действительно создал свой собственный плагин maven, который использует форк из postgresql-embedded для запуска внутрипроцессной загрузки / установки / инициализации 9.2 postgreSQL DB.
Пришлось отработать несколько изломов, но этот подход мне удался.
Вот небольшой плагин maven, который можно использовать как оболочку maven для postgresql-embedded.