0

I have been struggling setting up a new project for weeks. Desired project properties:

Goals:

  • Springboot 2.7.3 Kotlin and Gradle
  • Java 18 or Kotlin SDK
  • MySQL Latest
  • Gradle Latest
  • IDE Intellij Latest
  • Docker Compose on WSL, web server and db server (with working communication link)
  • Hot reload and other development tool features working.
  • NPM where required for package management.

Problems

  • One of the problems I am finding is that I have way to many ways to start the application; npm start, gradle bootrun, docker-compose up app and others. I don't know when to use each and each doesn't seem to fully function for a good dev setup.
  • mysql connection issues are constant, seems different configs are required depending how the app is started.
  • application.yml, I'm not confident when this is being used in each run configuration.

The Application Very simple hello world REST API, simple hello world DB tables, JPA, spring web, lombok.

application.yml

development: true
server:
  port: '8080'
  servlet:
    context-path: /api
spring:
  datasource:
    url: 'jdbc:mysql://localhost:3306/farnsworthy'
    username: farnsworthy
    password: password
  jpa:
    hibernate:
      ddl-auto: update
    database-platform: org.hibernate.dialect.MySQL8Dialect
  sql:
    init:
      mode: always

Dockerfile

FROM openjdk:18-alpine
WORKDIR /app
LABEL maintainer="support@farnsworthy.app"
VOLUME /main-app
ADD build/libs/farnsworthy-pics-backend-0.0.1-SNAPSHOT.jar /app/app.jar
ADD /src/main/resources/application.properties /app/application.properties
EXPOSE 8080 5005
ENTRYPOINT ["java", "-Dspring.config.location=/app/application-properties", "-jar","/app/app.jar"]

Docker-compose.yml

version: '3.8'
services:
  api_service:
    build: .
    container_name: api_service
    restart: always
    ports:
      - "8080:8080"
      - "5005:5005"
    depends_on:
      - mysql_db
    command: sh -c './wait-for mysql_db:3306 -- npm start'
  mysql_db:
    image: "mysql:8.0"
    container_name: mysql_db
    restart: always
    ports:
      - "3306:3306"
    expose:
      - "3306"
    environment:
      MYSQL_DATABASE: farnsworthy
      MYSQL_USER: farnsworthy
      MYSQL_PASSWORD: password
      MYSQL_ROOT_PASSWORD: password

build.gradle.kts

import org.jetbrains.kotlin.gradle.tasks.KotlinCompile

plugins {
    id("org.springframework.boot") version "2.7.3"
    id("io.spring.dependency-management") version "1.0.13.RELEASE"
    id("idea")
    kotlin("jvm") version "1.6.21"
    kotlin("plugin.spring") version "1.6.21"
    kotlin("plugin.jpa") version "1.6.21"
}

group = "com.farnsworthy.pics"
version = "0.0.1-SNAPSHOT"
java.sourceCompatibility = JavaVersion.VERSION_17

configurations {
    compileOnly {
        extendsFrom(configurations.annotationProcessor.get())
    }
}

repositories {
    mavenCentral()
}


dependencies {
    implementation("org.springframework.boot:spring-boot-starter-data-jpa")
    implementation("org.springframework.boot:spring-boot-starter-data-rest")
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("com.fasterxml.jackson.module:jackson-module-kotlin")
    implementation("org.jetbrains.kotlin:kotlin-reflect")
    implementation("org.jetbrains.kotlin:kotlin-stdlib-jdk8")
    //implementation("org.springframework.data:spring-data-rest-hal-explorer")
    compileOnly("org.projectlombok:lombok")
    implementation("org.springframework.boot:spring-boot-devtools")
    runtimeOnly("mysql:mysql-connector-java")
    annotationProcessor("org.projectlombok:lombok")
    testImplementation("org.springframework.boot:spring-boot-starter-test")
}

tasks.withType<KotlinCompile> {
    kotlinOptions {
        freeCompilerArgs = listOf("-Xjsr305=strict")
        jvmTarget = "18"
    }

}

tasks.withType<Test> {
    useJUnitPlatform()
}
gunslingor
  • 1,358
  • 12
  • 34
  • FWIW, Spring boot has a Gradle action to build containers, so you don't need a Dockerfile. And you definitely shouldn't hard-core your jar version into it if your goal is to reduce development friction. In any case, compiled languages aren't exactly "hot swappable" – OneCricketeer Sep 19 '22 at 13:56
  • Thanks. My issue is really that getting all these tools to work in an optimal development environment is complicated and often the tutorials are obsolete in one or more of these tools... because of the cross dependencies, I don't think there are any complete tutorials from the actual companies so third party blogs is all I find and they are old. But I have re-learnt of gradle build --continuous, but don't understand why there isn't a default devRun task. So many questions, just looking for the best solution with these tools. Good info and thanks. – gunslingor Sep 24 '22 at 15:03
  • I've only used Gradle with Android, and the Android plugin does have a default run task. That's to say that Gradle is plugin based, and you need to refer to the official plugin documentation to know what's supported or not. And as mentioned, JVM languages will need recompiled after any changes you make – OneCricketeer Sep 25 '22 at 12:36

1 Answers1

1

I'm having a bit of a hard time understanding your project structure, but I'll make assumptions:

In production you have two processes:

  1. A spring boot application which also serves the frontend (npm part)
  2. A MySQL database.

If thats so then how I would do in dev is have three separare things:

  1. Only MySQL running in docker.
  2. Backend will be started with Gradle or from IntelliJ from Kotlins main function with Run/Debug. It might serve built frondend too, but that's not the one you'll be using in dev environment.
  3. Frontend will be started separately with npm start and you can configure it from package.json to proxy to your backend port in dev.

Now if you want to build for production you might want to extend your Gradle script to invoke npm build also (Check this out: https://github.com/srs/gradle-node-plugin/). And if all is built, it is a single JAR file which can be smashed into one single docker container, because in reality in production your app will be in a single process, except the database.

Trying to get it all in-docker-single-build-works-with-IDE is too much IMHO because you often times don't even need to spin up the whole stack to develop something.

Also spinning things up separately in dev gives you lot more flexibility. You can run any random combinations of backend/frontend (one in debug mode, other with "watch" for example). If you'd do it through docker-compose, it would be a lot harder.

Tarmo
  • 3,851
  • 2
  • 24
  • 41
  • There are tools that allow for hot swapping within containers, though. E.g https://github.com/okteto/okteto – OneCricketeer Sep 19 '22 at 13:54
  • @OneCricketeer Nice to know. But my experience still shows me that it's beneficial to keep things open in your dev environment. As I said, it gives you a lot more flexibility. – Tarmo Sep 19 '22 at 15:00
  • What do you mean "open"? – OneCricketeer Sep 19 '22 at 15:54
  • @OneCricketeer I mean what I meant in last 3 sentences of my answer. – Tarmo Sep 20 '22 at 07:31
  • Thank you. I found the below and life is better. I should have been focusing on gradle this whole time in my native windows environment. I sort of made the assumption that the default gradle config was all I needed for a good dev setup, but there is no default devRun task... guess I got to define it... once I got that working as best as possible, I can bring back docker and WSL more trivially I suspect. https://stackoverflow.com/questions/52092504/spring-boot-bootrun-with-continuous-build/73838212#73838212 – gunslingor Sep 24 '22 at 15:11