How to design an abstract DAO interface correctly and is it worth doing it at all?


Every time I'm faced with writing another DAO, I stumble over the same problem - designing the most abstract DAO interface that meets the needs of the Service Layer. This is what a normal DAO/Repository looks like in all Hello World.

interface DAO {

    Entity get(Long id);
    List<Entity> getAll();
    void save(Entity e);
    void update(Entity e);
    void delete(Long id);
}

Unfortunately, there are not many such perfect little entities, and in all the applications that I wrote, it was necessary to fasten a more or less complex search. With all these JOIN, GROUP BY, HAVING and other SQL, well, you and so you know. And in this particular place, it always turns out that expressing search criteria with the help of OOP, which so simply and obviously fall on SQL, is still a task.

Usually in such cases, the DAO begins to acquire a bunch of methods that directly meet the needs of the service. Often to the point where the method signatures match, and the only thing the service does is wrap the DAO method in a transaction.

interface DAO {

    // немного утрируем, но для примера сойдет
    List<Entity> findByUsernameAndStatusAndDate(String userName, Status status, Date lastLogged);
    Long countByUsernameAndStatusAndDate(String userName, Status status, Date lastLogged);

    //.. еще 100500 таких же методов
}

Once I got pretty sick of it, and I wrote something like next:

interface DAO {

    List<Entity> find(String queryName, Param[]... params);
    Long count(String queryName, Param[]... params);
}

class Param<V> {

    String name;
    V value;
}

The second method offers a more flexible interface, but at the same time we have a leaky abstraction, since the details of the DAO implementation "leaked" into the service.

Recently, I've been digging through just a bunch of stuff, trying to find some universal principle of writing a "search API" that can be applied to the DAO. I found several ways:

  1. Query By Example-an object of the same class that we are looking for is passed as search parameters. Fits only for simple cases.

  2. JPA Criteria API - you can pass search terms as a list of predicates. A very expressive API, which, unfortunately, is only implemented for JPA. Writing an analog for JDBC is long and tedious.

  3. The so-called Specification is not really described anywhere, it resembles a degenerate Criteria API. Quite verbose, and breaks down with a bang when trying to describe something more or less complex.

Questions:

Is the DAO flowing into the service such a "code smell"? Is it worth trying to implement the search using abstract/generic DAO at all, or is it better to continue riveting specific methods for each new use case in batches and not show off?

Author: Maxim, 2017-10-20

3 answers

I usually create an abstract generalized DAO class that describes general CRUD operations and something a little more, like a search by ids, which can be described in a simple and understandable way on generics. The rest of the specific stuff goes into the specific db/jpa implementation of the service. In the course of the project, with an understanding of the requirements, common places are usually identified that can/should be brought to the parent class. I think the author is chasing the beautiful. The DAO is not a pill for all ills.

 1
Author: Alexey Poloukhin, 2018-07-23 16:22:31

In one of the tasks, I used a search object, which is a separate class that describes all possible search fields.

This was a training task with books and it was necessary to create a search for all fields (author, title, publisher, release date, or period).

I will propose such a solution to your court.

interface Template{

    // метод вернет готовую строку с параметрами
    // param1 = param and param2 = param2...
    String getfindString();
}

And you have the essence of the book:

class Book{
     String author;
     String title;
     Date date;
}

It remains to create an entity to search for:

class BookFindTemplate implements Template{
     String author;
     String title;
     Date afterDate;
     Date beforeDate;

     String getfindString(){
          String Builder query = new StringBuilder();
          query.append(формируем условие запроса, которое идет после where);
          //в моем исполнении логика была очень страшная.
          return query.toString();
     }
}

Then in the TAO, you still have the search methods that you believe in. you pass a sample search, it will already tell you what to look for and how to search, you just need to specify in which table to look for it.

The question is where to keep these samples, in the dao or in the model. Since part of the sql query is formed there, I think it is worth getting closer to the DAO.

 0
Author: Виктор, 2017-10-20 19:02:35

Well, to be honest, the DAO is not very suitable for such tasks. This pattern is intended only for CRUD methods, while Repository is intended for this purpose, it has specific queries that are not suitable for DAO. So with the concept of Repository and what it is needed for, it seems to have been sorted out.

By abstraction. I would still use the simplest approach possible and a more flexible approach. If the signatures match, it's okay. If the app is going to be big, then you then you will miss such "coincidences". It should be remembered that the service is for logic, and the repository is for working with the database, and if you get everything you need through the database, then this is good.

 0
Author: alex safsafsd, 2018-08-27 12:33:20