Enterprise 5 — Service Layer, DAO, DTO, Data Mapper and Active Record
In one line
Five names you'll meet in every Java codebase. A Service Layer holds the use cases. A DAO hides the SQL for one table. A DTO is a plain object for moving data across a boundary (like an API response). Data Mapper keeps domain objects ignorant of the database (JPA works this way), while Active Record lets each object save itself.
Think of it like this
A bank branch. The teller desk (service layer) handles your request from start to finish. The vault clerk (DAO) is the only one who knows how the vault is organised. The printed statement you take home (DTO) contains only what you're allowed to see. Whether account records file themselves (Active Record) or a records office files them (Data Mapper) is a choice about who knows the filing system.
Words you'll meet
New words in this topic, in plain English. Come back here whenever one feels fuzzy.
- Service layer
- The application's use cases, coordinating repositories, rules and transactions.
- DAO
- Data Access Object: an object that runs the queries for one table or entity.
- DTO
- Data Transfer Object: a plain object shaped for crossing a boundary such as an API.
- Data Mapper
- A layer that copies data between domain objects and the database, keeping the objects unaware of storage.
- Active Record
- An object that wraps one database row and knows how to load and save itself.
Step by step
01The problem: entities leaking through the API
Tiffin's first API returned JPA Customer entities directly as JSON. A new field passwordHash appeared in every response, a lazy-loaded relationship caused a serialization error, and renaming a database column broke the mobile app. Business rules were also split between controllers and entities.
02Service in the middle, DTOs at the edge
The controller now receives and returns DTOs only. The service runs the use case with entities and a DAO/repository, and a small mapper converts the entity to the response DTO, choosing exactly which fields leave the system.
import java.util.*;
// entity: internal, mirrors the table
final class CustomerEntity {
final long id; final String name; final String email; final String passwordHash;
CustomerEntity(long id, String name, String email, String passwordHash) {
this.id = id; this.name = name; this.email = email; this.passwordHash = passwordHash;
}
}
// DAO: the only place that knows how customers are stored
interface CustomerDao { Optional<CustomerEntity> findById(long id); void insert(CustomerEntity c); }
final class InMemoryCustomerDao implements CustomerDao {
private final Map<Long, CustomerEntity> rows = new HashMap<>();
public Optional<CustomerEntity> findById(long id) { return Optional.ofNullable(rows.get(id)); }
public void insert(CustomerEntity c) { rows.put(c.id, c); }
}
// DTO: what the API is allowed to show
record CustomerView(long id, String name, String maskedEmail) {}
// service layer: the use case
final class CustomerService {
private final CustomerDao dao;
CustomerService(CustomerDao dao) { this.dao = dao; }
CustomerView profile(long id) {
CustomerEntity c = dao.findById(id).orElseThrow(() -> new NoSuchElementException("customer " + id));
return toView(c);
}
private static CustomerView toView(CustomerEntity c) { // the mapping is explicit
String masked = c.email.charAt(0) + "***" + c.email.substring(c.email.indexOf('@'));
return new CustomerView(c.id, c.name, masked);
}
}03Data Mapper or Active Record?
JPA is a Data Mapper: Customer is a plain class with annotations, and the EntityManager (through Spring Data repositories) loads and saves it. Active Record frameworks put customer.persist() and Customer.findById(1) on the class itself.
Choose Active Record for small CRUD services where tables and objects match one to one. Choose Data Mapper when the domain has real behaviour and invariants, or when the object model and the tables differ (aggregates, value objects, inheritance).
Try it yourself
- 1
Return a profile without leaking the password hash
The DTO has no passwordHash field at all, so it can't leak.
terminal$ jshell> /open Customers.javajshell> var dao = new InMemoryCustomerDao()jshell> dao.insert(new CustomerEntity(7, "Asha", "asha@example.com", "$2b$12$Qx..."))jshell> new CustomerService(dao).profile(7)── expected output ──$5 ==> CustomerView[id=7, name=Asha, maskedEmail=a***@example.com]
In your stack
- →
Java records are ideal DTOs. MapStruct generates entity↔DTO mappers at compile time. JPA/Hibernate is a Data Mapper; Quarkus Panache offers both an active-record style (
extends PanacheEntity) and a repository style.
Break it on purpose
Errors are the best teachers. Make each change, read the error, guess what went wrong, then reveal the answer.
Break #1
Returning JPA entities from the API
A controller returns CustomerEntity directly, and the entity gains a lazy @OneToMany List<OrderEntity> orders relationship.
Myth vs fact
Myth
DTOs are pointless boilerplate.
Fact
Records make them one line each, and they're what lets the database and the API evolve independently while keeping sensitive fields private.
Pro corner
Extra depth for experienced readers. New to this? Skip it for now and come back later.
- ▸
Keep transaction boundaries on service methods, not controllers or DAOs, and build DTOs inside them so lazy relationships are loaded deliberately (or fetched with a query that returns the DTO directly).
Remember this
- 1
Service Layer: one class per group of use cases (
OrderService.placeOrder,cancelOrder). It coordinates repositories, rules and transactions. Controllers, message consumers and scheduled jobs all call the same service, so business behaviour is identical everywhere (see Enterprise 4). - 2
DAO (Data Access Object): an object that wraps the queries for one table or entity (
OrderDao.insert,findByCustomer). It's lower level than a Repository: DAOs think in tables and rows, Repositories in domain aggregates. In Spring, the two names are often used loosely for the same thing. - 3
DTO (Data Transfer Object): a simple carrier (in Java, usually a
record) shaped for one boundary: an API response, a request body, a message. It lets the API stay stable while internal entities change, and it stops internal fields (password hashes, internal ids) from leaking out. - 4
Data Mapper vs Active Record: with Data Mapper, a separate layer moves data between objects and tables, so domain objects have no database code (JPA/Hibernate, MyBatis). With Active Record, each object has
save()/find()methods and maps to one row (Ruby on Rails, Quarkus Panache's active-record style). Active Record is quick for simple CRUD; Data Mapper keeps a rich domain model clean.
Explain it without notes
What's the difference between a DAO and a Repository?
Why return DTOs instead of entities from an API?
Practice
Add an updateName(id, newName) use case to CustomerService that rejects empty names.
When would you pick Active Record for a new service?
Trade-offs
- ↔
Clear layers, safe APIs and an independent domain model, versus more classes and mapping code. Use the lightest layering that keeps business rules in one place.
Done when you can
I can explain service layer, DAO, DTO, Data Mapper and Active Record in one sentence each.
I can design an endpoint that returns a DTO built by a service.