Skip to main content

Data Access Object

StructuralData accessLayered architecturePersistenceAbout 3 min

Also known as

  • Data Access Layer
  • DAO

Intent

The Data Access Object (DAO) design pattern aims to separate the application's business logic from the persistence layer, typically a database or any other storage mechanism. By using DAOs, the application can access and manipulate data without being dependent on the specific database implementation details.

Explanation

Real world example

There's a set of customers that need to be persisted to database. Additionally, we need the whole set of CRUD (create/read/update/delete) operations, so we can operate on customers easily.

In plain words

DAO is an interface we provide over the base persistence mechanism.

Wikipedia says

In computer software, a data access object (DAO) is a pattern that provides an abstract interface to some type of database or other persistence mechanism.

Programmatic Example

Walking through our customers example, here's the basic Customer entity.

public class Customer {

    private int id;
    private String firstName;
    private String lastName;

    public Customer(int id, String firstName, String lastName) {
        this.id = id;
        this.firstName = firstName;
        this.lastName = lastName;
    }
}

Here's the CustomerDao interface and two different implementations for it. InMemoryCustomerDao keeps a simple map of customers in memory while DBCustomerDao is the real RDBMS implementation.

public interface CustomerDao {

    Stream<Customer> getAll() throws Exception;

    Optional<Customer> getById(int id) throws Exception;

    boolean add(Customer customer) throws Exception;

    boolean update(Customer customer) throws Exception;

    boolean delete(Customer customer) throws Exception;
}

public class InMemoryCustomerDao implements CustomerDao {

    private final Map<Integer, Customer> idToCustomer = new HashMap<>();

    // implement the interface using the map
}

@Slf4j
public class DbCustomerDao implements CustomerDao {

  private final DataSource
      dataSource;

  public DbCustomerDao(
      DataSource dataSource) {
    this.dataSource =
        dataSource;
  }

  // implement the interface using the data source
}

Finally, here's how we use our DAO to manage customers.

    final var dataSource=createDataSource();
        createSchema(dataSource);
final var customerDao=new DbCustomerDao(dataSource);

        addCustomers(customerDao);
        log.info(ALL_CUSTOMERS);
        try(var customerStream=customerDao.getAll()){
        customerStream.forEach((customer)->log.info(customer.toString()));
        }
        log.info("customerDao.getCustomerById(2): "+customerDao.getById(2));
final var customer=new Customer(4,"Dan","Danson");
        customerDao.add(customer);
        log.info(ALL_CUSTOMERS+customerDao.getAll());
        customer.setFirstName("Daniel");
        customer.setLastName("Danielson");
        customerDao.update(customer);
        log.info(ALL_CUSTOMERS);
        try(var customerStream=customerDao.getAll()){
        customerStream.forEach((cust)->log.info(cust.toString()));
        }
        customerDao.delete(customer);
        log.info(ALL_CUSTOMERS+customerDao.getAll());

        deleteSchema(dataSource);

The program output:

customerDao.getAllCustomers():
        Customer{id=1,firstName='Adam',lastName='Adamson'}
        Customer{id=2,firstName='Bob',lastName='Bobson'}
        Customer{id=3,firstName='Carl',lastName='Carlson'}
        customerDao.getCustomerById(2):Optional[Customer{id=2,firstName='Bob',lastName='Bobson'}]
        customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@7cef4e59
        customerDao.getAllCustomers():
        Customer{id=1,firstName='Adam',lastName='Adamson'}
        Customer{id=2,firstName='Bob',lastName='Bobson'}
        Customer{id=3,firstName='Carl',lastName='Carlson'}
        Customer{id=4,firstName='Daniel',lastName='Danielson'}
        customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@2db0f6b2
        customerDao.getAllCustomers():
        Customer{id=1,firstName='Adam',lastName='Adamson'}
        Customer{id=2,firstName='Bob',lastName='Bobson'}
        Customer{id=3,firstName='Carl',lastName='Carlson'}
        customerDao.getCustomerById(2):Optional[Customer{id=2,firstName='Bob',lastName='Bobson'}]
        customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@12c8a2c0
        customerDao.getAllCustomers():
        Customer{id=1,firstName='Adam',lastName='Adamson'}
        Customer{id=2,firstName='Bob',lastName='Bobson'}
        Customer{id=3,firstName='Carl',lastName='Carlson'}
        Customer{id=4,firstName='Daniel',lastName='Danielson'}
        customerDao.getAllCustomers():java.util.stream.ReferencePipeline$Head@6ec8211c

Class diagram

alt text
Data Access Object

Applicability

Use the Data Access Object in any of the following situations:

  • There is a need to abstract and encapsulate all access to the data source.
  • The application needs to support multiple types of databases or storage mechanisms without significant code changes.
  • You want to keep the database access clean and simple, and separate from business logic.

Tutorials

Known Uses

  • Enterprise applications that require database interaction.
  • Applications requiring data access to be adaptable to multiple storage types (relational databases, XML files, flat files, etc.).
  • Frameworks providing generic data access functionalities.

Consequences

Benefits:

  • Decoupling: Separates the data access logic from the business logic, enhancing modularity and clarity.
  • Reusability: DAOs can be reused across different parts of the application or even in different projects.
  • Testability: Simplifies testing by allowing business logic to be tested separately from the data access logic.
  • Flexibility: Makes it easier to switch underlying storage mechanisms with minimal impact on the application code.

Trade-offs:

  • Layer Complexity: Introduces additional layers to the application, which can increase complexity and development time.
  • Overhead: For simple applications, the DAO pattern might introduce more overhead than necessary.
  • Learning Curve: Developers might need time to understand and implement the pattern effectively, especially in complex projects.

Credits