Sync dev stack

This commit is contained in:
2026-03-10 21:11:49 -06:00
parent c18ef16ef6
commit 8cb80d8ada
2 changed files with 172 additions and 0 deletions

View File

@@ -0,0 +1,154 @@
package com.petshop.backend;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.context.event.ContextClosedEvent;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.Duration;
import java.util.ArrayList;
import java.util.List;
import java.util.Locale;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;
public class DevStackApplication {
private static final Duration WATCH_INTERVAL = Duration.ofSeconds(2);
public static void main(String[] args) {
DevStackController controller = new DevStackController();
ConfigurableApplicationContext context = null;
CountDownLatch shutdownLatch = new CountDownLatch(1);
try {
controller.startDatabase();
context = new SpringApplicationBuilder(BackendApplication.class).run(args);
ConfigurableApplicationContext appContext = context;
context.addApplicationListener(event -> {
if (event instanceof ContextClosedEvent) {
shutdownLatch.countDown();
}
});
controller.watchDatabase(appContext);
shutdownLatch.await();
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Dev stack interrupted", ex);
} finally {
controller.stopWatching();
if (context != null && context.isActive()) {
context.close();
}
controller.stopDatabase();
}
}
private static final class DevStackController {
private final Path projectDir = Paths.get("").toAbsolutePath();
private final AtomicBoolean shuttingDown = new AtomicBoolean(false);
private ScheduledExecutorService scheduler;
private void startDatabase() {
runCommand(composeCommand("up", "-d", "--wait", "db"));
}
private void stopDatabase() {
if (!shuttingDown.compareAndSet(false, true)) {
return;
}
stopWatching();
runCommand(composeCommand("stop", "db"));
}
private void watchDatabase(ConfigurableApplicationContext context) {
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleWithFixedDelay(() -> {
if (shuttingDown.get()) {
return;
}
if (!isDatabaseRunning()) {
shuttingDown.set(true);
if (context.isActive()) {
context.close();
}
}
}, WATCH_INTERVAL.toSeconds(), WATCH_INTERVAL.toSeconds(), TimeUnit.SECONDS);
}
private void stopWatching() {
if (scheduler != null) {
scheduler.shutdownNow();
scheduler = null;
}
}
private boolean isDatabaseRunning() {
CommandResult result = runCommand(composeCommand("ps", "--status", "running", "--services", "db"), false);
return result.exitCode == 0 && result.output.lines()
.map(String::trim)
.anyMatch("db"::equals);
}
private List<String> composeCommand(String... args) {
List<String> command = new ArrayList<>();
command.add(resolveDockerExecutable());
command.add("compose");
command.add("-f");
command.add("docker-compose.dev.yml");
for (String arg : args) {
command.add(arg);
}
return command;
}
private String resolveDockerExecutable() {
String os = System.getProperty("os.name", "").toLowerCase(Locale.ROOT);
return os.contains("win") ? "docker.exe" : "docker";
}
private void runCommand(List<String> command) {
CommandResult result = runCommand(command, true);
if (result.exitCode != 0) {
throw new IllegalStateException(result.output.isBlank() ? "Command failed: " + String.join(" ", command) : result.output);
}
}
private CommandResult runCommand(List<String> command, boolean printOutput) {
ProcessBuilder builder = new ProcessBuilder(command);
builder.directory(projectDir.toFile());
builder.redirectErrorStream(true);
try {
Process process = builder.start();
StringBuilder output = new StringBuilder();
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
output.append(line).append(System.lineSeparator());
if (printOutput) {
System.out.println(line);
}
}
}
int exitCode = process.waitFor();
return new CommandResult(exitCode, output.toString().trim());
} catch (IOException ex) {
throw new IllegalStateException("Unable to run docker command", ex);
} catch (InterruptedException ex) {
Thread.currentThread().interrupt();
throw new IllegalStateException("Docker command interrupted", ex);
}
}
}
private record CommandResult(int exitCode, String output) {
}
}