Project: Tenant Management: An Evolutionary Project
Evolution: Evolution 3: Java Enterprise Stack
Focus: Spring Boot Migration
Status: 🔄 Active
Building on the hands-on migration I shared in Tenant Management App: Spring Boot and React Transition, this post documents the current architecture baseline for the new Java-based system. Right now the focus is scaffolding: wiring Spring Boot, Flyway, and the React frontend so the upcoming feature work has a solid foundation, while still keeping the modular principles from the earlier modular monolith evolution in sight.
Evolution Context: This post is part of Evolution 3: Java Enterprise Stack in the Tenant Management Evolutionary Project. This evolution focuses on enterprise patterns and Spring Boot migration, building upon the modular architecture established in Evolution 2.
The application is still a single deployable unit, but each layer now has its own responsibilities. Spring Boot handles the API shell, Flyway protects the schema, and the React client keeps the presentation layer independent while the Java endpoints come online.
graph TB
subgraph "Client Layer"
react["React SPA"]
end
subgraph "API Layer"
spring["Spring Boot Application"]
flyway["Flyway Migrations"]
jpa["Spring Data JPA"]
end
subgraph "Database Layer"
h2["H2 (dev profile)"]
postgres["PostgreSQL (prod profile)"]
end
subgraph "Infrastructure Layer"
actuator["Spring Boot Actuator"]
compose["Docker Compose"]
end
react -->|HTTP/JSON| spring
spring --> jpa
flyway --> h2
flyway --> postgres
jpa --> h2
jpa --> postgres
spring --> actuator
compose --> spring
compose --> react
compose --> postgres
%% Styling
classDef frontend fill:#4fc3f7,stroke:#0277bd,stroke-width:3px,color:#000
classDef backend fill:#66bb6a,stroke:#2e7d32,stroke-width:3px,color:#fff
classDef database fill:#42a5f5,stroke:#1565c0,stroke-width:3px,color:#fff
classDef infrastructure fill:#ff7043,stroke:#d84315,stroke-width:3px,color:#fff
class react frontend
class spring,jpa,flyway backend
class h2,postgres database
class actuator,compose infrastructure
TenantManagementApplication bootstrap loads Spring Web, Validation, Data JPA, Actuator, Flyway, and Springdoc so controllers can be dropped in with minimal ceremony./actuator/health and friends for container checks and future monitoring.graph TD
subgraph Web Layer
controllers["Controllers<br/>tenant/property/transaction"]
advice["GlobalExceptionHandler"]
end
subgraph Service Layer
tenantSvc["TenantService"]
propertySvc["PropertyService"]
transactionSvc["TransactionService"]
end
subgraph Persistence Layer
tenantRepo["TenantRepository"]
propertyRepo["PropertyRepository"]
transactionRepo["TransactionRepository"]
domain["Domain Entities"]
end
controllers --> tenantSvc
controllers --> propertySvc
controllers --> transactionSvc
advice --> controllers
tenantSvc --> tenantRepo
tenantSvc --> propertyRepo
tenantSvc --> transactionRepo
propertySvc --> propertyRepo
transactionSvc --> transactionRepo
tenantRepo --> domain
propertyRepo --> domain
transactionRepo --> domain
%% Styling
classDef web fill:#4fc3f7,stroke:#0277bd,stroke-width:3px,color:#000
classDef service fill:#66bb6a,stroke:#2e7d32,stroke-width:3px,color:#fff
classDef repo fill:#42a5f5,stroke:#1565c0,stroke-width:3px,color:#fff
class controllers,advice web
class tenantSvc,propertySvc,transactionSvc service
class tenantRepo,propertyRepo,transactionRepo,domain repo
This mirrors the current package layout: web/ hosts controllers and exception handling, service/ contains transactional logic, and repository/ bridges into the domain entities generated by Flyway.
graph LR
react["React Components<br/>(Navigation, Tenants, Properties, Transactions)"]
axiosClient["Axios HTTP Calls"]
restApi["/api/** Endpoints<br/>(Tenant/Property/Transaction)"]
services["Business Services"]
repositories["Spring Data Repositories"]
flyway["Flyway Seeded Schema"]
database["H2 / PostgreSQL"]
react --> axiosClient --> restApi --> services --> repositories --> database
flyway --> database
%% Styling
classDef frontend fill:#4fc3f7,stroke:#0277bd,stroke-width:3px,color:#000
classDef backend fill:#66bb6a,stroke:#2e7d32,stroke-width:3px,color:#fff
classDef database fill:#42a5f5,stroke:#1565c0,stroke-width:3px,color:#fff
class react,axiosClient frontend
class restApi,services,repositories,flyway backend
class database database
The diagram highlights the runtime flow: React screens call the axios client, which targets the REST controllers. Those controllers delegate to Spring services and repositories, while Flyway keeps the database schema aligned across H2 and PostgreSQL profiles.
// backend/src/main/java/com/example/tenantmanagement/TenantManagementApplication.java
@SpringBootApplication
public class TenantManagementApplication {
public static void main(String[] args) {
SpringApplication.run(TenantManagementApplication.class, args);
}
}
SPRING_PROFILES_ACTIVE environment variable.// frontend/src/components/Tenants.js
const handleSubmit = async (e) => {
e.preventDefault();
try {
const payload = toPayload(form);
if (editingId) {
await axios.put(`/api/tenants/${editingId}`, payload);
toast.success('Tenant updated');
} else {
await axios.post('/api/tenants', payload);
toast.success('Tenant added');
}
setForm(initialForm);
setEditingId(null);
setOpenForm(false);
fetchTenants();
} catch (e) {
toast.error('Save failed');
}
};
POST/PUT calls) and refreshes the tenant list via fetchTenants.@mui/material and @mui/icons-material remain the backbone of the interface, aligning with the existing design system.react-hot-toast surfaces success and error states for every API interaction, matching the patterns established in earlier versions.# docker-compose.yml
services:
api:
build: ./backend
container_name: tenantmgmt_api
ports:
- "8080:8080"
environment:
SPRING_PROFILES_ACTIVE: ${PROFILE:-dev}
SPRING_DATASOURCE_URL: jdbc:postgresql://db:5432/tenantdb
SPRING_DATASOURCE_USERNAME: tenant
SPRING_DATASOURCE_PASSWORD: tenant
volumes:
- h2-data:/app/data
frontend:
build: ./frontend
container_name: tenant-management-frontend
ports:
- "3000:3000"
depends_on:
- api
db:
image: postgres:16
container_name: tenantmgmt_db
profiles:
- prod
environment:
POSTGRES_DB: tenantdb
POSTGRES_USER: tenant
POSTGRES_PASSWORD: tenant
volumes:
- pgdata:/var/lib/postgresql/data
volumes:
pgdata:
h2-data:
docker compose up defaults to the dev/H2 profile; setting PROFILE=prod introduces the Postgres container./actuator/health is exposed by default so Compose (or a future orchestrator) can monitor the API container.h2-data volume keeps the embedded H2 database files available between runs.This baseline keeps the independence and discoverability goals of the blog framework while pushing the Tenant Management project toward an enterprise-ready Java stack. Upcoming posts will cover domain-driven refinements, CI/CD automation, and production deployment experiments.
This architecture represents a significant advancement in the evolutionary journey:
This architecture keeps the independence and discoverability goals of the blog framework while pushing the Tenant Management project into an enterprise-ready Java stack. Upcoming posts will cover domain-driven refinements, CI/CD automation, and production deployment experiments.