Русский
Русский
English
Статистика
Реклама

Vue.js и слоистая архитектура вынесение бизнес-логики в сервисы

Когда нужно сделать код в проекте гибким и удобным, на помощь приходит разделение архитектуры на несколько слоев. Рассмотрим подробнее этот подход и альтернативы, а также поделимся рекомендациями, которые могут быть полезны как начинающим, так и опытным разработчикам Vue.js, React.js, Angular.

В старые времена, когда JQuery только появился, а о фреймворках для серверных языков лишь читали в редких новостях, веб-приложения реализовывали целиком на серверных языках. Зачастую для этого использовали модель MVC (Model-View-Controller): контроллер (controller) принимал запросы, отвечал за бизнес-логику и модели (model) и передавал данные в представление (view), которое рисовало HTML.

Объектно-ориентированное программирование (ООП) на тот момент только начинало формироваться, поэтому разработчики зачастую интуитивно решали, где и какой код надо писать. Таким образом, в мире разработки зародилось такое понятие, как Божественные объекты, которые первоначально отвечали практически за всю работу отдельных частей системы. Например, если в системе была сущность Пользователь, то создавался класс User и в нем писалась вся логика, так или иначе связанная с пользователями. Без разбиения на какие-то ещё файлы. И если приложение было большим, то такой класс мог содержать тысячи строк кода.

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

1. Выход есть

Как известно, Vue.js, React.js и прочие подобные фреймворки основаны на компонентах. То есть, по большому счету, приложение состоит из множества компонентов, которые могут заключать в себе и бизнес-логику и представление и много чего еще. Таким образом, разработчики во многих проектах пишут всю логику в компонентах и эти компоненты, как правило, начинают напоминать те самые божественные классы из прошлого. То есть, если компонент описывает какую-то крупную часть функционала с большим количеством (возможно сложной) логики, то вся эта логика и остается в компоненте. Появляются десятки методов и тысячи строк кода. А если учесть то, что, например, во Vue.js еще есть такие понятия как computed, watch, mounted, created, то логику пишут еще и во все эти части компонента. В итоге, чтобы найти какую-то часть кода, отвечающую за клик по кнопке, надо перелистать десяток экранов js-кода, бегая между methods, computed и прочими частями компонента.

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

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

Вот о таком разбиении кода на слои и пойдет речь, но уже применительно к frontend-фреймворкам, таким как Vue.js, React.js и прочим.

Изначальная теория слоистой архитектуры, применительно к backend, имеет много ограничений и правил. Идея же этой статьи в том, чтобы перенять именно разбиение кодовой базы на слои. Схематично ее можно изобразить примерно так.

2. Создание удобной архитектуры приложения

Рассмотрим пример, в котором вся логика находится в одном компоненте.

2.1. Логика в компоненте

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

methods: {    duplicateCollage (collage) {      this.$store.dispatch('updateCollage', { id: collage.id, isDuplicating: true })      dataService.duplicateCollage(collage, false)        .then(duplicate => {          this.$store.dispatch('updateCollage', { id: collage.id, isDuplicating: false })        })        .catch(() => {          this.$store.dispatch('updateCollage', { id: collage.id, isDuplicating: false })          this.$store.dispatch('errorsSet', { api: `We couldn't duplicate collage. Please, try again later.` })        })    },    deleteCollage (collage, index) {      this.$store.dispatch('updateCollage', { id: collage.id, isDeleting: true })      photosApi.deleteUserCollage(collage)        .then(() => {          this.$store.dispatch('updateCollage', {            id: collage.id,            isDeleting: false,            isDeleted: true          })          this.$store.dispatch('setUserCollages', { total: this.userCollages.total - 1 })          this.$store.dispatch('updateCollage', {            id: collage.id,            deletingTimer: setTimeout(() => {              this.$store.dispatch('updateCollage', { id: collage.id, deletingTimer: null })              this.$store.dispatch('setUserCollages', { items: this.userCollages.items.filter(userCollage => userCollage.id !== collage.id) })               // If there is no one collages left - show templates              if (!this.$store.state.editor.userCollages.total) {                this.currentTabName = this.TAB_TEMPLATES              }            }, 3000)          })        })    },    restoreCollage (collage) {      clearTimeout(collage.deletingTimer)      photosApi.saveUserCollage({ collage: { deleted: false } }, collage.id)        .then(() => {          this.$store.dispatch('updateCollage', {            id: collage.id,            deletingTimer: null,            isDeleted: false          })          this.$store.dispatch('setUserCollages', { total: this.userCollages.total + 1 })        })    }}

2.2. Создание слоя сервисов для бизнес-логики

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

Один из классических способов хоть какого-то разбиения логики это деление на сущности. Например, почти всегда в проекте есть сущность Пользователь или, как в описываемом примере, Коллаж. Таким образом, можно создать папку services и в ней файлы user.js и collage.js. Такие файлы могут быть статическими классами или просто возвращать функции. Главное чтобы вся бизнес-логика, связанная с сущностью, была в этом файле.

services  |_collage.js  |_user.js

В сервис collage.js следует поместить логику дублирования, восстановления и удаления коллажей.

export default class Collage {  static delete (collage) {    // ЛОГИКА УДАЛЕНИЯ КОЛЛАЖА  }   static restore (collage) {    // ЛОГИКА ВОССТАНОВЛЕНИЯ  КОЛЛАЖА  }   static duplicate (collage, changeUrl = true) {    // ЛОГИКА ДУБЛИРОВАНИЯ КОЛЛАЖА  }}

2.3. Использование сервисов в компоненте

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

methods: {  duplicateCollage (collage) {    CollageService.duplicate(collage, false)  },  deleteCollage (collage) {    CollageService.delete(collage)  },  restoreCollage (collage) {    CollageService.restore(collage)  }}

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

Также многие разработчики на пути к удобной архитектуре выносят вызовы методов API в отдельный файл (файлы). Это как раз создание слоя вызовов API, которое также приводит к удобству и структурированности кода.

import axios from '@/plugins/axios' export default class Api {   static login (email, password) {    return axios.post('auth/login', { email, password })      .then(response => response.data)  }   static logout () {    return axios.post('auth/logout')  }   static getCollages () {    return axios.get('/collages')      .then(response => response.data)  }    static deleteCollage (collage) {    return axios.delete(`/collage/${collage.id}`)      .then(response => response.data)  }    static createCollage (collage) {    return axios.post(`/collage/${collage.id}`)      .then(response => response.data)  }}

3. Что и куда выносить?

На вопрос, что же именно и куда выносить, однозначно ответить невозможно. Как вариант, можно разбить код на три условные части: бизнес-логика, логика и представление.

Бизнес-логика это все то, что описано в требованиях к приложению. Например, ТЗ, документации, дизайны. То есть все то, что напрямую относится к предметной области приложения. Примером может быть метод UserService.login() или ListService.sort(). Для бизнес-логики можно создать сервисный слой с сервисами.

Логика это тот код, который не имеет прямого отношения к предметной области приложения и его бизнес-логике. Например, создание уникальной строки или поиск некоего объекта в массиве. Для логики можно создать слой хэлперов: например, папку helpers и в ней файлы string.js, converter.js и прочие.

Представление все то, что непосредственно связано с компонентом и его шаблоном. Например, изменение реактивных свойств, изменение состояний и прочее. Этот код пишется непосредственно в компонентах (methods, computed, watch и так далее).

login (email, password) {  this.isLoading = true  userService.login(email, password)    .then(user => {      this.user = user      this.isLoading = false    })}

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

Если же сервисы или хэлперы начнут разрастаться, то сущности всегда можно разделить на другие сущности. К примеру, если у пользователя в приложении маленький функционал в 3-5 методов и пара методов про заказы пользователя, то разработчик может вынести всю эту бизнес-логику в сервис user.jsрешить всю эту бизнес-логику написать в сервисе user.js. Если же у сервиса пользователя сотни строк кода, то можно все, что относится к заказам, вынести в сервис order.js.

4. От простого к сложному

В идеале можно сделать архитектуру на ООП, в которой будут, помимо сервисов, еще и модели. Это классы, описывающие сущности приложения. Те же User или Collage. Но использоваться они будут вместо обычных объектов данных.

Рассмотрим список пользователей.

Классический способ вывода ФИО пользователей выглядит так.

<template><div class="users">  <div    v-for="user in users"    class="user"  >    {{ getUserFio(user) }}  </div></div></template> <script>import axios from '@/plugins/axios' export default {  data () {    return {      users: []    }  },  mounted () {    this.getList()  },  methods: {    getList() {      axios.get('/users')        .then(response => this.users = response.data)    },    getUserFio (user) {      return `${user.last_name} ${user.first_name} ${user.third_name}`    }  }}</script>

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

Для начала следует создать модель Пользователь.

export default class User {  constructor (data = {}) {    this.firstName = data.first_name    this.secondName = data.second_name    this.thirdName = data.third_name  }   getFio () {    return `${this.firstName} ${this.secondName} ${this.thirdName}`  }}

Далее следует импортировать эту модель в компонент.

import UserModel from '@/models/user'

С помощью сервиса получить список пользователей и преобразовать каждый объект в массиве в объект класса (модели) User.

methods: {   getList() {     const users = userService.getList()     users.forEach(user => {       this.users.push(new UserModel(user))     })   },

Таким, образом, в шаблоне или в методах не надо будет создавать какие-то отдельные функции для работы с объектом пользователя, они будут уже внутри этого объекта.

<template><div class="users">  <div    v-for="user in users"    class="user"  >    {{ user.getFio() }}  </div></div></template>

К вопросу о том, какую логику выносить в модели, а какую в сервисы. Можно всю логику поместить в сервисы, а в моделях вызывать сервисы. А можно в моделях хранить логику, относящуюся непосредственно к сущности модели (тот же getFio()), а логику работы с массивами сущностей хранить в сервисах (тот же getList()). Как будет удобнее.

5. Заключение

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

Также для удобства можно создать слои для операций с сессиями, перехватчиками (interceptors) api, глобальными обработчиками ошибок смотря что вам будет удобно. Таким образом, вы сделаете компоненты маленькими и простыми, а логика будет хранитьсятам, где ее легко будет найти и переиспользовать в любом месте проекта.

Спасибо за внимание! Будем рады ответить на ваши вопросы.

Источник: habr.com
К списку статей
Опубликовано: 10.05.2021 12:10:00
0

Сейчас читают

Комментариев (0)
Имя
Электронная почта

Блог компании simbirsoft

Javascript

Программирование

Ооп

Vuejs

Архитектура

Бизнес-логика

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru