Весняне завантаження 2.0 - Структура проекту та кращі практики (частина 2)

Надалі до першої частини серії про Spring Boot v2.0, це друга і підсумкова частина, де ми будемо проходити через структуру проекту програми разом з деякими важливими фрагментами та найкращими практиками, які слід враховувати під час розробки на базі Spring Boot .

Лише невелика відмова від відповідальності, встановивши цей набір для початківців і зробивши ретельний огляд, ви можете відчути, що це насправді не реалізація мікро-сервісу за допомогою Spring Boot, я мушу сказати, що я повністю з вами згоден, це не так, швидше це моноліт, розроблений у весняному черевику. У цій статті йдеться більше про те, як навчитися кодувати найкращі практики у Spring Boot, і в ідеалі система бронювання повинна мати більше ніж одну службу чи базу коду, я обіцяю, що незабаром публікую нову серію про використання Spring Boot у створенні розподіленої програми за допомогою стека Netflix OSS. До цього часу, будь ласка, ставитесь до цього як до вихідного пункту та вирівняйте структуру проекту та пов'язані інструменти.

Вихідний код

Вихідний код цього стартового набору можна клонувати з наступного сховища GitHub:

Структура програми

Spring Boot - це впевнена структура, яка робить наше життя дуже легкою, оскільки нам не потрібно вибирати версії різних залежностей, засновані на версії Spring Framework, про все це піклується Spring Boot.

Я намагався дотримуватися тієї самої ідеології під час створення структури проекту, спочатку це може здатися непосильним, але повірте мені, коли ви почнете писати свої твори, структура допоможе вам неабияк, заощадивши свій час і задумавшись над питаннями, на які вже відповіли. . Вигляд структури виглядає так:

Моделі та DTO

Різні моделі програми організовані під модельним пакетом, їх DTO (об'єкти передачі даних) містяться під пакетом dto. Існують різні думки щодо того, чи варто використовувати DTO чи ні, я належу до набору думок, які вважають, що ми, безумовно, повинні, а не використовувати DTO, ваш шар моделі дуже щільно поєднується з шаром інтерфейсу, і це те, що жоден корпоративний проект ніколи не повинен залазь.

DTO дозволяють нам передавати лише ті дані, які нам потрібно поділитися з користувальницьким інтерфейсом, а не весь об'єкт моделі, який ми можемо об'єднати за допомогою декількох суб-об'єктів і зберігатись у базі даних. Зіставленням моделей до DTO можна обробляти за допомогою утиліти ModelMapper, однак корисною є лише тоді, коли ваш DTO майже подібний (буквально) до відповідних моделей, що не завжди так, і тому я вважаю за краще використовувати користувацькі класи картографів. Деякі приклади ви можете знайти в пакеті "dto / mapper".

Наприклад, давайте подивимось, як організована наша модель Trip.java:

Trip.java

Як ми бачимо, модель містить посилання на колекції зупинок, автобусів та агентств. Це потрібно, щоб підтримувати зв’язок між цими різними колекціями з подорожжю. Хоча це не те саме, що поняття іноземного ключа в MySQL і тут не застосовується каскад за замовчуванням, він дає нам спосіб наслідувати те саме в MongoDB. Відповідний об'єкт передачі даних виглядає так:

TripDto.java

Як я описав раніше, DTO не мають бути дзеркальним зображенням своїх модельних аналогів, скоріше вони повинні бути відображенням того, що вимагає користувальницький інтерфейс або відповідь api. У цьому випадку не було сенсу складати композиційні відносини між поїздкою та її автобусом чи агентством або зупинками, скоріше їхні основні ключі насправді можуть зробити трюк. Це не тільки знімає ці DTO, але також зменшує загальний розмір пакета відповідей, який буде подорожувати по HTTP від ​​сервера до клієнта.

Наступне питання може полягати в тому, як перетворити модель POJO в цей DTO, але є кілька способів зробити це, але я вважаю за краще бути явним і робити так:

Послуги та DAO

Об'єкти доступу до даних (DAO) присутні у пакеті репозиторію. Всі вони є розширеннями інтерфейсу MongoRepository, що допомагає сервісному шару зберігати та отримувати дані з MongoDB.

Службовий рівень визначається в пакеті послуг, враховуючи поточний приклад, має сенс створити дві основні послуги:

  1. UserService та
  2. BusReservationService

Пояснення кожного методу в цих сервісах виходить за межі цієї публікації в блозі, проте я перелічу інтерфейси, що показують операції, що підтримуються цими службами.

UserService.java

BusReservationService.java

Окрім зауваження методу іменування конвенцій, я впевнений, що ви помітили, що сервісний рівень ніколи не приймає модель як вхідну і ніколи не повертає її. Це ще одна найкраща практика, яку розробники Spring повинні дотримуватися в шаруватій архітектурі. Рівень контролера взаємодіє з обслуговуючим рівнем, щоб отримати завдання, коли він отримує запит від перегляду або api-шару, коли він не повинен мати доступ до об'єктів моделі і повинен завжди спілкуватися з точки зору нейтральних DTO.

Коли сервіс отримує об'єкт DTO, він повинен спробувати вийти з цього сенсу, запитуючи відповідний об'єкт моделі з бази даних, а потім виконати необхідну операцію та створити DTO відповіді, який потрібно відправити назад в службу виклику. Такий підхід дозволяє самостійно змінювати вигляд та моделі, не переживаючи, що одна може зламати іншу.

Щоб проілюструвати вищезазначений підхід, візьмемо приклад службової операції "updateProfile", яка використовується для оновлення інформації користувача. Визначення методу виглядає так:

Безпека

Параметри безпеки присутні в пакеті config, а фактичні конфігурації виконуються відповідно до класу, присутнього в пакеті безпеки. У додатку є різні концепції безпеки для адміністратора порталу та API REST, для порталу я застосував механізм весняної сесії за замовчуванням, який базується на концепції sessionID та файлів cookie. Для API REST я використав механізм аутентифікації на основі лексеми JWT.

Захист для Інтернету та apis налаштовано як в одному класі MultiHttpSecurityConfig.java. У нього є два статичні класи, що поширюються на WebSecurityConfigurerAdapter, що дозволить нам налаштувати захист http для вхідних запитів.

Анотація @Order дозволяє сканувати запити через різні конфігурації у вказаному порядку. Таким чином, запит API проходить через ApiWebSecurityConfigurationAdapter і там поглинається, проте запит адміністратора спочатку проходить через нього, але оскільки він не відповідає критеріям Spring Security намагається змусити його пройти наступну конфігурацію з негайним вищим порядком, який у цьому випадку це FormLoginWebSecurityConfigurerAdapter.

Запити API повинні пройти через ApiJWTAuthenticationFilter та ApiJWTAuthorizationFilter, які відповідають за створення та перевірку маркера JWT, виданого під час входу. Якщо вам цікаво, яку URL-адресу слід використовувати для автентифікації API (логін), ось це:

http: // localhost: 8080 / api / auth

І якщо вам цікаво, як це було налаштовано, то відповідь лежить у класі ApiJWTAuthenticationFilter, його конструктор кодує таку інформацію:

this.setRequiresAuthenticationRequestMatcher (новий AntPathRequestMatcher ("/ api / auth", "POST"));

Це повідомляє AbstractAuthenticationProcessingFilter для підключення AuthenticationRequestMatcher зі шляху "/ api / auth" для запитів API.

Додаток адміністратора дозволено лише тим користувачам, чиєю роллю є "ADMIN". Всі паролі шифруються за допомогою BCryptPasswordEncoder, і ми ніколи не можемо побачити їх фактичне значення, як тільки вони зберігаються в базі даних.

Контролери

Остання, але найважливіша частина - рівень контролера. Він пов'язує все разом одразу з моменту перехоплення запиту до підготовки та відправлення відповіді. Рівень контролера присутній у пакеті контролера, найкращі практики пропонують нам підтримувати цей шар для підтримки декількох версій програми, і тут застосовується та ж практика.

На даний момент код присутній лише під v1, але з часом я очікую, що будуть різні версії з різними можливостями. Контролери, пов’язані з порталом адміністратора, присутні в пакеті ui, а його об'єкти команд форми розташовані під командним пакетом. Контролери REST API розташовані під пакетом api, а відповідні класи запитів - під пакетом запиту.

Контролери панелі адміністратора працюють над концепцією Spring WebMVC. Вони відповідають на вхідні веб-запити об'єктами SpringA ModelAndView, що містять дані, що відображаються у відповідних переглядах / формах, а також ім'я перегляду, яке має бути надано, наприклад клас DashboardController:

Запити та формувати команди

Знову ж таки, серед спільноти розробників існують різні думки щодо використання окремих класів для відображення вхідного запиту по відношенню до використання DTO та використання моделей безпосередньо, я особисто прихильник сегрегації їх настільки, наскільки це можливо, щоб сприяти вільній зв'язці між UI та контрольний шар.

Об'єкти запиту та команди форми дають нам можливість застосувати додатковий рівень перевірок на вхідні запити до їх перетворення в DTO, які передають дійсну інформацію на рівень обслуговування для збереження та пошуку даних. Ми можемо використовувати тут DTO, і деякі розробники віддають перевагу такому підходу, оскільки він зменшує деякі додаткові класи, однак я зазвичай вважаю за краще тримати логіку перевірки окремо від об'єктів передачі, і тому я схильний використовувати об'єкти запит / команда перед ними.

Приклад класу BusFormCommand на основі шаблону команд виглядає наступним чином:

Приклад запиту, надісланого через API, BookTicketRequest:

Статичні ресурси групуються під каталогом ресурсів. Усі об’єкти інтерфейсу та їх стильові аспекти можна розмістити тут.

Ломбок

Одна з найбільших скарг на Java - скільки шуму можна знайти в одному класі. Проект "Ломбок" вважав це проблемою і має на меті зменшити шум деяких з найгірших порушників, замінивши їх простим набором анотацій. У цьому комплекті для початківців ви знайдете Lombok, який фактично допомагає скоротити рядки коду, заощадити багато часу та зусиль на розробці та зробить код набагато більш читабельним. Деякі з найважливіших приміток, які я вважаю за краще використовувати:

@Getter / @ Setter

Ніколи не пишіть public int getFoo () {return foo;} знову.

@ToString

Не потрібно запускати налагоджувач, щоб побачити ваші поля: Дозвольте lombok генерувати для васString!

@EqualsAndHashCode

Рівність стала простою: генерує хеш-код і дорівнює реалізаціям з полів вашого об’єкта ..

@Data

Зараз усі разом: ярлик для @ToString, @EqualsAndHashCode, @Getter на всіх полях і @Setter для всіх не остаточних полів, і

@RequiredArgsConstructor!

По суті, щось настільки багатослівне, як:

Можна записати просто так:

Ви дуже добре бачите різницю, і не тільки пізніше виглядає набагато чистіше, ми перейшли з 59 рядків нудного POJO до файлу класу Java на 8 рядків.

Обробка відповідей та виключень API

Я спробував трохи експериментувати з RuntimeExceptions і придумав міні-фреймворк для обробки винятків всієї програми, використовуючи кілька класів та файл властивостей. Якщо ви уважно дотримуєтесь пакету винятків, він складається з двох переліків:

  1. EntityType і
  2. Тип винятку

Перелік EntityType містить усі імена об'єктів, які ми використовуємо в системі для постійності, і ті, які можуть призвести до виключення часу виконання. Перерахунок ExceptionType складається з різних виключень на рівні сутності, таких як винятки EntityNotFound та DuplicateEntity.

Клас BRSException має два статичні класи EntityNotFoundException і DuplicateEntityException, які є двома найбільш викинутими винятками з рівня обслуговування. Він також містить набір перевантажених методів giveException, які беруть EntityType, ExceptionType та аргументи, щоб створити відформатоване повідомлення, шаблон якого присутній у файлі custom.properties.

Використовуючи цей клас, я зміг розширити повноваження всього рівня служб, щоб викидати різноманітні винятки сутності рівномірно, не захаращуючи кодову базу різними винятками сутності NOT_FOUND та DUPLICATE.

Наприклад, під час входу в систему, якщо ви намагаєтесь використовувати адресу електронної пошти, яка не зареєстрована, виняток піднімається та видаляється за допомогою наступного єдиного рядка коду -

виключення викидання (USER, ENTITY_NOT_FOUND, userDto.getEmail ());

Це призводить до поєднання імен цих двох елементів перерахунку USER ("користувач") та ENTITY_NOT_FOUND ("not.found") і створюється ключовий user.not.found, який присутній у файлах custom.properties таким чином:

user.not.found = Запитаний користувач з електронною поштою - {0} не існує.

Просто замінивши параметр {0} на адресу електронної пошти, включену у викинутий виняток, ви можете отримати добре відформатоване повідомлення, яке буде показане користувачеві або відправлене назад як відповідь на дзвінок REST API. Визначення класу BRSException таке:

Ще одна важлива частина цієї міні-бази - це клас CustomizedResponseEntityExceptionHandler, який виглядає наступним чином:

Цей клас доглядає за цими програмами RuntimeExceptions, перш ніж надсилати відповідь на запити API. Порада контролера, яка перевіряє, чи викликано виклик сервісного рівня в EntityNotFoundException або DuplicateEntityException, і надсилає відповідний відповідь абоненту.

Нарешті, всі відповіді API надсилаються однаково, використовуючи клас відповідей, присутній у пакеті dto / response. Цей клас дозволяє нам створювати однакові об'єкти, які призводять до відповіді, як показано нижче (це відповідь для api "api / v1 / резервування / зупинки"):

{
 "Статус": "ОК",
 "Корисне навантаження": [
   {
    "Код": "STPA",
    "Ім'я": "Стоп",
    "Деталь": "Близькі пагорби"
   },
   {
    "Код": "STPB",
    "Ім'я": "Зупинка B",
    "Деталь": "Біля річки"
   }
 ]
}

І коли є виняток (наприклад, пошук поїздки між двома зупинками, які не пов’язані жодним автобусом), відповіді надсилаються назад (результат запиту GET "api / v1 / booking / tripsbystops"):

{
  "Статус": "NOT_FOUND",
  "Помилки": "Наразі жодних відключень між зупинкою джерела -" STPD "та пунктом призначення -" STPC "недоступні."
}
{
  "Статус": "NOT_FOUND",
  "Помилки":
  {
    "Часова марка": "2019–03–13T07: 47: 10.990 + 0000»,
    "Message": "Запрошена зупинка з кодом - STPF не існує."
  }
}

Як ви можете помітити, обидва типи відповідей, один із HTTP-200 та інший з HTTP-404, корисне навантаження відповіді не змінює свою структуру, і викликова система може сліпо прийняти відповідь, знаючи, що є стан та помилка або поле корисного навантаження в об'єкті JSON.

Документація API

Важливо так само задокументувати свою роботу (як і її розвиток) і переконатися, що API Spring Boot доступні для читання для команд, що працюють за кордон (внутрішніх) або зовнішніх споживачів. Інструмент для документації API, що використовується в цьому наборі для початківців, є Swagger2, ви можете відкрити його всередині браузера за наступною URL-адресою -

http: // localhost: 8080 / swagger-ui.html

Він представить вам добре структурований інтерфейс, який має дві характеристики:

1. Користувач
2. БРС

Ви можете використовувати специфікацію користувача для виконання api для входу для генерації маркера Bearer. Потім маркер слід застосувати у спливаючому вікні "Авторизувати", який за замовчуванням застосує його до всіх захищених apis (дістати та опублікувати обидва). Будь ласка, майте на увазі, що перед тим, як застосувати його в діалоговому вікні авторизації, слід додати слово "Носій", а потім пробіл перед маркером.

Конфігурація Swagger піклується класом BrsConfiguration. Я визначив там дві характеристики за допомогою методів “swaggerBRSApi” та “swaggerUserApi”. Оскільки частина входу за замовчуванням піклується Spring Security, ми не отримуємо експозицію її apis неявно, як решту apis, визначені в системі, і з тієї ж причини я визначив контролер у конфігураційному пакеті з назвою "FakeController":

Її мета - сприяти створенню нестабільної документації для входу та виходу apis, вона ніколи не виникне протягом життєвого циклу програми, оскільки "/ api / auth" api обробляється фільтрами безпеки, визначеними в кодовій базі. Ось кілька зразкових скріншотів, які допоможуть вам трохи краще візуалізувати речі:

Інтерфейс Swagger/ api / auth логін, ввічливість FakeControllerДіалогове вікно авторизації для реєстрації маркерів носіяAPI BRSПерелічені API BRS

Щоб використовувати інтерфейс Swagger та виконати захищені API, вам потрібно спочатку керувати “/ api / auth” з специфікації користувача та генерувати маркер Bearer. Після видачі маркера ви можете зареєструвати його у спливаючому вікні авторизації, а потім перейти до специфікації BRS для виконання захищених API. Якщо ви не зареєструєте маркер, ви продовжуватимете отримувати помилку HTTP 401.

Ви можете зіткнутися з проблемами, випробовуючи Swagger-Ui для тих HTTP-GET запитів, у яких є тіло з параметрами, не хвилюйтеся, це не помилка коду. Це рішення Swagger припинити підтримувати параметри тіла для запитів GET і DELETE . Як вирішення, ви можете скопіювати запит на згортання з swagger і виконати його всередині вікна терміналу, і це повинно спрацювати нормально, або ви можете вибрати для пошти або подібного інструменту, оскільки вони ще не застосовують такого жорсткого обмеження. На мою думку, доки специфікації Open API не обмежують параметри тіла в GET-запитах, такі інструменти, як Swagger, теж не повинні, однак це заклик, який вони повинні робити, а не ми як розробники.

Архітектура інтерфейсу

Користувацький інтерфейс для адміністратора порталу розроблений з використанням дизайну матеріалів за допомогою програми Bootstrap та адаптивного веб-додатка. Користувацький інтерфейс - серверний сервер, що використовується за допомогою шаблонів Thymeleaf (бажаний двигун шаблонів навесні).

Стандартний спосіб роботи з Чебрецем - це використання включає. Це досить часто призводить до повторюваного коду, особливо, коли веб-сайт має багато сторінок і кожна сторінка має кілька компонентів для багаторазового використання (наприклад, заголовок, навігація, бічна панель та колонтитул). Це повторюється, оскільки кожна сторінка вмісту повинна містити однакові фрагменти в одних і тих же місцях. Це також має негативний ефект, коли змінюється макет сторінки, наприклад при покладенні бічної панелі зліва в праву сторону.

Візерунок декораторів, використовуваний діалектом "Маточка чебрецю", вирішує ці проблеми. У контексті двигунів шаблонів шаблон декоратора більше не працює з включеними на сторінках із вмістом, але стосується загального файлу шаблонів. Кожна сторінка в основному надає лише основний вміст і описуючи, який основний шаблон для використання механізму шаблонів може створити кінцеву сторінку. Вміст прикрашається файлом шаблону. Цей підхід має переваги порівняно зі стандартним способом включення фрагментів:

  • Сама сторінка має лише надавати вміст.
  • Оскільки файл шаблону використовується для побудови кінцевої сторінки, глобальні зміни можна легко застосувати.
  • Код стає коротшим і чистішим.
  • Оскільки кожна сторінка вмісту посилається на те, який файл шаблону використовувати, легко використовувати різні шаблони для різних областей програми (наприклад, публічна область та область адміністратора).

Макет порталу адміністратора розроблений таким чином:

Блог, як макет

Окремі області цього макета служать наступному призначенню:

  • Заголовок: цей фрагмент використовується для статичного імпорту (CSS та JavaScript), заголовка та метатегів.
  • Навігація: панель навігації з профілем користувача вгорі праворуч.
  • Вміст: заповнення вмісту, яке буде замінено запитуваною сторінкою.
  • Бічна панель: бічна панель для отримання додаткової інформації.
  • Footer: область нижнього колонтитулу, яка надає інформацію про авторські права.

Ці компоненти можуть бути розміщені в каталозі ресурсів / шаблонів у корені, а також під фрагментами та компонуванням підкаталогів. В області вмісту цього макета розміщуються наступні сторінки:

  • Панель приладів
  • Агентство
  • Автобус
  • Подорож
  • Профіль

Крім того, сторінка помилок для будь-якого необробленого винятку розроблена з назвою "error.html". Сторінки для входу та реєстрації розроблені окремо від порталу, доступного користувачеві, який увійшов.

Запуск сервера локально

Щоб мати змогу запустити цю програму Spring Boot, спочатку потрібно створити її. Щоб скласти та упакувати додаток Spring Boot в єдиний виконуваний файл Jar з Maven, скористайтеся командою нижче. Вам потрібно буде запустити його з папки проекту, яка містить файл pom.xml.

пакет Maven

або ви також можете використовувати

mvn чистий встановити

Для запуску програми з вікна терміналу ви можете скористатися командою java -jar. Це за умови, що додаток Spring Boot було запаковано у вигляді виконуваного файлу jar.

java -jar target / springboot-starterkit-0.0.1-SNAPSHOT.jar

Ви також можете використовувати плагін Maven для запуску програми. Використовуйте наведений нижче приклад, щоб запустити додаток Spring Boot за допомогою плагіна Maven:

mvn spring-boot: запустити

Ви можете дотримуватися будь-яких / всіх перерахованих вище команд або просто використовувати конфігурацію запуску, надану вашим улюбленим IDE, та запустити / налагодити додаток звідти для цілей розвитку. Після налаштування сервера ви зможете отримати доступ до інтерфейсу адміністратора за такою URL-адресою:

http: // localhost: 8080

А API REST можна отримати через наступний базовий шлях:

http: // localhost: 8080 / api /

Деякі важливі кінцеві точки api такі:

  • http: // localhost: 8080 / api / v1 / user / реєстрація (HTTP: POST)
  • http: // localhost: 8080 / api / auth (HTTP: POST)
  • http: // localhost: 8080 / api / v1 / бронювання / зупинки (HTTP: GET)
  • http: // localhost: 8080 / api / v1 / бронювання / tripsbystops (HTTP: GET)
  • http: // localhost: 8080 / api / v1 / бронювання / графіки поїздок (HTTP: GET)
  • http: // localhost: 8080 / api / v1 / booking / bookticket (HTTP: POST)

Контейнер Docker

Будь ласка, використовуйте таку команду для створення образу контейнера:

docker build -t весна / стартеркіт.

І наступна команда для запуску контейнера:

docker run -p 8080: 8080 весна / стартеркіт

Зверніть увагу, коли ви створюєте зображення контейнера, і якщо mongodb працює локально у вашій системі, вам потрібно буде вказати IP-адресу вашої системи (або хмарну IP-адресу бази даних) у файлі application.properties (або env vars), щоб мати можливість підключитися до бази даних зсередини контейнера.

Висновок

Як бачимо, набір для початківців створений для того, щоб заощадити години кодування, надаючи обтічний, ефективний та чітко кодований інтерфейс та архітектуру, на якій можна базувати власну розробку. Спробуйте це і обов'язково повідомте мені ваші відгуки.

Я сподіваюсь, що ця серія з двох статей допомогла вам покращити набір навичок Spring Boot і що вона дала вам надійну платформу для початку написання наступного додатка Spring Boot.

Щасливого кодування всім!

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

Чи знаєте ви, що можете дати до 50 хлопок?