№
|
Название атрибута
|
Тип данных
|
Описание
|
1
|
id
|
Целочисленный
|
Идентификатор места
|
2
|
name
|
Строковый
|
Название места
|
3
|
latitude
|
Число с плавающей точкой
|
Координата широты места на карте
|
4
|
longitude
|
Число с плавающей точкой
|
Координата долготы места на карте
|
В приложении 2 представлена UML - диаграмма приведенных выше сущностей.
2.4 Выбор инструментальных средств
Для системы необходимо разработать как серверную, так и клиентскую части,
и это требует ответственного отношения к выбору инструментальных средств анализа,
проектирования, разработки и сборки разрабатываемого проекта.
Для написания серверной части был выбран язык программирования Java и написанный на нем фреймворк Jooby.
Язык Java - это объектно-ориентированный
строго типизированный язык программирования, который обладает большой
вычислительной мощностью, производительностью, переносимостью. Язык хорошо
поддерживается разработчиками, имеет обширную документацию и постоянно
развивается. Для выполнения программ, написанных на Java, требуется специальная виртуальная машина, что
является ключевой особенностью языка.
Фреймворк - это готовый набор решений конкретной задачи или проблемы,
чаще всего использующий простую концептуальную структуру.
Jooby
- это фреймворк, который обладает довольно простой и эффективной программной
моделью для разработки как маленьких, так и больших веб-приложений. Jooby имеет простую систему маршрутизации
и способен работать с различными серверами, базами данных, сессией, средствами
безопасности, системами кэширования и многим другим благодаря концепции
модулей. Если необходимо подключить базу данных, то нужно просто определить
соответствующий модуль и настроить его в специальном файле [4].
Клиентская часть, а точнее приложение для платформы Android, разрабатывается с помощью набора программных
средств для разработки гибридных приложений таких, как:
- Cordova;
- Ionic;
- Angular2.
Гибридное приложение - это приложение, написанное с использованием
технологий HTML, CSS и JavaScript, которое в конечном счете трансформируется в мобильное приложение.
Cordova
- это утилита, которая осуществляет сборку приложения в готовый пакет
мобильного приложения. Поддерживаемые платформы: iOS, Android,
Windows Phone, Browser.
Для платформы Android пакет будет иметь расширение apk (формат архивных исполняемых
файлов-приложений для ОС Android)
[5].
Angular2
- это фреймворк для создания различных приложений: мобильных, веб-приложений и
приложений для настольного компьютера. Он написан на языке TypeScript и его ключевой особенностью является
повторное использование компонентов, внедрение зависимостей и делегирование
бизнес - логики в специальные сервисы. Компонент в Angular2 - это совокупность шаблона (представления) и кода,
который его контролирует. Внедрение зависимости - это процесс предоставления
внешней зависимости программному компоненту [6].
TypeScript - это расширение языка JavaScript, позволяющее писать код в объектно-ориентированном стиле и определять
типы данных, как в статически-типизированных языках [7].
Фреймворк Ionic
предоставляет набор готовых компонентов Angular2, стилей для проектирования интерфейса мобильного
приложения. Это могут быть компоненты навигации в мобильном приложении,
контекстного меню, всплывающих окон и другие полезные компоненты. Для каждой
мобильной платформы определены свои стили [8].
3. Программная реализация
.1 Определение программных модулей
Модуль - это ключевой концепт для создания многократно используемых и
настраиваемых частей программного обеспечения.
Как было отмечено в предыдущей главе, для реализации серверной части
используется веб - фреймворк Jooby,
написанный на Java. Фреймворк сам по себе состоит из
модулей, которые представляют собой Java - классы, реализующие интерфейс Module.
Jooby
поддерживает большой набор готовых к использованию модулей, помимо этого есть
возможность определять свои.
При реализации серверной части были использованы следующие программные
модули:
- Модуль App;
- Модуль Auth;
Модуль Jackson;
Модуль Vk.
App -
это главный модуль приложения, который содержит в себе другие модули и является
точкой входа в приложение. В этом модуле происходит определение и конфигурация
других модулей, а также определяются маршруты (адреса) к API приложения, реализующие методы GET, POST, PUT, DELETE протокола HTTP посредством переопределения соответствующих методов get(), post(), put(),
delete() класса Router.
Auth -
это готовый модуль аутентификации, при помощи которого можно защитить API серверного приложения от
несанкционированного доступа. Этот модуль является оберткой над популярной java-библиотекой безопасности pac4j, которая в свою очередь поддерживает различные клиенты и
протоколы аутентификации. В нашем случае, в качестве протокола аутентификации
выступает OAuth - открытый протокол, который позволяет третьей
стороне получить доступ к защищенным ресурсам без необходимости передавать
логин и пароль.
Jackson
- это готовый модуль, который автоматически приводит содержимое (тело)
приходящих на сервер приложений HTTP-запросов,
а также отправляемых с него ответов в формат JSON. JSON -
это удобочитаемый текстовый формат обмена данными, основанный на JavaScript.
Этот формат отлично подходит для взаимодействия клиентской и серверной частей
системы, так как клиентская часть написана на языке JavaScript имеющем встроенную поддержку данного
формата.
Vk -
это модуль, позволяющий взаимодействовать с API сайта Вконтакте через java-библиотеку, которую предоставляют разработчики данной
социальной сети. В данном API
интерес представляют два класса: Search
и Group. Методы первого класса позволяют
найти с помощью глобального поиска все события (встречи), получить их
идентификаторы, а затем с помощью методов класса Group по идентификатору получить информацию о событии [9].
Структурная схема Jooby
- модулей представлена на изображении ниже.
Рисунок 3.1 - Структурная схема модулей
Клиентская часть реализована при помощи фреймворка Angular2, который также обладает модульной
системой.
Модуль Angular2 - это класс, который помечен
декоратором @NgModule, принимающим метаданные, которые
подсказывают компилятору, как компилировать и запускать данный модуль. Он в
свою очередь идентифицирует свои компоненты и сервисы, делая их доступными
модулю, который его использует.
Следующие модули Angular2
были использованы при разработке:
- AppModule
- IonicModule
BrowserModule
- HttpModule
AgmCoreModule
AppModule - это главный модуль Angular2
приложения. Он содержит в себе другие модули и является входной точкой в
приложение. Все создаваемые компоненты и услуги содержатся и поставляются
данным модулем.
IonicModule - это модуль, который занимается начальной загрузкой всех компонентов и
сервисов фреймворка Ionic. Все
компоненты и сервисы становятся доступными в главном модуле. Именно этот модуль
определяет внешний вид гибридного приложения.
BrowserModule - это модуль, позволяющий корректно отобразить запущенное Angular2 приложение в браузере.
HttpModule - это модуль доступа к web.
Данный модуль обладает набором сервисов, при помощи которых можно отправлять HTTP-запросы на сервера и получать HTTP-ответы. Запросы можно отправлять как
синхронно, так и асинхронно. Преимущество асинхронных запросов в том, что они
позволяют не прерывать поток выполнения приложения, а доверить результат
запроса специальному объекту Promise,
из которого затем можно извлечь соответствующий ответ, если он успешен, иначе
обработать ошибку.
LocalStorageModule - это модуль, реализующий локальное хранилище данных,
которое позволяет хранить данные в веб-браузере. При помощи этого модуля
реализовано хранение мероприятий (событий) на мобильном устройстве.
AgmCoreModule - это модуль, реализующий API Google Карты.
Данный модуль предоставляет готовый компонент AgmMap, с помощью которого можно отобразить карты, а также
компонент AgmMarker, который представляет собой маркер в
определенном месте карты. Для того, чтобы задать точку на карте, необходимо
инициализировать атрибуты latitude (широта) и longitude
(долгота) этих компонентов.
Структурная схема Angular2
- модулей представлена на изображении ниже.
Рисунок 3.2 - Структурная схема модулей
Листинг программного кода с объявлением модулей приведен в приложении 3.
.2 Разработка алгоритмов
В общем случае функционирование системы можно представить следующей
схемой.
Мобильное приложение при помощи методов модуля HttpModule отправляет асинхронный HTTP - запрос на сервер. В результате
вызова методов этого модуля HttpModule возвращается объект Promise,
который имеет две функции обратного вызова (callback), которые срабатывают в случаях,
если запрос выполнен успешно или с ошибками. Благодаря асинхронному запросу
интерфейс приложения не блокируется [10].
Рисунок 3.3 - Общая схема функционирования системы
Сервер предоставляет мобильному приложению свое API для получения списка событий, события по
идентификатору, по имени и т. п. Он настроен таким образом, что прослушивает
приходящие на него запросы и передает их маршрутизатору, в котором описаны
маршруты приложения.
Маршрут описывает интерфейс, на который направляются запросы к
приложению. Он сочетает в себе HTTP -
метод и шаблон пути (path pattern, url). Маршрут имеет ассоциированный с
ним обработчик, который выполняет какую - либо работу и производит выходные
данные, HTTP - ответ.
Обработчик в нашем случае обращается к API сайта Вконтакте при помощи модуля Vk, получает оттуда запрашиваемые
данные и формирует ответ мобильному приложению.
Формирование ответа происходит после того, как получен ответ от сервера
Вконтакте и включает в себя маппинг (отображение состояния одного объекта на
состояние другого) полученных данных в объект события Event, который в дальнейшем при помощи модуля Jackson преобразуется в формат для передачи
данных JSON.
Сформированный ответ направляется обратно мобильному приложению в виде HTTP - ответа с содержимым в виде JSON - объекта. Обработчик асинхронного
запроса, инициированного в мобильном приложении, срабатывает и приложение
отображает полученные данные в своем интерфейсе.
Таким образом можно описать общий алгоритм работы системы, но помимо
этого, стоит более подробно остановиться на следующих алгоритмах системы:
- алгоритм получения списка событий с помощью API сайта Вконтакте;
- алгоритм поиска события в мобильном приложении.
Алгоритм получения списка событий с помощью API сайта Вконтакте описан ниже.
Сначала, для того, чтобы получить доступ к API сайта Вконтакте, необходимо создать новое приложение
на странице приложений сайта Вконтакте и получить специальные ключи: APP_ID (идентификатор приложения), CLIENT_SECRET
(секретный ключ) и REDIRECT_URI (доверенный адрес переадресации).
Далее нужно создать экземпляр специального объекта API VkApiClient, передав в него любой подходящий
транспортный клиент, например HttpTransportClient и пройти авторизацию с помощью механизма Authorization Code Flow, основанного на технологии OAuth []. После этого в GET запросе будет получен специальный ключ «code», который необходимо передать в
метод userAuthorizationCodeFlow() в качестве параметра вместе с другими: APP_ID, CLIENT_SECRET и REDIRECT_URI. В результате выполнения метода будет возвращен
объект класса UserAuthResponse, при помощью которого создается
класс UserActor. UserActor - это пользователь, от имени
которого можно обращаться к API
Вконтакте [9].
Часть алгоритма, связанная с авторизацией, выполняется администратором
после запуска сервера.
Для того, чтобы получить список событий или точнее список групп Вконтакте
с типом event, нужно сначала их найти. Для этого
следует обратиться от имени пользователя к методу getHints() объекта Search API с параметром фильтрации, установленным в значение groups и параметром global_search, равным 1. В результате будет возвращен массив
«сырых» объектов, содержащих идентификаторы, из которого нужно извлечь группы.
Группы имеют идентификаторы. Для того, чтобы получить полную информацию о
группе, нужно обратиться к методу getById() объекта Group API. Это
необходимо сделать для каждого объекта, полученного в результате поиска. Таким
образом будет получен список объектов Group. Объекты списка нужно преобразовать в объект Event. Для лучшего понимания ниже представлена блок - схема
данного алгоритма.
Рисунок 3.4 - Блок - схема алгоритма получения списка событий с помощью API Вконтакте
Алгоритм поиска события в мобильном приложении описан ниже.
В мобильном приложении присутствует функция поиска событий по названию.
Когда пользователь вводит какой - либо символ или последовательность символов,
то ему возвращается список с мероприятиями, в названиях которых содержатся
данные символы. Для этих целей используются асинхронные запросы к API нашего сервера, которые возвращают
объекты Observable, вместо Promise.
Observable - это поток каких-либо, который можно обрабатывать операторами, которые
применимы к массиву. Данный объект позволяет создавать запрос, отменять его не
дожидаясь ответа и затем создавать новый. Такой механизм трудно реализовать с
помощью Promise [11].
Когда пользователь вводит символ в специальном окне ввода компонента
приложения, то компонент прослушивает событие keyUp (нажатие клавиши), и прежде чем на сервер
отправляется запрос, приложение ожидает 300 миллисекунд для того, чтобы не
делать частые запросы. Запрос не отправляется, если пользователь ввёл символ,
соответствующий предыдущему.
Рисунок 3.5 - Блок - схема алгоритма поиска событий в мобильном
приложении
В результате запроса возвращается список объектов Observable, который с помощью оператора
приведения преобразуются в список объектов Event, список может быть пустым. Если в результате
произошла ошибка, то также возвращается пустой список мероприятий.
Блок - схема алгоритма представлена на изображении 3.5 на следующей
странице.
Таким образом, в этом разделе были рассмотрены алгоритмы приложения,
особое внимание было уделено алгоритмам обращения к API сайта Вконтакте и поиска события в мобильном
приложении. Остальные алгоритмы, используемые в приложении, были уже
реализованы фреймворками.
В приложении 4 представлены листинги кода реализованных алгоритмов
системы.
.3 Особенности программной реализации
Главной особенностью программной реализации, на мой взгляд, является
модульность и использование готовых решений: библиотек, фреймворков,
инструментов. Модульность позволяет повторно использовать какие - либо части
программной системы и настраивать их определенным образом. При реализации
серверной части был использован модульный фреймворк Jooby, а при реализации клиентской - Angular2. Подобные средства позволяют «не
изобретать велосипед заново», а использовать надежное, качественное, чётко -
структурированное решение конкретной проблемы, реализованное и поддерживаемое
профессиональными разработчиками.
Еще одной особенностью является использование объектно - ориентированных
языков программирования (ООП) c
возможностями функциональных языков программирования, таких как Java (версия 8) и TypeScript. ООП позволяет выделять реальные
объекты окружающего мира и работать с ними, определив их состояния и поведения.
Так, в проекте были выделены два объекта Event и Place,
представляющие городское мероприятие и место его проведения. Функциональное
программирование повышает надёжность кода и позволяет применять к программам
достаточно сложные методы автоматической оптимизации, улучшая тем самым
производительность.
Еще одной особенностью реализации является использование сетевой клиент -
серверной архитектуры. Для передачи данных между клиентом и сервером
применяется повсеместно используемый протокол прикладного уровня HTTP.
Особенностью серверной части является использование встроенного в
приложение сервера приложений. Это означает, что приложение не нужно
разворачивать на каком - то отдельном сервере приложений, а достаточно его
(приложение) запустить, следом за ним запустится встроенный сервер,
прослушивающий входящие запросы на настроенном имени хоста и порту.
Клиентское приложение на платформе Android особенно тем, что является гибридным. Оно основано на
технологиях HTML, CSS и JavaScript и их расширений, с помощью которых создается веб - проект. Затем этот
проект с помощью специальной программной утилиты Cordova собирается в готовый пакет приложения Android.
Таким образом, были рассмотрены основные особенности программной
реализации системы.
4. Тестирование
.1 Функциональное тестирование
Функциональное тестирование - это тестирование программного обеспечения, целью которого является проверка реализуемости
функциональных требований, то есть способности программного обеспечения в
определенных условиях решать заявленные задачи.
Функционал серверной части информационно - справочной системы
тестировался при помощи модульных и интеграционных тестов. Модульное
тестирование - это тестирование отдельного компонента системы в изоляции от
других. Интеграционное - это тестирование, при котором отдельные компоненты
объединяются и тестируются в месте.
Основная часть функционала системы была реализована в клиентском
приложении.
Рисунок 4.1 - Просмотр списка событий и детальный просмотр
Рисунок 4.2 - Просмотр места проведения событий на карте и поиск
Рисунок 4.3 - Возможность поделиться в соц. сетях и сохранить событие
На рисунках 4.1 - 4.3 следующей страницы приведены изображения интерфейса
приложения на платформе Android
с реализованными функциями, такими, как: просмотр списка событий, просмотр конкретного
события с отметкой на карте, поиск конкретного события, возможность добавить
событие в календарь, возможность поделиться событием в социальных сетях,
возможность сохранить событие.
Приложение тестировалось в эмуляторе, поэтому некоторые функции протестировать
не удалось, в частности: возможность поделиться событием в социальных сетях и
добавление события в календарь; на устройстве данные функции полностью
поддерживаются.
.2 Тестирование производительности
Тестирование производительности - это тестирование, которое проводится с
целью определения времени работы программного обеспечения или его части под
определенной нагрузкой. Оно также может служить для проверки и подтверждения
других показателей системы, таких как масштабируемость, надёжность и потребление
ресурсов.
Тестирование системы на производительность в нашем случае может носить
только оценочных характер, так как тестирование проводилось на локальной
машине. Развертывании системы на более мощных серверах может повысить
показатели.
Тестировалась серверная часть системы, то есть получение списка событий,
получение события по идентификатору, получение события по названию.
Тестирование производительности проводилось с помощью свободно -
распространяемого инструмента для нагрузочного тестирования Apache JMeter, в конфигурации перед запуском тестов которого
указываются группа потоков (количество пользователей), шаблон запроса (тип
запроса, хост, порт и шаблон пути) и таймер (время паузы между запросами) [12].
В качестве шаблона запроса к API системы был HTTP - запрос типа
GET со следующими шаблонами пути (id - число, идентификатор события, name - строка, название события):
- #"897356.files/image001.gif">
Приложение 2
- диаграмма модели данных
Приложение 3
Листинг кода программных модулей серверной части (Java):
package
com.dynnoil.afisha;org.jooby.Jooby;org.jooby.auth.Auth;org.jooby.jackson.Jackson;com.dynnoil.afisha.modules.vk.*;class
App extends Jooby {
{(new Jackson());(new Vk());(new Auth().client(config ->
{String key = config.getString("vk.app_id");String secret =
config.getString("vk.client_secret");new VkClient(key, secret);
}));
}static void main(final String[] args) {
run(App::new, args);
}
Листинг кода программных модулей клиентской части (TypeScript):
import { NgModule, ErrorHandler } from '@angular/core';{
BrowserModule } from '@angular/platform-browser';{ IonicApp, IonicModule,
IonicErrorHandler } from 'ionic-angular';{ MyApp } from './app.component';{
LocalStorageModule } from 'angular-2-local-storage';{ AgmCoreModule } from
'@agm/core';{ HttpModule } from '@angular/http';{ SearchPage } from
'../pages/search/search';{ FavoritesPage } from
'../pages/favorites/favorites';{ HomePage } from '../pages/home/home';{
TabsPage } from '../pages/tabs/tabs';{ DetailsPage } from '../pages/details/details';{
EventCardComponent } from '../components/event-card/event-card.component';{
GoogleMapsComponent } from '../components/google-maps/google-maps.component';{
StatusBar } from '@ionic-native/status-bar';{ SplashScreen } from
'@ionic-native/splash-screen';{ SocialSharing } from
'@ionic-native/social-sharing';{ Calendar } from '@ionic-native/calendar';{
InMemoryWebApiModule } from 'angular-in-memory-web-api';{ AlertsService } from
'../services/alerts.service';{ ActionSheetService } from '../services/action-sheet.service';{
ToastService } from '../services/toast.service';{ EventService } from
'../services/event.service';{ SharingService } from
'../services/sharing.service';{ EventStorageService } from
'../services/event-storage.service';{ InMemoryDataService } from
'../services/in-memory-data.service';
@NgModule({: [,,,,,,,
],: [,,.withConfig({: 'afisha-app',: 'localStorage'
}),.forRoot({: 'AIzaSyDsBqAWWUYSX5XmYSmuTHCvmaxzGrIR_8w'
}),.forRoot(InMemoryDataService),.forRoot(MyApp)
],: [IonicApp],: [,,,,,
],: [,,,,
{ provide: ErrorHandler, useClass: IonicErrorHandler },,,,,,
EventStorageService
]
})class AppModule { }
Приложение 4
Листинг кода алгоритма получения списка событий с помощью API сайта Вконтакте (Java):
com.dynnoil.afisha.modules.vk;
import com.fasterxml.jackson.databind.ObjectMapper;com.google.inject.Binder;com.typesafe.config.Config;com.typesafe.config.ConfigFactory;com.vk.api.sdk.client.TransportClient;com.vk.api.sdk.client.VkApiClient;com.vk.api.sdk.client.actors.UserActor;com.vk.api.sdk.httpclient.HttpTransportClient;com.vk.api.sdk.objects.UserAuthResponse;com.vk.api.sdk.objects.groups.Group;com.vk.api.sdk.objects.groups.GroupType;com.vk.api.sdk.objects.groups.responses.SearchResponse;org.jooby.*;java.util.List;
/**
* Vk module
* <p>
* Created by SBT-Krukov-LYu on 14.03.2017.
*/class Vk implements Jooby.Module {UserActor
actor;VkApiClient vkApiClient;
@Overridevoid configure(Env env, Config config, Binder
binderthrows Throwable {.onStart(() -> {transportClient =
HttpTransportClient.getInstance();= new VkApiClient(transportClient);
});router = env.router();.get("/auth", (Request rq,
Response rs) -> {String code = rq.param("code").to(String.class,
MediaType.plain);(config, code);.send("Done!");
});.get("/api/", (rq, rs) -> {searchResponse =
vkApiClient.groups().search(actor, "")
.type(GroupType.EVENT.getValue())
.countryId(1)
.cityId(41)
.future(true)
.execute();<Group> groups =
searchResponse.getItems();json = new ObjectMapper().writeValueAsString(groups);.send(Results.json(json));
});
}
@OverrideConfig config()
{ConfigFactory.parseResources(Vk.class, "vk.conf");
}void initializeUserActor(Config config, String code) throws
Throwable {Integer appId =
Integer.valueOf(config.getString("vk.app_id"));String clientSecret =
config.getString("vk.client_secret");String redirectUri =
config.getString("vk.redirect_uri");authResponse =
vkApiClient.oauth()
.userAuthorizationCodeFlow(appId, clientSecret, redirectUri,
code)
.execute();= new UserActor(authResponse.getUserId(),
authResponse.getAccessToken());
}
}
Листинг кода алгоритма поиска события в мобильном приложении (TypeScript):
import { Component, OnInit } from '@angular/core';{
NavController } from 'ionic-angular';{ Observable } from 'rxjs/Observable';{
Subject } from
'rxjs/Subject';'rxjs/add/observable/of';'rxjs/add/operator/catch';'rxjs/add/operator/debounceTime';'rxjs/add/operator/distinctUntilChanged';'rxjs/add/operator/switchMap';{
Event } from '../../model/event';{ EventSearchService } from '../../services/event-search.service';
@Component({: 'page-search',: 'search.html',:
[EventSearchService]
})class SearchPage implements OnInit {searchTerms = new
Subject<string>();: Observable<Event[]>;(navCtrl:
NavController,eventSearchService: EventSearchService
) { }(term: string): void {.searchTerms.next(term);
}(): void {.events = this.searchTerms
.debounceTime(300)
.distinctUntilChanged()
.switchMap(term => term
// return the http search observable
? this.eventSearchService.search(term)
// or the observable of empty heroes if there was no search
term
: Observable.of<Event[]>([]))
.catch(error =>
{.log(error);Observable.of<Event[]>([]);
});
}
}
<ion-header>
<ion-toolbar>
<ion-searchbar #searchBar
(ionInput)="search(searchBar.value)"></ion-searchbar>
</ion-toolbar>
</ion-header>
<ion-content padding>
<ion-list>
<ion-item *ngFor="let event of events |
async">
{{ event.name }}
</ion-item>
</ion-list>
</ion-content>{ Injectable } from '@angular/core';{
Http } from '@angular/http';{ Observable } from
'rxjs/Observable';'rxjs/add/operator/map';{ Event } from '../model/event';
@Injectable()class EventSearchService {(private http: Http) {
}(term: string): Observable<Event[]> {this.http
.get(`api/events/?name=${term}`)
.map(response => response.json().data as Event[]);
}