|
| 1 | +# Spring Local PostgreSQL |
| 2 | + |
| 3 | +## Description |
| 4 | +Provides configuration of an embedded PostgreSQL database (via Testcontainers) |
| 5 | +within Spring Boot Applications for local development and testing. |
| 6 | + |
| 7 | +Requires minimal configuration using Spring conventions, but a variety of optional properties are supported to override default behavior. |
| 8 | + |
| 9 | +## Rationale |
| 10 | +When developing an Application that uses PostgreSQL in production, an embedded PostgreSQL server provides the benefits of using an in-memory database, like H2, but avoids the downsides. The database is spun up and torn down when the Application starts up and shuts down, but developers are able to utilize PostgreSQL features (that alternatives like H2 may not support) and the test and local environments better resemble production. Development is more effective and reliable. |
| 11 | + |
| 12 | +While Testcontainers is meant to be used exclusively in support of integration tests, this module is designed to support running the Application locally as well, reducing the overhead of maintaining Testcontainers and some other solution that fundamentally does the same thing. There is no need to maintain a local PostgreSQL server nor additional Docker configuration inside or outside the project. |
| 13 | + |
| 14 | +## Requirements |
| 15 | +### Java 17 |
| 16 | +https://adoptium.net/temurin/releases/?version=17 |
| 17 | + |
| 18 | +### Docker |
| 19 | +https://www.docker.com/products/docker-desktop/ |
| 20 | + |
| 21 | +### PostgreSQL JDBC Driver |
| 22 | +https://jdbc.postgresql.org<br> |
| 23 | +```xml |
| 24 | +<dependency> |
| 25 | + <groupId>org.postgresql</groupId> |
| 26 | + <artifactId>postgresql</artifactId> |
| 27 | + <scope>runtime</scope> |
| 28 | +</dependency> |
| 29 | +``` |
| 30 | + |
| 31 | +### Testcontainers PostgreSQL |
| 32 | +Version `1.18.3` is included as a transitive dependency.<br> |
| 33 | +https://www.testcontainers.org/modules/databases/postgres |
| 34 | +```xml |
| 35 | +<dependency> |
| 36 | + <groupId>org.testcontainers</groupId> |
| 37 | + <artifactId>postgresql</artifactId> |
| 38 | + <version>1.18.3</version> |
| 39 | +</dependency> |
| 40 | +``` |
| 41 | + |
| 42 | +### Spring Boot 3 |
| 43 | +A variety of modules from version `3.1.2` are included as transitive dependencies.<br> |
| 44 | +https://spring.io/projects/spring-boot |
| 45 | +```xml |
| 46 | +<dependencyManagement> |
| 47 | + <dependencies> |
| 48 | + <dependency> |
| 49 | + <groupId>org.springframework.boot</groupId> |
| 50 | + <artifactId>spring-boot-dependencies</artifactId> |
| 51 | + <version>3.1.2</version> |
| 52 | + <type>pom</type> |
| 53 | + <scope>import</scope> |
| 54 | + </dependency> |
| 55 | + </dependencies> |
| 56 | +</dependencyManagement> |
| 57 | +``` |
| 58 | +```xml |
| 59 | +<dependencies> |
| 60 | + <dependency> |
| 61 | + <groupId>org.springframework.boot</groupId> |
| 62 | + <artifactId>spring-boot-starter-web</artifactId> |
| 63 | + </dependency> |
| 64 | + <dependency> |
| 65 | + <groupId>org.springframework.boot</groupId> |
| 66 | + <artifactId>spring-boot-configuration-processor</artifactId> |
| 67 | + </dependency> |
| 68 | +</dependencies>> |
| 69 | +``` |
| 70 | +## Usage |
| 71 | + |
| 72 | +### Configuration |
| 73 | + |
| 74 | +First, add this module as a dependency in your project: |
| 75 | +```xml |
| 76 | +<dependency> |
| 77 | + <groupId>io.github.quinnandrews</groupId> |
| 78 | + <artifactId>spring-local-postgresql</artifactId> |
| 79 | + <version>1.0-SNAPSHOT</version> |
| 80 | +</dependency> |
| 81 | +``` |
| 82 | + |
| 83 | +Next, add `@EnableLocalPostreSQL` to your Spring Boot Application Class: |
| 84 | +```java |
| 85 | +@EnableLocalPostgreSQL |
| 86 | +@SpringBootApplication |
| 87 | +public class Application { |
| 88 | + |
| 89 | + public static void main(final String[] args) { |
| 90 | + SpringApplication.run(Application.class, args); |
| 91 | + } |
| 92 | +} |
| 93 | +``` |
| 94 | + |
| 95 | +Then define the following property for the profiles that should initialize the embedded PostgreSQL: |
| 96 | +```properties |
| 97 | +spring.local.postgresql.engaged=true |
| 98 | +``` |
| 99 | + |
| 100 | +If desired, define any of the following properties to override default behavior (see below for detailed descriptions): |
| 101 | + |
| 102 | +```properties |
| 103 | +spring.local.postgresql.container.image=postgres:15 |
| 104 | +spring.local.postgresql.container.port=15432 |
| 105 | +spring.local.postgresql.container.log.follow=true |
| 106 | +spring.local.postgresql.database.name=local |
| 107 | +spring.local.postgresql.database.username=superuser |
| 108 | +spring.local.postgresql.database.password=password |
| 109 | +spring.local.postgresql.database.application.username=appuser |
| 110 | +spring.local.postgresql.database.application.password=password |
| 111 | +spring.local.postgresql.database.init.script=data/init.sql |
| 112 | +``` |
| 113 | + |
| 114 | +### Configuration as a Test Dependency |
| 115 | +Understandably, one may prefer this module as a test dependency rather than a compile dependency. But this means that all configuration for this module can only reside in your project's test source, which is fine for integration tests, but how, then, can you run the Application locally? |
| 116 | + |
| 117 | +To do this we implement an approach surfaced in this [article](https://bsideup.github.io/posts/local_development_with_testcontainers/) by Sergei Egorov. |
| 118 | + |
| 119 | +First, add this module as a test dependency in your project: |
| 120 | +```xml |
| 121 | +<dependency> |
| 122 | + <groupId>io.github.quinnandrews</groupId> |
| 123 | + <artifactId>spring-local-postgresql</artifactId> |
| 124 | + <version>1.0-SNAPSHOT</version> |
| 125 | + <scope>test</scope> |
| 126 | +</dependency> |
| 127 | +``` |
| 128 | +Next, add a Spring Boot Application Class to your project's test source that includes the `@EnableLocalPostgreSQL` Annotation and passes the Application Class in your project's main source to the `run` method (so that Configuration in the main source is initialized): |
| 129 | +```java |
| 130 | +@EnableLocalPostgreSQL |
| 131 | +@SpringBootApplication |
| 132 | +public class LocalDevApplication { |
| 133 | + |
| 134 | + public static void main(final String[] args) { |
| 135 | + SpringApplication.run(Application.class, args); |
| 136 | + } |
| 137 | +} |
| 138 | +``` |
| 139 | + |
| 140 | +Then you can reference `LocalApplication.class` in your integration tests to enable the embedded PostgreSQL by using the `classes` attribute on `@SpringBootTest`: |
| 141 | +```java |
| 142 | +@ActiveProfiles("test") |
| 143 | +@SpringBootTest(classes = LocalApplication.class) |
| 144 | +class IntegrationTest { |
| 145 | + |
| 146 | +} |
| 147 | +``` |
| 148 | + |
| 149 | +Or, alternatively, your integration tests can ignore `LocalApplication.class` and enable the embedded PostgreSQL by using `@EnableLocalPostgreSQL`: |
| 150 | +```java |
| 151 | +@EnableLocalPostgreSQL |
| 152 | +@ActiveProfiles("test") |
| 153 | +@SpringBootTest |
| 154 | +class IntegrationTest { |
| 155 | + |
| 156 | +} |
| 157 | +``` |
| 158 | +Then you can create a `Run Configuration` in IntelliJ IDEA, for example, setting `LocalApplication.class` as the `Main Class` and defining `local` as the active profile. |
| 159 | + |
| 160 | +Now when you run the `Run Configuration` or your integration tests, the embedded PostgreSQL will be initialized, assuming that the `local` and `test` profiles have set the `spring.local.postgresql.engaged` property to `true'.` |
| 161 | + |
| 162 | +**But there is a way to make this even more convenient.** |
| 163 | + |
| 164 | +If your integration tests reference `LocalApplication.class`, it's worth noting that the `main` method isn't actually executed in that case. Spring is doing something different. |
| 165 | + |
| 166 | +What this means is that you can activate the `local` profile explicitly in the body of the `main` method: |
| 167 | +```java |
| 168 | +@EnableLocalPostgreSQL |
| 169 | +@SpringBootApplication |
| 170 | +public class LocalApplication { |
| 171 | + |
| 172 | + public static void main(final String[] args) { |
| 173 | + final var springApplication = new SpringApplication(Application.class); |
| 174 | + springApplication.setAdditionalProfiles("local"); |
| 175 | + springApplication.run(args); |
| 176 | + } |
| 177 | +} |
| 178 | +``` |
| 179 | + |
| 180 | +Now you can run the Application locally in IntelliJ IDEA by simply right-clicking on `LocalApplication.class` in the Project Panel and selecting `Run 'LocalApplication'` from the Context Menu, which will also create a `Run Configuration` for later use. |
| 181 | + |
| 182 | +### Supported Configuration Properties |
| 183 | + |
| 184 | +spring.local.postgresql.engaged |
| 185 | +: Whether the embedded, containerized PostgreSQL database should be configured to start when the Application starts. By default, it is not engaged. Only engaged if `true`. |
| 186 | + |
| 187 | +spring.local.postgresql.container.image |
| 188 | +: The name of a Docker Image containing a given version of PostgreSQL (example: `postgres:15`). If undefined, the Testcontainers default of `postgres:9.6.12` is used. |
| 189 | + |
| 190 | +spring.local.postgresql.container.port |
| 191 | +: The port on the Docker Container that should map to the port used by PostgreSQL inside the container. If undefined, a random port is used, which is preferred for integration tests, but when running the Application locally, defining a fixed port is useful. It gives developers the ability to configure a JDBC client with a consistent port. Otherwise, the port in the client's configuration must be updated if the Application had been restarted since the client was last used. |
| 192 | + |
| 193 | +spring.local.postgresql.container.log.follow |
| 194 | +: Whether the Application should log the log output produced by the Container. By default, container logs are not followed. Set with `true` to see their output. |
| 195 | + |
| 196 | +spring.local.postgresql.database.name |
| 197 | +: The name of the PostgreSQL database the Application will connect to. If undefined, defaults to the Testcontainers default of `test`. |
| 198 | + |
| 199 | +spring.local.postgresql.database.username |
| 200 | +: The username of an admin or superuser in the PostgreSQL database. If no `spring.local.postgresql.database.application.username` is defined, this will also be the username the Application uses to connect. If undefined, defaults to the Testcontainers default of `test`. |
| 201 | + |
| 202 | +spring.local.postgresql.database.password |
| 203 | +: The password that goes with the username of the admin or superuser in the PostgreSQL database. If no `spring.local.postgresql.database.application.username` is defined, this will also be the password for the username the Application uses to connect. If undefined, defaults to the Testcontainers default of `test`. |
| 204 | + |
| 205 | +spring.local.postgresql.database.application.username |
| 206 | +: In most cases the database user used by the Application should not have admin or superuser privileges. This property provides the ability to define the username of an "application user" for use during testing and local development. The Application will use this username to connect to the PostgreSQL database. If undefined, the value defined by `spring.local.postgresql.database.username` will be used instead. NOTE: This application user is NOT automatically created. An init-script is required to create the user and grant their initial privileges. |
| 207 | + |
| 208 | +spring.local.postgresql.database.application.password |
| 209 | +: In most cases the database user used by the Application should not have admin or superuser privileges. This property provides the ability to define the password for the username of an "application user" for use during testing and local development. The Application will use this password to connect to the PostgreSQL database. If undefined, the value defined by `spring.local.postgresql.database.password` will be used instead. NOTE: This application user is NOT automatically created. An init-script is required to create the user and grant their initial privileges. |
| 210 | + |
| 211 | +spring.local.postgresql.database.init.script |
| 212 | +: The path to an SQL file (beginning with the `resources` directory as the root) that should be executed when the Docker Container starts. Executes before migrations. Useful for administrative tasks, like creating additional users, for example. If undefined, no script is executed. |
| 213 | + |
| 214 | +## Examples |
| 215 | + |
| 216 | +The test source contains an example Application as well as integration tests that test against it. They can be referenced as examples, if needed. |
| 217 | + |
| 218 | +However, a more robust, true to life example will be provided in the near future. |
| 219 | + |
| 220 | +## Roadmap |
| 221 | +1) **Provide Robust Example** |
| 222 | +2) **Support Option to Activate pgAdmin.** |
| 223 | +3) **Support Testcontainers Reuse Property.** |
0 commit comments