Архитектура
В современной программной архитектуре становится все более распространенным разбивать приложения на более мелкие компоненты. Эти компоненты должны взаимодействовать друг с другом (например, через RESTful API), обеспечивая беспрепятственный обмен данными.
Преимущество этого подхода заключается в разделении компонентов, что значительно облегчает масштабирование приложения. Однако одна из проблем такой архитектуры заключается в том, как эффективно уведомлять каждый компонент о соответствующих изменениях.
Для решения этой проблемы можно использовать несколько подходов, таких как использование шины сообщений или реализация двухточечных интерфейсов. Тем не менее, популярной и заслуживающей внимания функцией является использование push-уведомлений, если речь идет о событиях, связанных с конечным пользователем.
Чтобы удовлетворить эту потребность, несколько известных интернет-гигантов предоставляют услуги, специально предназначенные для push-уведомлений. Среди них я выбрал Firebase Cloud Messaging от Google из-за его удобного характера, большого количества примеров и множества вариантов использования.
В моем демонстрационном приложении у нас есть несколько клиентов, участвующих в голосовании по разным вопросам, и всем этим помогает администратор. Кроме того, у нас есть клиент, отвечающий за представление текущего состояния процесса голосования. Поддержка этих взаимодействий — это универсальный бэкенд, который предлагает RESTful API и позволяет эффективно манипулировать данными.
Стоит отметить, что клиент голосования, интерфейс администратора и клиент, отображающий результаты голосования, не зависят от платформы, то есть их можно развернуть на разных платформах.
Однако ради простоты и легкости реализации я решил использовать веб-приложения для каждого из этих компонентов (на основе Laravel и Livewire).
Поскольку мне по своей природе интересно понять внутреннюю работу систем, я возьму на себя задачу продемонстрировать, как такая архитектура может быть реализована, вместо того, чтобы полагаться на уже существующие пакеты Laravel.
При рассмотрении концепции push-уведомлений важно создать механизм, который позволит клиентам подписаться на эту функцию. Этого можно достичь с помощью службы подписки, предлагаемой серверной частью, а также необходимых средств для отправки уведомлений через выбранного поставщика услуг. В конечном итоге весь процесс должен завершиться предоставлением клиентам возможности эффективно получать и обрабатывать эти уведомления.
Подписка на push-уведомления
Процесс подписки включает в себя использование конечной точки API, куда клиент отправляет токен, который впоследствии сохраняется для дальнейшего использования.
Чтобы инициировать подписку на уведомления, клиенты могут использовать следующую конечную точку API:
// Tokens for push notifications $router->post('/subscribe', ['uses' => 'TokenController@storeToken']);
С нашей стороны, мы используем кэширование Laravel для хранения токенов, сгенерированных клиентами. Если клиент представляет новый токен, мы можем обновить его в кеше. С другой стороны, если клиент использует тот же токен, мы отвечаем соответствующим образом.
public function storeToken(Request $request) { $key = $request->ip; // key the tokens by the requestor IP address if (!Cache::has($key)) { Cache::put($key, $request->token, Carbon::now()->addMinutes(self::CACHE_EXPIRATION_TIME)); return response()->json(['status' => 'success', 'message' => 'Token registered successfully'], 201); } else { if ($request->token !== Cache::get($key)) { Cache::forget($key); Cache::put($key, $request->token, Carbon::now()->addMinutes(self::CACHE_EXPIRATION_TIME)); return response()->json(['status' => 'success', 'message' => 'Token refreshed successfully'], 200); } } return response()->json(['status' => 'success', 'message' => 'Token is already registered'], 200); }
Отправка уведомлений
Уведомления инициируются действиями пользователя, такими как получение нового голоса за вопрос. В примере приложения, когда такое событие происходит, наша цель — уведомить подписанных клиентов об изменении голосования, чтобы они могли надлежащим образом обработать эту информацию.
try { $this->initWithPushNotification( $question->question_text . ' / '. $new_vote->vote_text, 'Votes increased to ' . $new_vote->number_of_votes, "http://localhost:8200/questions/$question_id/votes" )->sendPushNotification(); } catch (\Exception $e) { return response()->json($e->getMessage(), 500); }
Чтобы поддерживать чистый и организованный контроллер маршрута, я объединил все действия в отдельный трейт.
Мы инициализируем наш «пушер» нужной информацией и оперативно рассылаем уведомление.
Для эффективной обработки я реализовал простой метод на основе HTTP-фасада вместе с обработчиками очередей. Такой подход позволяет нам эффективно управлять нагрузкой даже при большом количестве подписанных клиентов.
Обработчики очередей также предоставляют хороший способ обработки невыполненных заданий и управления журналом событий.
Отображение результатов
FCM предлагает обширную библиотеку JavaScript, облегчающую обработку входящих сообщений.
Документация для этой библиотеки очень обширна и сопровождается многочисленными примерами.
Поэтому я не буду здесь углубляться в специфику клиентской реализации. Тем не менее, я хотел бы предоставить краткую демонстрацию того, как обрабатывать входящие сообщения:
messaging.onMessage((payload) => { console.log('Message received. ', payload); Livewire.emit('refresh-chart'); });
Я использую Livewire для отображения красивого графика результатов голосования.
Всякий раз, когда получено новое сообщение, я запускаю событие (обновление диаграммы), которое обрабатывается на бэкэнде:
public function refreshChart() { $this->fetchData(); } public function fetchData(): void { try { $url = self::URL.'/questions/'.$this->question_id.'/votes'; $response = Http::get($url)->throwUnlessStatus(200)->json(); } catch (\Exception $e) { $this->error_message = $e->getMessage(); } $this->votes = $response; $this->vote_texts = $this->getVoteTexts($response); $this->vote_results = $this->getVoteResults($response); $this->emit('chart-refreshed'); }
Мы знаем, что было получено новое голосование, и для того, чтобы обновить наши данные, нам просто нужно сделать вызов к серверной части, используя соответствующую конечную точку API.
Как только мы получаем обновленный набор данных, мы информируем клиентскую сторону, создавая событие «обновление диаграммы».
В результате наша линейчатая диаграмма на основе chart.js автоматически обновляется, демонстрируя плавный эффект перехода.