Желание разработать собственный Angular.js webApi модуль возникло при работе с большим количеством http-запросов в проекте.
Важно было не просто создать файл с константами, а разработать некий модуль для упрощения поддержки существующего функционала. В свою очередь, необходимо было и позаботиться о возможном последующем расширении без нарушения целостности текущего ядра модуля.
Задачи, которые должен решать будущий webApi модуль:
- Предотвратить дублирование http-запросов в проекте.
- Группировать существующий список запросов по функциональным категориям, чтобы проще вносить правки в конкретные методы.
- Быть полностью независимой функциональной единицей приложения, которая подключается к любому другому Angular.js проекту простым Dependency Injection’ом.
- Инкапсулировать внутреннюю реализацию, чтобы избежать проблем при работе с внешними источниками.
Дальше поговорим о каждом из этих пунктов подробнее.
Дублирование 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
.
Рассмотрим каждый из вариантов генерации.
// 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/
Добавить комментарий