Architecture¶
Backend Core is built around a classical three-tier architecture and provides the contracts that make strict layer separation feasible.
flowchart LR
P[Presentation Layer] --> S[Service / Business Logic Layer]
S --> D[Persistence Layer]
M[Model Layer]:::model
P -.uses.-> M
S -.uses.-> M
D -.uses.-> M
classDef model fill:#fff4ec,stroke:#fc7a5f,stroke-width:1px;
Layers¶
Persistence layer¶
The persistence layer holds the code that persists and retrieves entities to and from a storage system — typically a relational database, but possibly a NoSQL store or a remote API. It depends on the libraries and frameworks needed to do that work (JPA, a JPA provider, a database driver, etc.), but only JPA is exposed as a transitive dependency from Backend Core.
It is not the most important layer — straightforward to write, except for the complex queries needed to extract data from rich entity graphs.
Service / business-logic layer¶
The service layer (also called the business-logic layer) is the most important of the three. It contains the code that captures what the user actually wants the application to do, has medium complexity, and survives migrations of the persistence or presentation layers.
The "service" name comes from the common practice of exposing this layer to other applications: a heterogeneous enterprise environment normally communicates via services, and the services exposed are almost always written here.
Typical content:
- Business-logic validation.
- Business-logic calculations.
- Code that is exposed as services to other applications.
- Long-running processes.
- File system access.
- Mail sending.
- And so on.
Presentation layer¶
The presentation layer is often the most difficult to write because of the complexity of building good UIs that meet user expectations. Despite that, it should not be the most important layer — it should be replaceable, so newer visualization technologies can be adopted without huge changes to the business logic.
It converts human interaction into the information the application needs to run business logic. That conversion requires two kinds of validation:
- Presentation / UI validation — the first stage, executed in the presentation layer before data can be converted into a form that the business-logic layer accepts.
- Business-logic validation — the second stage, executed in the business-logic layer once the data is in the application's domain shape. It must live in the business-logic layer so it can validate data coming from any source, not just the UI.
The presentation layer depends on the visual frameworks needed to render the application — these dependencies are out of Backend Core's scope.
Model layer¶
The model layer is the "language" the other layers use to talk to each other. It contains the object definitions that represent the data and behavior of the application. Two kinds of objects are typical:
- Persistent entities — objects with metadata (JPA annotations) that lets the persistence layer save and load them. That metadata is only needed by the persistence layer, so it should not be forced onto the other layers.
- DTOs (Data Transfer Objects) — used to move data between the presentation layer or external systems and the business-logic layer.
These two kinds of objects have a direct impact on how the model layer is organized — one common approach is to split it into a module that contains interfaces (consumable from any layer) and an implementation module that contains the persistence-annotated classes.
Layer-separation approaches¶
Backend Core supports the following separation approaches, listed in order of architectural strictness.
1. Three layers with contracts in separated modules¶
The strictest approach: each layer's contract lives in its own Maven module, and the implementation lives in another. Consumers depend on the contract module and never see the implementation's transitive dependencies.
flowchart TD
PRES[Presentation Layer Module]
SLC[Services Layer Contracts Module]
SLI[Services Layer Impl Module]
PLC[Persistence Layer Contracts Module]
PLI[Persistence Layer Impl Module]
MOD[Model Layer Contracts Module]
PRES --> SLC
PRES --> MOD
SLI -- implements --> SLC
SLI --> PLC
SLI --> MOD
PLI -- implements --> PLC
PLI --> MOD
- Persistence layer contracts module — interfaces describing what the persistence layer offers; never forces consumers to import persistence-specific libraries (e.g., JPA).
- Persistence layer implementation module — implements those contracts. Persistent entities with storage-specific metadata live here.
- Services layer contracts module — interfaces describing the services offered by the business-logic layer; never exposes the persistence contracts module as a transitive dependency.
- Services layer implementation module — implements the service contracts.
- Model layer contracts module — interfaces for the domain objects used across layers, plus DTOs (which can be classes). Persistent entities are always referenced via interfaces from this module, so they can be returned without leaking persistence metadata.
In this approach, each layer can also be split physically — connected through REST APIs, for example — without changing the consumer's contract.
2. Three layers in separated modules¶
A less strict variant: each layer is a separate module, but contract and implementation live together. Consumers can still prevent the presentation layer from depending on persistence by excluding transitive dependencies (or by configuring the persistence module's dependency as non-transitive).
flowchart TD
PRES[Presentation Module]
SVC[Service Module]
PERS[Persistence Module]
MOD[Model Module]
PRES --> SVC
PRES --> MOD
SVC --> PERS
SVC --> MOD
PERS --> MOD
3. Three layers in the same module¶
Everything in a single module — for example, a single Java web application packaged as a WAR. All imports are permitted, but the library still helps reduce the boilerplate needed to organize the code in three layers internally.
flowchart TD
APP[Application Module]
P[Presentation]
S[Service]
D[Persistence]
M[Model]
APP --- P
APP --- S
APP --- D
APP --- M
4. Hexagonal / Domain-Driven Design¶
Strict layer separation is a natural fit for hexagonal architectures and Domain-Driven Design. A specific effort is made to keep these scenarios viable, although they are not the primary target.