Как использовать Java-компонент Embedded PostgreSQL Server в качестве отдельной службы?

Я пытаюсь создать комплексный набор тестов интеграции для Java-приложения RESTful (Services), которое работает в Tomcat(7.x) и зависит от экземпляра Postgresql (9.x). Кроме того, я хотел бы иметь возможность запускать этот пакет как отдельный процесс, исключительно из maven 3.x, если это возможно, с помощью отказоустойчивого плагина maven. Таким образом, тесты можно будет запускать на 3 основных платформах (Mac OSX, Linux и Windows).

Из того, что я узнал, я считаю, что ключ к тому, чтобы это произошло, состоит в том, чтобы сделать что-то вроде этих шагов (в следующем порядке):

  1. Как-нибудь запустить "встроенную" версию базы данных postgresql (и настроить схему) на этапе тестирования перед интеграцией жизненного цикла maven, чтобы этот процесс postgres-db работал в фоновом режиме
  2. Запустите мой контейнер Java, который загружает мое приложение служб: я использую плагин Jetty 9.1.5
  3. Запустите мои (основанные на JUnit) тесты из плагина Failsafe на этапе тестирования интеграции
  4. Завершите мой контейнер Jetty на этапе тестирования после интеграции maven
  5. Наконец, есть какой-то механизм, закрывающий ранее запущенный (фоновый) процесс 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.

Другие вопросы по тегам