Страницы

Поиск по вопросам

среда, 11 декабря 2019 г.

Как правильно спроектировать абстрактный DAO интерфейс и стоит ли вообще это делать?

#java #ооп #шаблоны_проектирования #dao


Всякий раз сталкиваясь с написанием очередного DAO, я споткаюсь об одну и ту же проблему
- проектирование максимально абстрактного интерфейса DAO, удовлетворяющего нуждам Service
Layer. Примерно так выглядит обычный DAO/Repository во всех Hello World.

interface DAO {

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


К сожалению, таких идеальных маленьких сущностей немного, и во всех приложениях,
что я писал, требовалось прикрутить более-менее сложный поиск. Со всеми этими JOIN,
GROUP BY, HAVING и прочим SQL, ну вы и так знаете. И вот в этом конкретном месте всегда
оказывается, что выразить с помощью ООП критерии поиска, которые так просто и очевидно
ложатся на SQL, та еще задачка.

Обычно в таких случаях DAO начинает обрастать кучей методов, которые напрямую удовлетворяют
нуждам сервиса. Часто до такой степени, что совпадают сигнатуры методов, и единственное
что делает сервис, это оборачивает метод DAO в транзакцию.

interface DAO {

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

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


Как-то раз меня это порядком достало, и я написал примерно следующее:

interface DAO {

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

class Param {

    String name;
    V value;
}


Второй способ предлагает более гибкий интерфейс, но при этом мы имеем leaky abstraction,
поскольку детали реализация DAO "протекли" в сервис.

За последнее время я перелопатил просто кучу материала, пытаясь найти какой-то универсальный
принцип написания "search API", который можно применить к DAO. Нашел несколько способов:


Query By Example - в качестве параметров поиска передается объект того же класса,
который мы ищем. Подходит только для простых случаев.
JPA Criteria API - можно передать условия поиска в виде списка предикатов. Очень
выразительный API, который, к сожалению, реализован только для JPA. Писать аналог для
JDBC долго и муторно.
Так назывемый Specification - нигде толком не описан, напоминает вырожденный Criteria
API. Довольно многословный, и с треском ломается при попытке описать что-то более-менее
сложное.


Вопросы:

Явлется ли протекающий в сервис DAO таким уж "code smell"? Стоит ли вообще стремиться
реализовать поиск с помощью abstract/generic DAO или лучше и дальше пачками клепать
специфичные методы под каждый новый use case и не выпендриваться?
    


Ответы

Ответ 1



Я обычно создаю абстрактный обобщенный DAO класс, в котором описаны общие CRUD операции и что-то чуть более, как то поиск по ids, который можно незатрано и понятно описать на генериках. Остальное специфичное уходит уже в конкретную реализацию db/jpa сервиса. По ходу проекта с пониманием требований обычно выявляются общие места, которые можно/нужно вынести в родительский класс. Я думаю, автор гонится за прекрасным. DAO не таблетка от всех бед.

Ответ 2



В одной из задач я использовал объект поиска, это отдельный класс, который описывает все возможное поля поиска. Это была учебная задача с книгами и надо было создать поиск по всем полям (автор, название, издательство, дата выпуска точная или промежуток). На ваш суд предложу такое решение. interface Template{ // метод вернет готовую строку с параметрами // param1 = param and param2 = param2... String getfindString(); } И у вас есть сущность книги: class Book{ String author; String title; Date date; } Остается создать сущность для поиска: 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(); } } Тогда в ДАО у вас остаются методы поиска, в которые вы передаете образец поиска, он уже сообщит что искать и как искать, вам остается указать в какой таблице это искать. Вопрос где держать эти образцы, в дао или в модели. Т.к. там формируется часть sql запроса, думаю стоит поближе к ДАО.

Ответ 3



Ну если быть откровенным, то DAO не очень подходит для таких задач. Данный паттерн предназначен только для CRUD методов, в то время как Repository, для этого и предназначен, он имеет специфические запросы которые для DAO не подойдут. Так что с понятием Repository и для чего он нужен вроде как разобрали. По абстракции. Я же все таки использовал бы максимально простой подход и более гибкий подход. Если сигнатуры совпадают, ничего страшного. Если приложение будет большим, то ты потом будешь скучать по таким "совпадениям". Следует помнить что сервис для логики, а репозиторий для работы с БД, и если ты через бд получаешь все что нужно, то это хорошо.

Комментариев нет:

Отправить комментарий