Command Palette

Search for a command to run...

PHASE 2Beginner ~16 min· topic 28 of 28Enterprise

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.

0/28 · 0%

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.

Customers.javawhole filejava
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).

Data Mapper or Active Record?diagram
Rendering diagram…

Try it yourself

  1. 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.java
    jshell> 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.

terminal
$ curl -s https://api.tiffin.in/customers/7
── what you'll see ──
{"status":500,"error":"Could not write JSON: failed to lazily initialize a collection of role: in.tiffin.CustomerEntity.orders: could not initialize proxy - no Session"}

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. 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. 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. 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. 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

01

What's the difference between a DAO and a Repository?

02

Why return DTOs instead of entities from an API?

Practice

01

Add an updateName(id, newName) use case to CustomerService that rejects empty names.

02

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.

Back to phase