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

#java #postgresql #maven #integration-testing #embedded-database

#java #postgresql #maven #интеграция-тестирование #встроенная база данных

Вопрос:

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

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

  1. Запустите «встроенную» версию базы данных postgresql (и настройте схему) каким-либо образом на этапе предварительного интеграционного тестирования жизненного цикла maven, чтобы этот postgres-db-process выполнялся в фоновом режиме
  2. Запустите мой Java-контейнер, который загружает мое приложение Services: я использую плагин Jetty 9.1.5
  3. Запустите мои тесты (на основе JUnit) из отказоустойчивого плагина на этапе тестирования интеграции
  4. Завершите работу моего контейнера Jetty на этапе maven после интеграции-тестирования
  5. Наконец, есть какой-нибудь механизм завершения ранее запущенного (фонового) процесса postgres-db на этапе жизненного цикла после интеграции-тестирования (убить / очистить этот процесс)

В моей текущей реализации шаги 1-3 успешно завершены. На шаге 2 используется exec-maven-plugin, который вызывает класс Java, который использует встроенный в postgresql Java-компонент. Выдержка из 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. Итак, в настоящее время у меня нет возможности завершить процесс DB. Как только я выйду из этого класса DbSetup , все ссылки на этот существующий процесс будут потеряны. И процесс Postgres никогда не завершается. На самом деле, похоже, что контейнер jetty также не завершит работу, и все задание 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 DB в рамках одного модульного теста. Я пытаюсь найти способ его использования, который выходит за рамки первоначально предполагаемого варианта использования. По сути, я хотел бы какой-нибудь встроенный в postgresql сервис, который можно запускать из плагина maven и впоследствии использовать для завершения этого процесса postgres.

Есть предложения по созданию такого сервиса и / или плагина?

Ответ №1:

Основная проблема здесь заключается в том, чтобы иметь возможность разделить некоторое состояние между двумя разными целями плагина: start целью, которая запускает процесс, а затем stop целью, которая его останавливает. Хороший способ сделать это — использовать ContextEnabled интерфейс, который реализуют все mojos. Он предоставляет getPluginContext() метод, который возвращает (необработанную) карту, в которой вы можете хранить объекты для совместного использования среди mojos.

При таком подходе вы можете сохранить то, что вы создали в start цели плагина, и получить это обратно в stop цели. Вот минималистичный пример, чтобы показать это в действии, где простое строковое значение разделяется между 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 mojo, который по умолчанию привязан к 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 mojo, а затем правильно извлечено stop mojo.

Конечно, вы можете хранить сложные объекты на этой карте, а не только String (для которых могли бы быть более простые решения). Примечательно, что это может быть хост для вашего process экземпляра, который вы хотите остановить. Вы можете избавиться от exec-maven-plugin , создать новый плагин Maven, содержащий код, который у вас уже есть для настройки встроенной базы данных в start цели, сохранить экземпляр процесса в контексте плагина в этой цели и, наконец, остановить этот процесс позже в другом stop mojo, извлекая его из контекста плагина.

Комментарии:

1. Спасибо за ваш ответ, @Tunaki. Отличный ответ.

2. Последующий вопрос для @Tunaki. Итак, если я следую вашей логике, я должен иметь возможность использовать этот новый плагин maven, который я бы разработал для управления этим новым пользовательским сервисом, который запускает и останавливает встроенный экземпляр postgres в 2 разных этапа: запуск при предварительном тестировании интеграции и остановка при тестировании после интеграции. Часть, с которой я борюсь, заключается в том, как я могу гарантировать, что мой пользовательский сервис остается в области видимости / памяти между этими двумя фазами, поскольку это всего лишь класс Java с методом Main. Это другая часть, которую я изо всех сил пытаюсь понять / выяснить.

3. @aramcodez Правильно, экземпляр запущен в предварительном интеграционном тестировании и остановлен в тестировании после интеграции. Maven гарантирует, что эти две фазы всегда выполняются при запуске интеграционных тестов, поэтому он также гарантирует, что объект живет. Объект, помещенный в контекст плагина, никогда не удаляется во время сборки, поэтому вы можете извлечь его в любое время.

Ответ №2:

Продолжение: Основываясь на предложении @Tunaki, я действительно создал свой собственный плагин maven, который использует форк postgresql-embedded для запуска загрузки / установки / инициализации базы данных PostgreSQL 9.2 в процессе.

Пришлось устранить несколько недочетов, но этот подход мне удался.

Вот небольшой плагин maven, который можно использовать в качестве maven-оболочки для postgresql-embedded.