The project is intentionally split into two architectural parts:
expense-splitter.standalone: the pure business engineexpense-splitter: the Spring Boot webservice and persistence adapter
The split exists so that the core expense-splitting model and settlement algorithms remain independent of HTTP, Spring, PostgreSQL, and deployment concerns.
The standalone module owns the domain language and the settlement behavior of the system. It contains the classes that define what the system means:
TripParticipantTransactionBalanceSheetDebt- settlement algorithms such as
BasicSettlerandSimplifiedSettler SettlerFactoryandSettlementMode
This layer answers questions like:
- What is a trip?
- Who are the participants?
- How is a transaction represented?
- How are debts computed?
- How does “basic” settlement differ from “simplified” settlement?
This layer should not know:
- HTTP endpoints
- request/response DTOs
- Spring annotations
- repositories or JPA entities
- database schema
- deployment/runtime configuration
In short: standalone is the product logic.
The webservice module exposes the standalone core through a deployable API. It contains the classes that define how the system is used and stored:
- controllers (
TripController,TripTransactionController,SettlementController) - request DTOs (
POSTTripRequest,PUTTripRequest,PostTransactionRequest) - application services (
TripServiceImpl,TripTransactionServiceImpl,SettlementServiceImpl) - repository interfaces and PostgreSQL implementations
- exception handlers
- Spring Boot configuration
This layer answers questions like:
- Which API creates a trip?
- How is a transaction request validated?
- How do we load and persist a trip in PostgreSQL?
- How do we translate HTTP input into domain objects?
- How do we return domain results as JSON?
In short: webservice is the adapter around the core.
Put code here when it is:
- business-rule heavy
- reusable outside Spring
- independent of storage and transport
- testable as plain Java
Examples:
- settlement algorithms
- domain objects and invariants
- balance/debt calculation
- settlement mode selection
Put code here when it is:
- API-specific
- persistence-specific
- framework-specific
- integration/orchestration-focused
Examples:
- controllers and URL mappings
- JSON request parsing and validation
- repository implementations
- JPA entities and mappers
- exception-to-HTTP translation
- startup/configuration code
The standalone boundary is trying to achieve portability, replaceability, and long-term safety.
The same expense-splitting engine can be reused by:
- a web API
- a CLI tool
- batch processing
- another UI later
If the delivery layer changes, the core should survive unchanged. For example, PostgreSQL can be replaced, or Spring Boot can be replaced, without rewriting settlement logic.
The most important logic — debt computation — can be tested without bringing up Spring, Docker, or a database. That keeps the core tests fast and trustworthy.
Feature changes are easier to reason about:
- a new settlement algorithm should mostly change the standalone module
- a new endpoint or persistence detail should mostly change the webservice module
The runtime flow is:
- A controller receives an HTTP request.
- A service validates/maps the request.
- The service loads or builds domain objects.
- The service calls the standalone core (
Trip,BalanceSheet,SettlerFactory, settler implementations). - The computed result is persisted or returned.
- The controller serializes the response back to JSON.
So the dependency direction is:
- webservice depends on standalone
- standalone does not depend on webservice
That is the key architectural rule in this project.
- Layered architecture in the webservice: controller -> service -> repository
- Strategy pattern in the standalone core:
SettlerwithBasicSettlerandSimplifiedSettler - Factory-style selection via
SettlerFactory - Adapter/mapper role in the webservice when translating between DTOs, database rows/entities, and domain objects
Add it in the standalone module. Only the selection wiring in the webservice should need minor changes.
Add it in the webservice module. Do not move HTTP logic into the standalone core.
Change the webservice repository implementations. The standalone core should remain untouched.
Keep that in the webservice layer. The domain model should not become shaped by JSON concerns.
The project is designed so that expense-splitter.standalone is the pure expense-sharing engine, while expense-splitter is the Spring Boot adapter that validates requests, persists state, and exposes the core through HTTP.