Разработка webApi модуля для Angular.js

от автора


Желание разработать собственный Angular.js webApi модуль возникло при работе с большим количеством http-запросов в проекте.

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

Задачи, которые должен решать будущий webApi модуль:

  1. Предотвратить дублирование http-запросов в проекте.
  2. Группировать существующий список запросов по функциональным категориям, чтобы проще вносить правки в конкретные методы.
  3. Быть полностью независимой функциональной единицей приложения, которая подключается к любому другому Angular.js проекту простым Dependency Injection’ом.
  4. Инкапсулировать внутреннюю реализацию, чтобы избежать проблем при работе с внешними источниками.

Дальше поговорим о каждом из этих пунктов подробнее.

Дублирование http-запросов

Речь идет об использовании одного запроса в нескольких местах приложения (контроллеры, сервисы, фабрики). Когда методов 10-20, то внести изменения в каждый запрос не составит большой проблемы. Но когда речь идет о количестве 100, 200 и более url’ов, поддержка такого кода вызывает все больше трудностей.

Пример #1

Есть метод, который получает список групп пользователей. Допустим, он выводится в соответствующий dropdown. При выборе группы происходит подгрузка ее юзеров по другому запросу, куда передается "id" группы. Также на странице имеется дополнительный функционал для визуализации каких-то данных пользователей.

// получаем все группы $http.get("api/group-manage/get-all")  // получаем пользователей выбранной группы $http.get("api/group-manage/2/get-users") 

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

// получаем пользователей выбранной группы $http.get("api/group-manage/2/get-users") 

В действительности похожих запросов может быть значительно больше.

Решение проблемы

Создать специальный файл с константами, содержащий список всех http-запросов на сервер, т.е. всех используемых в приложении url'ов. 

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

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

Группировать запросы по категориям

Этот подход позволит решить вытекающую выше проблему. Как именно будет происходить определение независимых категорий — определяет сам разработчик. Для простоты можно ориентироваться на имя контроллера-метода из api.

// http://yourapi.com/api/group-manage/2/get-users // http://yourapi.com/api/group-manage/get-all

Из примера выше видно, что в запросах есть общий корень /api/group-manage/. Создаем категорию с соответствующим названием groupManage.js.

В Angular.js среде данный файл объявляется как constant, который в дальнейшем подключается к основному функционалу webApi модуля.

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

Если же вызывать добавленный url напрямую, то рано или поздно появится череда однотипных зависимостей в коде. Поэтому, необходимо создать общий блок, предоставляющий список всех существующих запросов для оперирования ими в "ядре" webApi.

Инкапсуляция функционала

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

Пример запроса выглядит следующим образом:

{ Url: '/api/acc/login', CustomOptions: false, Method: 'post', InvokeName: 'login' }

  • customOptions — использовать ли дополнительные настройки запроса. Обычно там могут указываться header’ы для конкретного запроса, значение timeout, параметр withCredentials и др.

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

В директории webApi/config/ находится файл с настройками API. Именно там мы и указываем DOMAIN url.

Пример #2

Практически все современные Angular.js приложения работают с системой аутентификации. Обычно это post-метод, который отправляет на сервер данные юзера (login, password).

При успешном respons’e происходит оповещение главному роуту приложения, после чего пользователь будет перенаправляется на страницу с функционалом.

Вызываем метод:

webApi.login({     "Login": "user",     "Password": "qwerty" }).success(function(response, status, headers, config){     // какие-то действия })

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

// объявляем запрос в настройках { Url: '/api/acc/logout', CustomOptions: false, Method: 'get', InvokeName: 'logout' }  // где-то вызываем метод webApi.logout([]);

Наверное, сейчас не совсем понятно использование пустого массива в get-методе, но дальше об этом всем будет рассказано.

Шаблонизация запросов

Довольно часто при разработке приложения, сервер-сайд предоставляет клиенту следующий формат запросов:

  • /api/admin/delete/profile/{id}
  • /api/admin/update/profile/{id}/block

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

// объявляем запрос в настройках { Url: '/api/admin/update/profile/{id}/block', CustomOptions: false, Method: 'put', InvokeName: 'blockAdminProfileById' }  // где-то вызываем метод webApi.blockAdminProfileById({     "url": { "id": 5 } });

Сгенерированный запрос: /api/admin/update/profile/5/block (плюс domain url, разумеется).

И если нам нужно отправить на сервер более сложный запрос (например, длительность блокировки и тип), то просто указываем остальные параметры в качестве полей объекта "url":

// объявляем запрос в настройках { Url: '/api/admin/update/profile/{id}/block/{type}/{time}', CustomOptions: false, Method: 'put', InvokeName: 'blockAdminProfileById' }  // где-то вызываем метод webApi.blockAdminProfileById({     "url": {          "id": 5,         "type": "week",         "time": 2     } });

Сгенерированный запрос: /api/admin/update/profile/5/block/week/2. И теперь пользователь будет заблокирован системой на 2 недели.

Шаблонизация работает для всех типов запросов, включая get. Желательно все запросы формировать именно таким образом: экономим время, не засоряем внутренний код лишними операциями.

Передача данных в теле запроса

Следует отметить, что если Вы хотите отправить помимо шаблонизированного url на сервер еще и какие-то данные (например, post-запрос), то необходимо их передать следующим образом:

webApi.createPost({     "data": {         "title": "Test post",         "category": "sport",         "message": "Content..."     } });

Естественно, можно использовать одновременно и url-шаблонизацию, и передачу объекта с данными. Последний будет отправлен на сервер в теле запроса.

Работа с GET-методами

Тут никакие данные в теле запроса не передаются, но всем известно, что get-запрос может быть сформирован так:

api/admin/news/10?category=sport&period=week

или так:

api/admin/manage/get-statistic/5/2016

или же так:

api/admin/manage/get-all.

Рассмотрим каждый из вариантов генерации.

Примеры создания get-запроса

// Case #1 -> api/admin/manage/get-all // в настройках -> "Url" : 'api/admin/manage/get-all', ... // вызываем метод webApi.getAllAdmins([]).success(//...)  // Case #2 -> api/admin/manage/get-statistic/5/2016 // в настройках -> "Url" : 'api/admin/manage/get-statistic/{id}/{year}', ... // вызываем метод webApi.getAdminStatsticById({     "url": {          "id": 5,         "year": 2016     } }).success(//...)  // Case #3 -> admin/news/10?category=sport&period=week // в настройках -> "Url" : 'admin/news', ... // вызываем метод webApi.getNews({       before: ['10'],       after: {            "category": "sport",            "period": "week"        } }).success(//...)

Со вторым типом запросов мы уже разобрались выше.

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

В случае #3 поле before определяет ряд параметров, которые идут до знака "?", а поле after — набор "ключ-значение". Естественно, в некоторых случаях можно оставить before пустой коллекцией [].

Параметр CustomOptions в настройках

Get-запрос без шаблонизации url:

webApi.getNewsById([10, {"headers": {"Content-Type": "text/plain"} } ]);

Во всех остальных случаях (в том числе, get-запросы с шаблонизацией url):

webApi.login({   options: {"timeout": 100, {"headers": {"Content-Type": "text/plain"} } });

Настройка webApi в новом проекте

Структура модуля следующая:

  • файл module.js — объявление самого модуля;
  • директория main/ — содержит в себе ядро webApi, оно не изменяется;
  • директория categories — группы запросов, одна группа — один *.js файл;
  • директория categories-handler — регистратор всех запросов в webApi модуле.

Вам придется работать с последними двумя директориями.

Пример #3

Допустим, разрабатывается система учета книг в каком-то университете. Большая вероятность разбиения запросов на следующие группы:

  • account.js — запросы на авторизацию, деавторизацию, восстановление паролей и т.д.;
  • bookManage.js — запросы на CRUD-операции с книгами;
  • studentManage.js — менеджмент студентов;
  • adminManage.js — ряд запросов по управление админской частью приложения.

Конечно, этот список может быть расширен.

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

Объявляем новую категорию запросов

(function(){      angular         .module("_webApi_")         .constant("cat.account", {              "DATA": [                  { Url: '/api/acc/login', CustomOptions: false, Method: 'post', InvokeName: 'login' },                 // остальные запросы              ]          });  })();

Файл с запросами создан. Теперь нужно связать его с нашим webApi ядром.

Добавляем группу запросов в специальный обработчик

(function(){      angular         .module("_webApi_")         .service("webApi.requests", webApiRequests);      function webApiRequests(catAccount){          // специальные обработчики и регистраторы         // их изменять не нужно, только добавляем новые зависимости      }      // IoC container.     webApiRequests.$inject = [         "cat.account"     ];  })();

В данном случае все константы пишутся через "cat.имя константы", а подключаются в регистратор "catИмяКонстанты".

Таким образом, в webApi используется дополнительное пространство имен "cat.", чтобы не было конфликтов с другими константами в приложении.

И теперь вызываем метод согласно описанному шаблону:

webApi.login( //логин-пароль для авторизации )

Заключение

Мы рассмотрели вариант создания конфигурируемого и расширяемого webApi модуля для работы с Angular.js.

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

Demo

Посмотреть исходный код модуля: github.

ссылка на оригинал статьи https://habrahabr.ru/post/282397/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *