This document records the major architectural and technical decisions visible in the LifeOS implementation. It focuses on why the system is shaped the way it is, what trade-offs the choices create, and where future changes may make sense.
LifeOS is implemented as a decoupled React frontend and Spring Boot backend, with PostgreSQL as the persistence layer.
The application has a rich browser UI and a backend with authentication, persistence, prioritization, social features, notifications, and AI integration. Separating the SPA from the API lets each side use tools suited to its responsibilities.
A server-rendered monolith would simplify deployment but make the interactive dashboard, task workspace, route guards, and real-time client state less natural. A microservice architecture would create more operational overhead than the current project needs.
The split improves frontend/backend clarity and deployment flexibility, but it introduces CORS, build-time API configuration, OAuth redirect coordination, and separate hosting concerns.
If the application grows, the backend can remain a modular monolith while extracting only truly independent services later, such as notifications or analytics.
Use Spring Boot as the backend framework.
The implementation needs mature support for REST APIs, security, OAuth2 login, JPA, validation, WebSockets, scheduling, and dependency injection. Spring Boot provides these pieces in one ecosystem.
Node/Express or NestJS could serve the API, but the current codebase benefits from Spring Security, Spring Data JPA, typed Java domain code, and built-in WebSocket support.
Spring Boot is heavier than minimalist frameworks and requires more configuration knowledge. In exchange, it provides production-oriented primitives for authentication, persistence, and modular backend design.
Add stronger test coverage around service boundaries and consider API metadata annotations for richer generated Swagger documentation.
Use React 18 with Vite for the frontend SPA.
LifeOS has route-protected pages, reusable dashboard/task/profile components, hooks, and client-side state that fit React's component model. Vite keeps local development and production builds straightforward.
Next.js could provide a full-stack framework and server rendering, but the current app is an authenticated dashboard where most content is client-rendered after login. A plain server-rendered app would be less ergonomic for the current interaction model.
The SPA requires explicit fallback rewrites in Vercel and Nginx. It also embeds VITE_API_URL at build time, so frontend deployments must be rebuilt when the backend URL changes.
Add frontend tests and linting, and consider route-level code splitting if bundle size becomes an issue.
Use PostgreSQL through Spring Data JPA, configured by environment variables and compatible with managed providers such as Neon.
The data model is relational: users own profiles, tasks, labels, notes, activity, notifications, stats, and social relationships. PostgreSQL handles these relationships well and supports JSONB metadata for notifications.
A document database could store flexible task or notification data, but the implementation relies on relational ownership and joins. An embedded database would simplify local demos but would not match production needs.
PostgreSQL requires managed database setup, connection configuration, and schema management. JPA keeps repository code productive, but production schema evolution will eventually need migrations.
Introduce Flyway or Liquibase and set production schema management to validation-only after migrations are in place.
Organize backend code by product domain rather than by global technical layer.
Packages such as task, student, friend, notification, and dashboard make feature ownership easier to understand. Controllers, services, repositories, entities, and DTOs stay near the concepts they serve.
A global controllers, services, and repositories layout would be familiar, but it can force maintainers to jump across unrelated directories for a single feature.
Some cross-domain dependencies still exist, especially in dashboard, rewards, stats, and social features. The benefit is that most feature changes remain local to one package.
Document package boundaries in code-level ADRs if the backend grows, and watch for domains that become too coupled.
Use stateless JWT authentication for protected APIs.
The decoupled SPA/API model works naturally with bearer tokens. Stateless tokens also simplify backend deployment because normal API requests do not depend on server-side HTTP session storage.
Server-side sessions could reduce token exposure in browser storage but require cookie/session infrastructure. OAuth-only login would exclude credentials-based registration. Refresh tokens are not currently implemented.
JWTs reduce server session state, but token storage and expiration handling become important frontend concerns. The current frontend stores tokens in local storage.
Add refresh-token support or a more hardened storage strategy if longer sessions or a stricter threat model become requirements.
Use Spring Security OAuth2 login, then redirect the frontend with a temporary exchange code that is consumed for the final application JWT.
Redirecting the final JWT in a URL would expose it in browser history and logs. A short-lived exchange code narrows that exposure and lets the frontend obtain the normal application JWT through a POST request.
The backend could redirect directly with the JWT or use cookie-based sessions. Direct JWT redirects are simpler but less careful with token exposure. Cookie sessions would change the stateless API model.
The exchange-code approach adds an extra endpoint and cache-backed lifecycle. In the current implementation, OAuthCodeService uses Caffeine with a 2-minute expiration and maximum size of 1000 codes.
For multi-instance backend deployments, move exchange-code storage to a shared external store or ensure sticky routing during OAuth completion.
Use STOMP over WebSockets for real-time notification delivery.
Friend requests, acceptances, and notification changes benefit from immediate delivery. WebSockets avoid repeated polling while users are online.
Polling notification endpoints would be easier to deploy and scale but less responsive and more wasteful. Server-Sent Events could handle one-way delivery but would not align as directly with the STOMP broker pattern already supported by Spring.
WebSockets require connection lifecycle handling, JWT authentication on CONNECT, and proxy support for upgrade headers. The current simple broker is appropriate for a small deployment but is not a durable external message bus.
Use an external broker if horizontal scaling or durable real-time delivery becomes necessary.
Provide Dockerfiles for both backend and frontend, plus Docker Compose for local orchestration.
Container builds make the backend and frontend portable across local machines and deployment platforms. Multi-stage builds keep build dependencies out of runtime images.
Running both apps directly on the host is simpler during development but less reproducible. A single combined image would reduce services but blur frontend/backend deployment boundaries.
The frontend must be built with the correct public VITE_API_URL, and Docker networking can be confusing because browser requests do not originate inside the Compose network.
Add documented image tags, container health checks, and CI image build validation.
Document deployment as a separated backend container, frontend static host, and managed PostgreSQL database.
The repository already supports this split through Dockerfiles, Vercel rewrites, and environment-driven database configuration.
A VPS could host all services together, but it would require more manual operations. A platform-specific monorepo deployment could simplify one target but reduce portability.
Separate deployment improves fit for each layer, but requires coordinating frontend API URL, backend CORS origin, OAuth redirect URIs, and HTTPS.
Add a deployment checklist with real production URLs once a public demo environment exists.
Use a Gemini-backed AI client behind an internal AiClient abstraction.
The product includes task draft generation from prompts. Keeping the model call behind an interface separates task-generation service behavior from the concrete provider client.
The controller could call Gemini directly, but that would couple HTTP routes to provider details and make testing harder.
The abstraction adds a small amount of indirection. In return, the service can evolve if prompt building or provider selection changes.
Add fallback behavior, validation of generated drafts, and tests around prompt construction and malformed model output.
Configure runtime and build behavior through environment variables.
Database credentials, OAuth credentials, JWT secrets, Gemini keys, frontend origins, and deployment URLs vary by environment and must not be hardcoded.
Checked-in property files would simplify local startup but would be unsafe for secrets and brittle across deployments.
Developers need a correctly populated local environment. Deployment platforms must also keep frontend build-time variables and backend runtime variables aligned.
Add .env.example and validation on startup for required variables.
Keep some production-maturity work as future improvement rather than claiming it is complete.
The repository contains substantial application functionality, but there is no visible CI pipeline, migration framework, public demo URL, or production monitoring configuration.
The documentation could present these as existing features, but that would be inaccurate and misleading.
Being explicit about gaps may look less polished than claiming full production maturity, but it gives future maintainers a clear roadmap.
Add CI/CD, migrations, monitoring, structured logging, a public demo deployment, and broader automated test coverage.
LifeOS favors pragmatic full-stack architecture: a separate SPA and API, a modular monolith backend, relational persistence, stateless authentication, and focused real-time messaging. The important trade-off is coordination across layers, especially around environment variables, OAuth, CORS, and deployment URLs.