Гайд Разработка сервиса отзывов. Часть 0.0.1. Архитектура

Модератор форума
Модератор
Статус
Оффлайн
Регистрация
26 Янв 2020
Сообщения
378
Реакции[?]
157
Поинты[?]
9K
Всем привет, на связи член модераторского состава форума yougame.biz, по совместительству бэкенд разработчик в аутсорсиноговой компании. На пути своего становления в этом ремесле, столкнулся с проблемой, что информация в интернете по бэкэнд разработке, в особенности на spring boot, в некоторых моментах страдает плохим качеством, а то и вовсе отсутствует. Поэтому сформировать полноценный пласт знаний в этой области начинающему разработчику бывает достаточно сложно. В связи с этим, появилась мотивация сделать цикл статей, в которых с самого нуля будет рассмотрено создание демо - сервиса, в котором будут отражены все основные аспекты, которыми должен владеть начинающий разработчик.

В этой статье пойдет речь о фундаменте любого приложения, не важно, web это или десктоп. Без понимания этой концепции строить надежные и легко расширяемые приложения будет практически невозможно. И говорить мы будем об Архитектуре.

Как скомпоновать приложение? Какие в нём должны быть слои? Как назвать пакеты? Где расположить DTO, маперы, реализации интерфейсов? И нужны ли вообще интерфейсы?

Когда человек начинает делать свой первый проект, то у него нету однозначных ответов на эти вопросы. Он смотрит код, "до которого может дотянуться" - открытые GitHub репозитории, обучающие статьи, и если повезет, то у новичка есть все шансы научиться писать хороший, чистый, код. Если же не повезёт, то человек будет цепляться за то, что есть, нахватается плохих практик, и по прошествии года-двух он уже сам будет себе авторитетом, которого не так-то просто будет переубедить.

В посте ниже я описываю личный и командный опыт, под который я постарался подвести теоретическую базу, опираясь на "Чистую архитектуру" Роберта Мартина. Да, этот пост - для новичков, хотя, эта тема жива и среди устоявшихся программистов, поскольку споры о компоновке приложения не утихают и среди сениоров.

Зайду я немного издалека и напомню, что такое луковичная архитектура.

Что такое луковичная архитектура?

В "Чистой архитектуре" Роберта Мартина описывается центрическая архитектура, ядром которой является бизнес-сущность.

Луковичная архитектура

Давайте же попробуем разобрать, что происходит на изображении, пройдясь по каждому из слоев.

Бизнес-сущность (Entity)

Бизнес-сущность (по англ. Entity) ничего не знает ни о логике приложения, ни о слое доступа к данным, ни уж тем более о подключаемых внешних интерфейсах - она содержит только собственные поля и базовые методы для работы с ними - геттеры, сеттеры, контроллеры, билдеры и всё, что необходимо сущности для работы с самой собой. Как правило, Entity необходим для отображения данных, с которыми работает java приложение в базу данных, именно поэтому entity чаще всего аннотируется, в соответствии с используемой в проекте спецификацией для работы с бд (JDBC, JPA) Вот тут, конечно, стоило бы оговориться, что при использовании Entity как объекта для отображения данных из бд в код и наоборот, нарушается принцип независимости, но в производственной сфере мало кто обращает на это внимание, так что опустим этот момент ;)
Пример:
1719081008288.png

Репозиторий (Repository)

Данный слой стоит на один уровень выше Entity. Основная его цель - обеспечить основные операции над данными (так называемый CRUD) между бд и приложением. На вход Repository получает сформированный Entity, на выходе он должен предоставить тоже Entity, то с до заполненным полями из бд, если таковые имеются (database-generative fields - поля, которые генерирует база данных), ничего более репозиторий уметь не должен.

Пример:

1719081449865.png


На самом деле, репозиторий не обязательно должен выглядеть именно так, его внешний вид напрямую зависит от, опять же, используемой спецификации общения бд и приложения.

Сервисы (Services)

Вот это уже поинтереснее. По факту, сервисы являются сердцем любого web приложения, именно они реализуют бизнес логику. Сервис общается только с одним репозиторием, это важно! Если в приложении вы видите такую цепочку зависимостей: Entity->Repository->Service, то это означает, что приложение проектировали осознанные люди :) Старайтесь всегда соблюдать это правило - на один энтити один репозиторий, на один репозиторий один сервис. Если вам нужно обращаться к другим энтити, сделайте межсервисное взаимодействие.

Как раз о нем. Формат общения сервисов друг с другом и с вышестоящими слоями заключается в использовании так называемых DTO - data transfer objects. Это обычные классы, которые так же содержат только собственные поля и базовые методы для работы с ними - геттеры, сеттеры, контроллеры, билдеры и всё, что необходимо сущности для работы с самой собой. DTO бывает двух видов - RequestDTO и ResponceDTO.

RequestDTO содержит только те поля, которые необходимы для выполнения вызова сервисом, ничего более.

ResponceDTO, как правило, содержит в себе отображение Entity, хотя вполне допускается и дополнительная информация.


Контроллер (Controller)

Является слоем, отвечающим за отображение данных service в формат, который понимает клиент. Контроллеры бывают разных типов, как пример RestController - взаимодействует с пользователем при помощи RestAPI.

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


В итоге мы получил следующую иерархию проекта:

Код:
/src
    /controller
    /service
    /repository
    /model
        /entity
        /DTO
            /request
            /responce

Буду рад услышать комментарии и дополнения к этой статье. До встречи!
 
Последнее редактирование:
Начинающий
Статус
Оффлайн
Регистрация
6 Май 2016
Сообщения
45
Реакции[?]
17
Поинты[?]
4K
Я бы еще сделал пакет для эксепшенов бизнес логики

Код:
/src
    /controller
    /service
    /repository
    /model
        /entity
        /DTO
            /request
            /responce
    /exception
И сделал бы что-то такое, кидал бы соответствующие исключение, при бизнес ошибке(что-то ответило не так и тп)
Java:
public class BusinessException extends RuntimeException implements Serializable {
    public BusinessException(String message) {
        super(message);
    }

    public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    public BusinessException(Throwable cause) {
        super(cause);
    }

    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class SomeBusinessException extends BusinessException {

    public SomeException(String message) {
        super(message);
    }

    public SomeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
 
Участник
Статус
Онлайн
Регистрация
23 Апр 2022
Сообщения
695
Реакции[?]
327
Поинты[?]
11K
Я бы еще сделал пакет для эксепшенов бизнес логики

Код:
/src
    /controller
    /service
    /repository
    /model
        /entity
        /DTO
            /request
            /responce
    /exception
И сделал бы что-то такое, кидал бы соответствующие исключение, при бизнес ошибке(что-то ответило не так и тп)
Java:
public class BusinessException extends RuntimeException implements Serializable {
    public BusinessException(String message) {
        super(message);
    }

    public BusinessException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }

    public BusinessException(Throwable cause) {
        super(cause);
    }

    public BusinessException(String message, Throwable cause) {
        super(message, cause);
    }
}

public class SomeBusinessException extends BusinessException {

    public SomeException(String message) {
        super(message);
    }

    public SomeException(String message, Throwable cause, boolean enableSuppression, boolean writableStackTrace) {
        super(message, cause, enableSuppression, writableStackTrace);
    }
}
Я бы тебе дырку в башке сделал
 
Сверху Снизу