ВР.pdf. Реферат Магистерская
Скачать 3.61 Mb.
|
2.4 Схема база данных Схема базы данных создавалась на основе модели предметной области. Использовались шаблоны проектирования «Single Table Inheritance» и «Association Table Mapping» для отображения классов в таблицы. Рисунок 2.4.1 — Схема базы данных 22 2.5 API-Приложение Перейдем к более подробному рассмотрению устройства API-Приложения. Для начала определимся с его областью ответственности и функционалом. API-Приложение должно: ● Предоставлять REST API для использования одностраничным приложением; ● Аутентифицировать и авторизовывать пользователей; ● Манипулировать данными в СУБД, соблюдая все ограничения; ● Ставить задачи для отложенного выполнения; ● Отправлять задачу на выполнение при наступлении определенного времени. Диаграмма компонентов приведена на рисунке 2.5.1. Рисунок 2.5.1 — Компоненты API-Приложения 23 REST Контроллеры представляют собой пакет с классами, содержащими публичные API методы, диаграмма классов которого представлена на рисунке 2.5.2. Рисунок 2.5.2 — Диаграмма классов REST контроллеров Исходя из нефункциональных требований для реализации REST интерфейса был использован фреймворк для веб приложений Play. Play Framework — это каркас разработки с открытым кодом, написанный на Scala и Java, использует паттерн проектирования Model-View-Controller (MVC), подходит для создания как классических веб-сайтов, так и для создания REST API. Рассмотрим составляющие фреймворка, которые использовались в этой работе. 24 Маршрутизация. Play Framework поддерживает обработку входящих HTTP запросов и вызов методов контроллеров, в зависимости от пути в запросе. Соответствия между путём и методом контроллера описываются в специальном формате, пример приведен ниже. PUT /admin/users/:id controllers.admin.UserController.update(id: Long) Это означает, что PUT запрос по пути /admin/users/:id должен вызывать метод update у класса UserController лежащего в пакете controllers.admin Сериализация и десериализация JSON. Каркас имеет встроенный функционал для автоматической конвертации объектов в JSON и конвертации JSON в объекты. Это необходимо потому что, в REST API для передачи состояния объектов используется именно формат JSON. Внедрение зависимостей. Поддерживается внедрение зависимостей по стандарту JSR 330 . В качестве реализации используется библиотека Google 5 Guice. Продолжим разбор функционала, рассмотрим реализацию аутентификации и авторизации пользователей. Необходимым требованием к приложению является поддержка входа через учетную запись Google. Так как ввод логина и пароля напрямую на странице сервиса не соответствует принятым нормам безопасности, было решено использовать протокол OAuth2 для авторизации получения доступа к основной информации о Google аккаунте пользователя. Далее, с помощью вызова в Google API получается уникальный идентификатор пользователя, который и используется для аутентификации. После успешной аутентификации API Приложение генерирует токен, который надо передавать с каждым последующим запросом. Для генерации токена был выбран стандарт JWT. Json Web Token — это открытый спецификаци для аутентификации в веб приложениях. Токены создаются сервером, подписываются секретным ключом и передаются клиенту, который в 5 JSR-330 — спецификация внедрения зависимостей для языка Java 25 дальнейшем использует данный токен для подтверждения своей личности. Система включает в токен идентификатор пользователя Google, время жизни токена и подпись, сгенерированную с использованием секретного ключа. Это позволяет отказаться от хранилища сессий и от состояния, что, в дальнейшем, облегчит горизонтальное масштабирование путем увеличения количества запущенных приложений. Рисунок 2.5.3 — Аутентификация пользователя 26 Авторизация в приложении реализована по ролям. Существует две роли — администратор и пользователь. Для авторизации, аутентификации, генерации и проверки токена была использована библиотека Silhouette. Следующий компонент подсистемы — это манипулирование данными в БД. Для этого был использован паттерн Data Access Object. На каждую сущность было создано по классу, ответственному за чтение, создание, обновление и удаления этой сущности в БД. Рисунок 2.5.4 — Классы в пакете model.dao Написание SQL запросов напрямую в коде не является подходящем решением, так как такие запросы не проверяются компилятором и могут стать причиной ошибки. Поэтому для написания запросов был использован Slick. Slick — библиотека для работы с БД для языка Scala, позволяющая писать запросы так, будто работа ведется с Scala коллекциями, расположенными в 27 памяти. Помимо этого Slick поддерживает отображение результата запроса в объекты. Для использования Slick необходимо: 1. Указать параметры подключения в файле конфигурации 2. Объявить схему. Выглядит это следующим образом: Здесь объявляется класс Competence и отображение CompetenceTable , в котором содержится информация о названии таблицы и именах и типах столбцов. 3. Написать запрос. Запросы на вставку пишутся так: На получение данных так: Подобные запросы автоматически преобразуются в SQL, и посылаются в СУБД с использованием JDBC 6 Перейдем к планированию и выполнению отложенных задач. В системе существуют действия, которые необходимо выполнять в фоне в определенное время, например отправка писем-уведомлений, генерация отчетов после события оценки. Непосредственно выполнением этих задач занимается отдельная подсистема, которая будет рассмотрена отдельно. Сейчас же 6 JDBC — платформенно независимый промышленный стандарт взаимодействия Java-приложений с различными СУБД 28 разберемся в деталях того, как именно планируется выполнение задач, и как они вызываются в нужно время. Для этого используется Quartz — библиотека с открытым исходным кодом, позволяющая создавать задания, которые необходимо выполнить в определенный момент времени. Перед использованием необходимо сконфигурировать хранилище для заданий, в данном случае задания будут храниться в основной БД. Все задания должны реализовывать интерфейс Job. Также в каждое задание передается контекст, содержащий в себе идентификатор события оценки и прочую необходимую информацию. Рисунок 2.5.5 — Диаграмма классов задач При срабатывании задача отправляет сообщение в очередь, которое будет прочитано Executor`ом и выполнено. 29 2.6 Исполнитель задач Для функционирования системы необходимо выполнять ряд фоновых задач, таких как подготовка события, отправка уведомлений, формирование отчета. Так как выполнение задач — это фоновый процесс, которые может занимать некоторое количество времени, было решено выделить их выполнение в отдельное приложение. Это позволяет системе быть более гибкой, устойчивой к перезапуску и отказам, позволяет, в случае надобности, выполнять горизонтальное масштабирование, путем увеличения количества запущенных исполнителей. Основные компоненты приведены ниже: Рисунок 2.6.1 — Компоненты исполнителя задач Приложение получает команды на выполнение через RabbitMQ. RabbitMQ — это программный брокер сообщений на основе стандарта AMQP . 7 Создан на основе системы Open Telecom Platform, написан на языке Erlang, в 7 AMQP — (Advanced Message Queuing Protocol) открытый протокол для передачи сообщений между компонентами системы 30 качестве системы управления базой данных для хранения сообщений использует Mnesia. Использование RabbitMQ в проекте даёт следующие преимущества: ● В случае некорректного завершения работы сервера, данные в очереди не теряются. При последующем запуске обработка продолжается с того места, где был обрыв; ● Если результат обработки не удовлетворяет, задачу можно послать в очередь повторно, например, при сбое или ошибке выполнения; ● Количество хранимых в очереди сообщений не ограничено; ● API Приложение и Исполнитель задач могут быть развернуты на различных серверах; ● В случае, если один запущенный экземпляр приложения не справляется с нагрузкой, есть возможность запустить несколько экземпляров. Задачи будут равномерно распределяться между ними. В RabbitMQ, а также обмене сообщениями в целом, используется следующая терминология: ● Producer (поставщик) — программа, отправляющая сообщения; ● Exchange (точка обмена) — получает сообщения от поставщика и отправляет эти сообщения в очередь; ● Queue (очередь) — буфер, хранящий сообщение; ● Consumer (подписчик) — программа, принимающая сообщения из очереди. Поставщик никогда не отправляет сообщения напрямую в очередь. Фактически, довольно часто поставщик не знает, дошло ли его сообщение до конкретной очереди. Вместо этого поставщик отправляет сообщение в точку обмена. Точки обмена бывают нескольких типов, в данном случае используется direct — все присылаемые сообщения перенаправляются в одну очередь, определяемую значением routing key у сообщения. Routing key есть у каждого 31 сообщения, он задается поставщиком. Итоговая схема представлена на рисунке 2.6.2 Рисунок 2.6.2 — Конфигурация обменников и очередей В случае, если Исполнитель по какой-то причине не может выполнить задачу (например, отсутствует доступ к интернету) задача возвращается обратно в очередь, и может быть прочитана снова, после перезапуска Исполнителя. Также задача автоматически возвращается в очередь если приложение завершилось во время выполнения. И чтобы мы могли быть уверены в отсутствии потерянных сообщений, RabbitMQ поддерживает подтверждение сообщений. Подтверждение (ack) отправляется подписчиком для информирования RabbitMQ о том, что полученное сообщение было обработано и RabbitMQ может его удалить. Если подписчик прекратил работу и не отправил подтверждение, RabbitMQ поймет, что сообщение не было обработано. Для обработки сообщений отсутствует тайм-аут. RabbitMQ вернет сообщение обратно в очередь только если соединение с подписчиком будет закрыто, поэтому нет никаких ограничений на время обработки сообщения. Помимо этого подписчик может вернуть сообщение в очередь, отправив в RabbitMQ отказ (reject). Далее рассмотрим основные компоненты Исполнителя. Первым делом задачи на выполнение поступают в распределитель. Задачи передаются в очередь в сериализованном виде, и распределитель должен произвести десериализацию, а затем передать ее подходящему обработчику. Для того, чтобы распределителю не нужно было знать об обработчиках и типах 32 сообщений, которые они поддерживают, был применен подход, представленный на рисунке 2.6.3. Его суть заключается в следующем: каждый обработчик должен реализовывать интерфейс JobHandler , имеющий два метода supports и handle . Метод supports должен возвращать истину в том случае, если обработчик поддерживает задачу. Метод handle непосредственно выполняет действия, необходимые для обработки сообщения конкретного типа. Рисунок 2.6.3 — Диаграмма классов исполнителей задач На рисунке 2.6.4 приведена диаграмма последовательности обработки поступающих сообщений. При создании экземпляра класса Dispatcher происходит регистрация функции обратного вызовы (callback) в RabbitConsumer , которая является входной точкой для обработки сообщений. Dispatcher содержит ссылку на коллекцию экземпляров JobHandler . При поступлении сообщения для каждого из них производится проверка, поддерживает ли обработчик сообщение. Если такой обработчик найден, то сообщение передается ему, иначе сообщение отправляется обратно в очередь. Также сообщение будет возвращено в очередь в случае возникновения исключения (exception). 33 Рисунок 2.6.4 — Последовательность обработки сообщения Перейдем к рассмотрению генерации уведомлений. Одним из требований к системе является отправка уведомлений на почту оценивающим пользователям и аудиторам. Для того, чтобы подготовленный текст можно было переиспользовать многократно, но при этом письма содержали в себе актуальную информацию, было решено применить систему шаблонов. Так как редактирование шаблонов уведомлений доступно администратору, и должно быть ему понятно, был использован шаблонизатор с минимальным количеством логики и с простейшим синтаксисом — Mustache. Шаблонизатор способен генерировать итоговый текст из шаблона, используя некий контекст. Контекст — это набор переменных, значения которых будут использованы для генерации. Шаблон представляет собой текст, содержащий в себе специальные синтаксические конструкции, позволяющие вставлять в него значения переменных, а также включать/исключать некоторые части в зависимости от 34 значения переменных. В качестве реализации использована библиотека Scalate. Пример шаблона приведен ниже. Добрый день, {{user.full_name}} Вы принимаете участие в оценке "{{event.name}}", которая продлится с {{event.start}} до {{event.end}} Вам необходимо оценить своих коллег в следующих проектах: {{#projects}} * Проект "{{name}}" {{/projects}} Для участия перейдите по ссылке: https://assessment-system-url.com/events/{{event.id}} Отправка писем реализована по протоколу SMTP, в качестве посредника выступает Amazon SES . Этот сервис необходим, так как иначе рассылка 8 большого количества писем ведет к их попаданию в спам. Следующий компонент Исполнителя задач — это Обработчик начала события оценки. Он необходим для создания всех записей, используемых для отображения вопросов пользователю и для сохранения ответов. 8 Amazon Simple Email Service — платформа отправки и получения электронной почты для использования в бизнесе и разработке ПО 35 Рисунок 2.6.5 — Сущности для хранения ответов На рисунке 2.6.5 приведена информация о моменте времени, в который создаются экземпляры классов, необходимых для оценки. В этом обработчике создаются экземпляры следующих классов: ● Вкладка; ● Ответы для пользователя; ● Ответы по форме. Для каждого проекта, в котором пользователь является оценивающим, создается вкладка. Для всех пользователей из всех отношений проекта создаются ответы для пользователя, содержащие в себе ответы по форме. На рисунке 2.6.6 приведен пример того, каким образом это может быть представлено интерфейсе. 36 Рисунок 2.6.6 — Пример интерфейса для ответа на вопросы в событии Такой подход позволяет отвязать текущие и завершенные события от изменений в оргструктуре(в группах, проектах, отношениях). То есть изменения этих сущностей не повлияет на набор пользователей и вопросы в прошедших и текущих событиях оценки. Перейдем к процессу формирования отчетов. Одной из важнейших функций системы является просмотр результатов оценки аудитором. Результат оценивания должен содержать всю информацию об ответах, как в детальном, так и агрегированном виде. Это необходимо для того, чтобы аудитор мог надлежащим образом проанализировать текущую обстановку в организации, особенности взаимоотношений между сотрудниками, сильные и слабые места каждого из них. Согласно требованиям, отчет должен формироваться в виде электронной таблицы и загружаться на сервис Google Drive. Доступ к файлам для аудиторов должен предоставляться автоматически. Таким образом, задача состоит в том, чтобы каким-то образом формировать файл электронной таблицы с результатами и загружать его на Drive. Для каждого события оценки система должна создавать отдельную директорию, содержащую по одному файлу с результатом на каждого оцениваемого, каждый файл должен содержать следующие элементы: 37 ● отчет по компетенциям — усредненное значение каждой компетенции, используемой в вопросах формы, отдельно необходимо считать значения самооценки, если эта опция включена, и оценок, полученных от других людей, результат представлен как в виде таблице, так и в виде круговой диаграммы; ● коэффициент согласованности ответов — величина, показывающая уровень однообразности мнений о человеке; ● история изменений компетенций, в виде таблицы и графика; ● агрегированные ответы на вопросы о человеке, сколько голосов было отдано за тот или иной варианты ответа; ● детальный ответы на вопросы, содержащие в себе информацию по каждому отвечающему. Для оценки согласованности ответов используется коэффициент конкордации Кендалла W. Значение коэффициента конкордации может находится в диапазоне от 0 до 1. Если W=0, считается, что мнения экспертов не согласованы. Если W=1, то оценки экспертов полностью согласованы. На рисунке 2.6.7 приведен вывод формулы, по которой рассчитывается это значение. Где: ● n — число компетенций; ● k — число оценивающих; ● r ij — ранг i–ой компетенции определённая j–ым оценивающим; ● d i — сумма рангов i–ой компетенции по всем оценивающим; ● W — коэффициент конкордации Кендалла. 38 Рисунок 2.6.7 — Вывод формулы для расчета коэффициента конкордации Кендалла Расчет коэффициента производился с помощью библиотеки Apache Commons Math. Для большей наглядности, перед отображением значение коэффициента умножается на 10. Рассмотрим возможные способы работы с электронными таблицами из языка программирования Scala. Существует много библиотек для выполнения этой задачи, обратим подробное внимание на две из них, кардинально отличающиеся подходом. Первая из них — это Apache POI. Apache POI является самой популярной библиотекой для работы с файлами MS Office в экосистеме Java. Она предоставляет интерфейс для чтения и записи офисных файлов. Рассмотрим подробнее формирование файлов электронных таблиц с помощью этой библиотеки. Подход является полностью императивным, программисту доступны самые низкоуровневые API для манипулирования элементами в документе. Например, для того, чтобы записать значение в первую ячейку, программист должен сделать следующее Workbook book = new HSSFWorkbook(); Sheet sheet = book.createSheet("Название листа"); Row row = sheet.createRow(0); Cell name = row.createCell(0); name.setCellValue("Значение ячейки А1"); 39 Можно видеть, что в распоряжении разработчика есть самые базовые методы. Реализация формирования сложных документов с таким подходом неудобна, а абсолютные индексы могут стать причиной ошибки при рефакторинге. Помимо этого, смотря на код, крайне сложно понять, каким образом будет выглядеть итоговый документ. По этим причинам библиотека не подходит, необходим инструмент с более высоким уровнем абстракций для формирования необходимых структур в документе. Следующий инструмент — это Jxls. Jxls внутри использует Apache POI для формирования отчетов, но разработчик освобожден от низкоуровневой работы с документом. Для создания электронной таблицы с данными необходимы две вещи — шаблон, сам являющийся электронной таблицей и контекст с данными. По своей идее данная библиотека похожа на шаблонизаторы для языка HTML, но работает с электронными таблицами. Шаблон содержит в себе управляющие теги в ячейках, и при формировании отчета библиотека подставляет вместо тегов соответствующие значения из контекста. Пример шаблона представлен на рисунке 2.6.8. Рисунок 2.6.8 — Создание шаблона для jxls 40 Шаблоны можно редактировать в специализированных офисных программах, таких как Microsoft Excel или Libre Office Calc. Для того, чтобы сгенерировать результат, библиотеке нужно передать сам шаблон и контекст. Контекст — это объект, содержащий в себе поля, которые будут использоваться для отображения. Поддерживается неограниченная вложенность объектов, списки, пары ключ-значение (Map). List InputStream template = getTemplate(); OutputStream result = new FileOutputStream("result.xls"); Context context = new Context(); context.putVar("employees", employees); JxlsHelper.getInstance().processTemplate(is, os, context); Такой подход работы с таблицами крайне удобен, но имеет один существенный недостаток. Шаблоны хранятся в системе контроля версий в виде двоичных файлов, из чего вытекает невозможность генерации списка изменений при коммите и невозможность разрешения конфликтов в случае их возникновения. Кроме того, отсутствует возможность проверять теги внутри шаблона на корректность. В случае рефакторинга, изменения названия переменных, проект будет собран успешно, но в процессе работы документы будут создаваться некорректно. Тесты также не могут помочь, так как в библиотеке отсутствует функционал валидации тегов, в случае неправильного названия или опечатки соответствующий блок просто не будет выведен в результирующий документ. Помимо этого в процессе работы были встречены различные проблемы с отображением полученных документов в программах, отличных от Microsoft Excel. Так как было необходимо отображать документы онлайн с помощью Google Sheets , это стало критическим недостатком. 9 Так как существующие библиотеки по тем или иным причинам не подходили, было решено реализовывать собственную. Основные функциональные возможности, которые хотелось бы поддерживать, это: 9 Google Sheets — бесплатный онлайн сервис для просмотра и редактирования электронных таблиц 41 ● декларативный подход, позволяющий программисту меньше уделять внимания низкоуровневым деталям и уменьшающий вероятность ошибок; ● набор высокоуровневых абстракций, таких как таблица, список; ● поддержка стилей, таких как цвет и толщина границ, стиль шрифта, фоновый цвет ячейки; ● независимость от реализации электронных таблиц. Библиотека должна поддерживать различные модули, занимающиеся непосредственно генерацией таблиц. То есть используя один и тот же код структуры документа должна быть возможность формировать как Excel файлы, так и вызовы в Google Sheets API. Принимая во внимания необходимые функциональные возможности, была представлена разработана концепция, удовлетворяющая требованиям. На рисунке 2.6.9 представлено, из каких частей состоит состоит библиотека и каким образом эти части взаимодействуют. Прямоугольник со скругленными углами — это данные, передаваемые в некоем формате. Простые прямоугольники — обработчики. Стрелки — потоки данных между обработчиками. Рисунок 2.6.9 — Потоки данных библиотеки Такой подход позволяет разделить шаблон, данные и конкретный генератор, используемый для формирования. По своей идее это напоминает шаблон проектирования двухэтапное представление (Two Step View). 42 Рассмотрим способ формирования шаблона. Для этого был разработан DSL (или предметно-ориентированный язык), с поддержкой всех базовых конструкций. Document[ExportDTO] result = Document.create(doc => { doc.page("Пользователи", page => { page.label(l => { l.text("Список пользователей") l.style(s => s.align(Alignment.CENTER)) }) page.table(_.getUsers, usersTable => { usersTable.column(_.getFirstName) usersTable.column(_.getLastName) }) }) }) DSL основан на использовании лямбда-выражений. Лямбда выражение — это специальный синтаксис для определения функциональных объектов. В языке Scala запись лямбда выражений выглядит так: (аргументы)=>выражение. Существует особый синтаксис для лямбда-выражений, содержащих в себе вызов метода у объекта. Такую запись: user=>user.getFirstName можно кратко записать как _.getFirstName. Диаграмма классов интерфейсов, доступных для пользователя этой библиотеки представлена на рисунке 2.6.10. 43 Рисунок 2.6.10 — Диаграмма классов для создания DSL Каждый из этих интерфейсов имеет свою реализацию, и каждая эта реализация унаследована от базового интерфейса VisitableDslElement, имеющего один метод — visit(DslElementVisitor visitor). Такой подход позволяет легко обрабатывать созданное дерево объектов, создавать новые классы таких объектов и различные обработчики. Перейдем к преобразованию шаблона в промежуточную структуру. Для этого было введено две абстракции — контейнер и ячейка. Контейнер может содержать ячейки и другие контейнеры, а также имеет направление, в котором добавляются новые элементы. Фактически это является реализацией паттерна проектирования «Компоновщик». 44 Рисунок 2.6.10 — Диаграмма классов для промежуточной структуры После этого структуру необходимо преобразовать в список действий, для этого необходимо вызвать метод getActions у корневого элемента, передав начальную позицию отрисовки в качестве параметра. Метод будет последовательно вызван у всех элементов контейнера и вернет полный список действий, необходимых для отрисовки таблицы. Диаграммы последовательности для классов Cell и StackPanel приведены на рисунках 2.6.11, 2.6.12. 45 Рисунок 2.6.11 — Диаграмма последовательности класса Cell 46 Рисунок 2.6.12 — Диаграмма последовательности класса StackPanel 47 Диаграмма классов для действий приведена на рисунке 2.6.13. Рисунок 2.6.13 — Диаграмма классов действий После того, как список действий получен, можно преобразовывать каждое из них в низкоуровневые вызовы в сторонние библиотеки, например, с помощью Apache POI. В данном случае преобразование производится напрямую в вызовы Google API для модификации данных в таблице. Помимо текста и таблиц было необходимо добавить поддержку вывода диаграмм и изображений. Диаграммы можно отрисовывать двумя путями: ● использовать инструменты встроенные в средство редактирования и просмотра электронных таблиц; ● генерировать их заранее и вставлять в виде обычного изображения. В данной работе был выбран второй подход, так как он позволяет сделать внешний вид диаграмм независимым от формата экспорта, дает возможность использовать типы диаграмм, неподдерживаемые офисным приложением. Этот подход имеет и минус, существенный в общем случае, но являющийся неважным в этом приложении — диаграммы не будут обновляться при 48 обновлении данных в таблице. Это не является проблемой, так как редактирование полученных отчетов не является необходимостью. Существует большое количество библиотек генерации диаграмм для языков Java и Scala. Из них была выбрана библиотека XChart, так как она имеет все необходимые функции, бесплатна, с открытыми исходными кодами и активно поддерживается разработчиками. Чтобы вывести график разработчику необходимо выполнить следующие действия: RadarChart chart = new RadarChartBuilder().width(800).height(600).title("График компетенций").build chart.setVariableLabels(Array("Инициативность", "Интерес к работе", "Исполнительность", "Командная работа")) chart.addSeries("Среднее", Array(0.8, 0.7, 0.4, 0.5)) chart.addSeries("Самооценка", Array(0.6, 0.9, 0.6, 0.7)) val byteArrayOutputStream = new ByteArrayOutputStream() BitmapEncoder.saveBitmap(chart, byteArrayOutputStream, BitmapEncoder.BitmapFormat.PNG) val bytes = byteArrayOutputStream.toByteArray Этот код создает новую круговую диаграмму в формате PNG, результат сохраняется в массив байт для дальнейшего использования. Рисунок 2.6.14 — Пример круговой диаграммы 49 |