2

I'm learning about the Data Access Object pattern, which provides access to a source of data, such as a database. This answer to another question provides the following example:

interface EmployeeDAO {
    List<Employee> findAll();
    List<Employee> findById();
    List<Employee> findByName();
    boolean insertEmployee(Employee employee);
    boolean updateEmployee(Employee employee);
    boolean deleteEmployee(Employee employee);
}

I see similar examples in other answers and articles around the internet. What confuses me is the fact that reading from and writing to a database often take a while, in which case these examples (particularly the find...() ones) would not be very practical as far as I can tell. That is, blocking during a find...() call would probably not be desired behavior.

I'm thinking it could make sense to create a Listener interface (EmployeeDAO.Listener) with methods such as void EmployeeFound(Employee employee), but I'm surprised I haven't seen this before in DAO examples. I wonder if I'm just not understanding Data Access Objects and/or if I'm missing a more obvious approach.

Community
  • 1
  • 1
Anthony
  • 8,570
  • 3
  • 38
  • 46

2 Answers2

3

There are a number of different options / approaches that are often taken:

  1. Blocking like the API you've shown. This is a very simple API to use, and parallelism can still be achieved by invoking the API from within a multithreaded application.

  2. Receiving/registering handlers in async operations. This is sometimes provided in combination with the blocking API (and, in fact, may be implemented in terms of the blocking API simply by spawning a background thread and then then invoking the handler at the end).

  3. Returning a Future or ListenableFuture object, which makes the interface more idiomatically Java-esque (by returning data in the return-type position), but representing the eventual result, not an immediately available result. The Future can then be used to block or non-block.

Personally, my recommendation here would be:

interface EmployeeDatabase {
   interface StringWhereClause {
     ListQueryBuilder is(String value);
     ListQueryBuilder contains(String value);
     ListQueryBUilder matchesRegex(String regex);
   }

   interface IntWhereClause {
     ListQueryBuilder is(int value);
     ListQueryBuilder isInRange(int min, int max);
     ListQueryBuilder isGreaterThan(int value);
     ListQueryBUilder isLessThan(int value);
     ListQueryBuilder isGreaterThanOrEqualTo(int value);
     ListQueryBUilder isLessThanOrEqualTo(int value);
   }
   // ... matchers for other types of properties ...

   interface ListQueryBuilder {
      // Generic restrict methods
      StringWhereClause whereStringProperty(String propertyName);
      IntWhereClause whereIntProperty(String propertyName);
      // ...

      // Named restrict methods
      StringWhereClause whereName();
      StringWhereClause whereJobTitle();
      IntWhereClause whereEmployeeNumber();
      // ...

      ListQueryBuilder limit(long maximumSize);
      ListQueryBuilder offset(long index);

      ResultSet<Employee> fetch();
   }

   ListQueryBuilder list();
   ListenableFuture<Employee> getById(Key key);
   ListenableFuture<KeyOrError> add(Employee employee);
   ListenableFuture<Status> update(Key key, Employee employee);
   ListenableFuture<Status> delete(Key key);
}

With:

 interface ResultSet<T> {
    Iterable<T> asIterable();
    // ... other methods ...
 }

 interface KeyOrError {
    boolean isError();
    boolean isKey();
    Key getKey();
    Throwable getError();
 }

 interface Status {
   boolean isOk();
   boolean isError();
   Throwable getError();
   void verifyOk();
 }

Basically, the idea is that insertion into the database returns a Key object (or an error if unsuccessful). This key can be used to retrieve, delete, or update the entry in the database. These operations (add, update, delete, and getById) all have a single result, in which case a ListenableFuture<T> is used instead of type T; this future object allows you to block (by calling .get() on the future object) or to retrieve the object asynchronously (by registering a callback to be invoked when the result is ready).

For list-y operations, there are many different ways that a list can be filtered, subselected, sorted, etc. In order to prevent a combinatoric explosion of various different overloads, we use the builder pattern to allow these different restrictions to be applied in multiple combinations. In short, the builder interface provides a way to tack on zero or more options (sorts, filters, limits, etc.) to apply the retrieval operation, before calling fetch() which causes the list query to be executed and returns a ResultSet. This operation returns a ResultSet rather than a ListenableFuture, because the results are not all returned at once (e.g. they may come back from the data base in streaming fashion); the ResultSet is effectively an interface with a similar behavior to a ListenableFuture but for lists of items (where items may be ready at different times). For convenience, it is important to have a way to easily iterate over the contents of the ResultSet (e.g. by providing an Iterable adapter to the ResultSet); however, you will probably also want to add other methods that allow you to perform other types of asynchronous processing on the ResultSet; for example, you might want a ListenableFuture<T> reduce(T initialValue, ReduceFunction<T> reducer) to aggregate the elements in the result set and provide a future object representing the eventual completion of that.

Michael Aaron Safyan
  • 93,612
  • 16
  • 138
  • 200
0

The methods that you implement in the interface above are going to be simple sql queries.

  • Find all is going to be "select * from table"
  • find by id is going to be "select * from table where id = :id" (which is the primary key of the table - and indexed).

I work on an application in which we execute millions of insert statements per day and we don't worry about blocking. If you're working in java and you use the Spring framework there are libraries available that take care of all of this for you. Check out EntityManager and TransactionManager in java.persistence.

neal
  • 880
  • 6
  • 11