Как стать автором
Обновить

Комментарии 25

Есть такая штука — spring data rest. :)


Там все тоже самое, только hateoas на выходе ) но я им, если честно, не проникся и колхозил решение, как в этой статье...


How not to hate hateoas? ;)

Если я не ошибаюсь, Spring Data Rest пришивает endpoint прямо к репозиторию. Такой подход не подразумевает сервиса и контроллера. То есть, Вы можете потом именно что "наколхозить", как Вы выразились, отдельно сервис и репозиторий и пройти всю цепочку для нестандартных запросов. Но тогда у Вас будет две параллельных архитектуры, с эндпоинтами в контроллере и репозитории.


Spring Data Rest, по моему мнению, подходит в том случае, если функция сервера чисто утилитарна и сводится к функциям "взять / положить в репозиторий".

Да, я ещё забыл написать — зачем в вашей абстрактной системе сервис, который лишь вызывает метод репозитория? Не проще ли сразу реп инжектить в контроллер, раз уж пошла такая пьянка?


По поводу утилитарности — пример в статье тоже "взять/положить". Говорю же, как s-d-r, только "без крыльев" и "с перламутровыми пуговицами",

Сервис как раз затем, что, как написано в конце статьи, Вам неизбежно придётся добавлять какую-то дополнительную логику для обработки сущностей — переопределять существующие абстрактные методы и писать новые. Если, конечно, приложение шагает за рамки работы с репозиторием.


Конечно, пример в статье максимально лаконичен, чтобы как можно меньше отвлекать читателя от сути (в ресурсах он немного более развёрнут). Вы бы ещё спросили, почему я тесты не написал :)))

Если в абстрактные классы сервиса.контроллера добавить конструктор, принимающий репозиторий/сервис, то и геттеры не нужно будет писать в наследниках. Достаточно только сузить тип аргумента в наследнике.

public abstract class AbstractController<
        E extends AbstractEntity,
        R extends CommonRepository<E>,
        S extends CommonService<E, R>> implements CommonController<E, R, S> {
    private final S service;
    protected AbstractController(S service) {
        this.service = service;
    }
    public getService() {
        return this.service;
    }
}

public class UserController extends AbstractController<User, UserRepository, UserService> {
    public UserController(UserService service) {
        super(service);
    }
}
Да, это действительно, круто. Я добавлю, с Вашего позволения, Ваше замечание в статью.
Спасибо за статью. А вот чтоб не писать по контроллеру/сервису на каждую Entity, припахать фильтр на контроллер, чтоб он делал CRUD по имени Entity? Я пытался у нас сделать, но времени не хватило.
Да, я размышлял об этом подходе, с тем, чтобы попытаться вообще обойтись без бинов, но без них тут, к сожалению, никак, потому что на каком-то этапе нужно будет добавлять / переопределять персональные реализации методов, и тогда для каждой сущности необходимо будет создавать бин и ломать архитектуру. Под это дело есть, как было указано в комментарии выше, Spring Data Rest, но для приложений с хоть какой-то логикой он не подходит. В любом случае, спасибо за комментарий!

Фи-фи-фи… Но тут нет к сожалению стримов, дженериков, опшиналов, спринг дата, сервисов, интерфейсов, энтитей, наследования и копипаста сервисов, контроллеров, и репозиториев для каждой энтити. Всего одна строчка делает все, что Ваш код и еще немного)))). Хотя я не знаю как спринг сериализует ResultSet.


@RestController
@RequestMapping("/rest")
public class Rest Controller{
@RequestMapping("/{tableName}/read}")
    public ResultSet read(String tableName) {
        return dbConnection.prepareStatement("Select * from "+tableName.replaceAll(" ","")).executeQuery();
Первый комментатор к посту описал подход, при котором эндпоинт тащится в репозиторий. Вы описали подход, при котором репозиторий тащится в контроллер. Я ему, кстати, тоже описал, в чём, на мой взгляд, заключается несостоятельность такого подхода. И Вам, кстати, тоже.

А что это у Вас за класс такой модный, Rest Controller?
Ой, не придирайся к опечаткам. Там еще по синтаксису есть проблемы, например @RequestMapping на методе и tableName тоже вроде какой то аннотации требует. А в чем проблема архитектуры? Архитектура-норм, есть общий крудКонтроллер, для стандартных КРУД запросов, а если тебе надо добавить метод, пишешь цепочку контроллер-сервис-репозиторий-энтити без всяких дженериков. Писанины меньше, код примитивный и понятный, как лом.
Да, согласен, ошибками и опечатками Ваш код кишит. Проблема Вашего подхода в том, что бэкенд-разработка давно ушла от низкоуровнего программирования, которое Вы описываете. Конечно, в старых компаниях, особенно в банковском секторе, очень много легаси-проектов, на которых, предположу, Вы выросли. Если же Вы считаете Ваш подход правильным и современным, напишите об этом статью. Если вылезете из минусов, конечно. Всего хорошего.
И сразу на личности, Слава(((И карма-то у меня низкая, и опечатки в коде. Причем здесь высоко-низко, использовать надо то, что подходит и удобно, а не то, что стильно, молодежно и с подворотами. Данную задачу мож удобней сделать sql и Spring MVC, в другой использовать дженерики и спринг дату.

Ждем следующую статью, как с помощью вейпа, гироскутера и смузи смыть за собой в туалете;)
Короче, пришёл Дмитрий Дьячков и занялся своим любимым занятием разводить бессмысленные дискуссии ни о чём. Карма -8 — это показатель, Дмитрий.

Я рад тебя видеть на просторах Хабра, но очередному бесполезному спору я не рад. Продолжать его не буду. Лучше займись делом и почитай хорошую статью, вместо того, чтобы убеждать себя, почему очередная технология тебе не нужна.

Почему больше всего «прорывных идей, простых как лом», исходит от людей, которые не могут освоить даже инкапсуляцию, не говоря уже про стримы?
Нуу блин, ты еще забыл сказать, что я так и не смог освоить
модный и современный метод Objects.notNull() и у меня вызывает искреннее удивление его использование;).

А про карму спасибо, что сказал, я сегодня узнал о ее существовании))
У вас примеры совсем не равнозначны. В простом примере контроллер завязан только на сервис. Что правильно. А вот в абстрактном примере к контроллер прибит гвоздями к сервису и даже знает про сущность через параметры. Это ненормально)
Не совсем понял, о чём Вы. В простом примере сервис явно автовайрится в контроллер, а сущность вшита в сигнатуру каждого метода. В абстрактном примере в бине мы переопределяем только конструктор. И да, конечно, мы задаём сущность через параметры, потому что один бин работает только с одной сущностью. Если где-то сервисный бин работает сразу с несколькими сущностями, вот это как раз ненормально :)

Кстати, ресурсы и код в примере обновился благодаря толковому предложению одного из комментаторов. Теперь в бине мы переопределяем только конструктор абстракции (которого ранее не было). Рекомендую ознакомиться с изменениями — возможно, тогда разногласия исчезнут :)
Дело не в том, что и где вы переопределяете, а что с чем связано.
Вот, в новом варианте более правильно сделано. Зависимость на репозиторий уехала из контроллера в сервис. Это гораздо более правильный вариант. Не должен контроллер ничего знать про то как хранится сущность.
Почему не инжектить так:

@Autowired
private final S service;
Потому что Spring рекомендует инжектить через конструктор.

Более подробно Вы можете ознакомиться с вопросом в посте одного из контрибьюторов Spring Оливера Гирке.

Как Вы наверняка знаете, бин — это синглтон. Использовать один и тот же бин для всех сервисов при многопоточном подходе не безопасно, и Spring не рекомендует этого делать. Поэтому намного более безопасным подходом будет создать финальную копию бина и использовать её.
Потому что это и не скомпилируется вовсе)
А что будет, если конструктор добавить, уж лучше и не проверять)))
Скомпилироваться-то скомпилируется, если конструктор оставить, но сам подход неправильный, Вы правы.
Не совсем про Spring Data (мы его пока не используем), но в тему Spring + Generics. Я реализовал следующий подход, который позволяет не использовать никакие общие абстрактные классы. Правда, потребовалась кастомная аннотация и фабрика для реализаций DAO.

Использование выглядит всего лишь вот так:
  @Autowired @CoreDaoClass(Car.class)
  private CoreDao<Car> carDao;


Аннотация:
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.RUNTIME)
@Qualifier("coreDaoByAnnotation")
public @interface CoreDaoClass {
    Class<?> value();
}


Ну и фабрика для конкретных реализаций DAO:
@Configuration
public class CoreDaoFactory {

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Lazy
    public <C> CoreDao<C> coreDaoForClass(Class<C> cls) {
        return new CoreDaoImpl<>(cls);
    }

    @Bean
    @Scope(ConfigurableBeanFactory.SCOPE_PROTOTYPE)
    @Lazy
    public CoreDao<?> coreDaoByAnnotation(InjectionPoint ip) {
        CoreDaoClass annotation = AnnotationUtils.findAnnotation(ip.getAnnotatedElement(), CoreDaoClass.class);
        if (annotation == null) {
            throw new NoSuchBeanDefinitionException("CoreDao", "CoreDaoClass is not defined");
        }
        return new CoreDaoImpl<>(annotation.value());
    }
}

Из замечаний — CrudRepository.save() никогда не вернет null (the saved entity will never be {@literal null}.). Поэтому Optiolal в сервисе и map в контроллере бессмыслены.
Для результата контроллера, который в вашем коде, есть готовый ResponseEntity.of().


Еще посмотрите на наследование аннотаций в контроллерах. В AbstractController можно определять маппинг методов, а в наследуемом репозитории задавать только маппинг класса.
К сожалению, по моему опыту, это очень частный случай, в основном приходится гораздо больше кастомизировать слои и дженерики начинают больше мешать, чем помогать. Полезны только абстрактные модели: AbstractIdEntity, AbstractNamedEntity и связанные с ними репозитории для общих действий: например поиск по имени. А если все очень типовое — то упомянутый spring-data-rest дает горазде меньше кода.

Зарегистрируйтесь на Хабре, чтобы оставить комментарий

Публикации

Изменить настройки темы

Истории