← Back to Home

Modular monolith with Spring Modulith (arc-transit)

The problem

Arc-transit is a bus transit management system — fleet registration, driver management, route planning, dispatching, incident reports, maintenance tracking, the whole thing. It's a university project but I wanted to build it properly, not just dump everything into one giant package and call it a day.

The issue with most monolithic Spring Boot apps I've seen is that over time, everything ends up depending on everything else. The driver module imports something from dispatch, dispatch reaches into fleet internals, and before you know it you can't change anything without breaking three other things.

Why not microservices

Microservices would technically solve the coupling problem, but for a system this size it's overkill. I'd need separate databases, service discovery, inter-service communication, deployment pipelines for each service — all for a university project that runs on one machine. The operational complexity just wasn't worth it.

The approach: Spring Modulith

I used Spring Modulith, which gives you the module isolation of microservices but inside a single deployable application. Each business domain — fleet, driver, route, dispatch, incident, maintenance, audit, auth — lives in its own top-level package under com.transit.arctransit.

The key thing Spring Modulith does is enforce boundaries at test time. I have a ModularityTests test class that verifies the module structure. If the dispatch module tries to directly access fleet internals instead of going through the public API, the test fails. It's like having an architecture linter built into your test suite.

How it works in practice

Each module exposes a clean public API (service interfaces, DTOs) and keeps everything else package-private. Flyway handles database migrations with versioned SQL scripts, so each module's schema changes are tracked and applied in order. There's also an append-only audit log that captures every business action across all modules.

The nice thing about this setup is that if I ever needed to extract a module into its own service, the boundaries are already clean. But for now, it's one JAR, one database, one deployment — and the architecture tests keep the boundaries honest.