Первый пример, который я сделал, чтобы начать распаковывать, как я буду управлять этим в масштабе, был базовым вызовом API Duck Duck Go. При разработке микро-фронтенда важно учитывать следующее:
- Можно ли его использовать повторно в других проектах или он будет одноразовым? Повторное использование — это здорово, но это увеличит сложность API, поскольку вы представляете себе другие случаи использования, а не только то, что является внутренним для данного сайта.
- Это визуальная или не визуальная реализация? Визуальная реализация должна состоять из нескольких невизуальных реализаций, чтобы избежать слишком тесной связи.
- Принимает ли это пользовательский ввод или отвечает на вопрос машины? Должны ли мы удовлетворить запрос пользователя или это скорее кнопка, которую он нажимает, а текущая страница выполняет перебор данных для формирования ответа.
- Является ли это критически важной услугой или просто приятной мелочью? Критические сервисы (кнопка входа) или приятные (PDF на этой странице), в любом случае должен быть запасной план на случай, если микросервис не ответит.
Duck Duck Go
- живой пример в сборнике рассказов
- фронтенд, то есть чисто пример
- код бэкенда для vercel
Давайте начнем с реализации фронтенда и вернемся к бэкенду. Это очень наглядный пример, так что это просто базовый ввод формы
<div>
<label>Duck duck go</label>
<input type="text" id="search" />
<button id="searchbtn">Search</button>
<div id="ddgresult"></div>
</div>
После того как пользователь вводит поисковый запрос, мы фиксируем событие нажатия на кнопку поиска
this.shadowRoot
.querySelector("#searchbtn")
.addEventListener("click", () => {
const params = {
q: this.shadowRoot.querySelector("#search").value,
};
MicroFrontendRegistry.call("@core/duckDuckGo", params, this.ddgCallback.bind(this));
});
и затем вызываем наш класс абстракции / frontend middleware под названием MicroFrontendRegistry
. Я напишу другую статью о том, как я создавал API для этого, но идея в том, что вместо вызова конечной точки, мы хотим вызвать имя разработчика для функции и позволить среде выяснить фактический полный адрес.
После того, как параметр q
передан микросервису, выполняется ddgCallback
с данными ответа от конечной точки.
Микросервис
Vercel обрабатывает всю магию развертывания, а также принимает на себя определенные настройки конфигурации, связанные с express. В сочетании с некоторыми извлеченными уроками (описанными в структуре API бэкенда) мы получаем очень чистую конечную точку для обработки нашего DDG-запроса.
// duckduckgo.js
// this is an example to fork from that uses common, simple conventions
// for getting data, validating data, and responding in a consistent way.
import { stdPostBody, stdResponse, invalidRequest } from "../../../utilities/requestHelpers.js";
import fetch from "node-fetch";
export default async function handler(req, res) {
// destructing GET params after ? available in this object
var { q } = req.query;
// use this if POST data is what's being sent
const body = stdPostBody(req);
// fallback support for post
if (!q && body && body.q) {
q = body.q;
}
// need to know what we're searching for otherwise bail
if (q) {
// we import fetch just to simplify endpoint creation but its just node-fetch
const searchResults = await fetch(`http://api.duckduckgo.com/?q=${q}&format=json`).then((d) => d.ok ? d.json(): {});
// standard response is how all transactions end
// this will assume 200 response code unless defined otherwise
// response data is passed in as searchResults here
// if type is not set in the options, then it is assumed JSON response
// and is added to a data param
res = stdResponse(res, searchResults, {cache: 86400, methods: "OPTIONS, POST" });
}
else {
// invalidate the response and provide a reason
// this optionally takes in a status code otherwise default is 400
// vercel will through a 500 if there was any bricking issue so we don't
// need to throw that most likely
res = invalidRequest(res, 'missing `q` param');
}
}
Здесь мы можем видеть, что перед отправкой в API duck duck go мы проводим простую валидацию вводимых данных. stdResponse
затем принимает данные JSON и просто устанавливает соответствующий ответ перед отправкой обратно на фронт-энд. Вышеизложенное хорошо документировано, так что прочитайте, что там происходит, но это довольно просто с небольшими абстракциями для работы с res
и req
.
Время показа результатов
Итак, как только бэкенд отправит этот JSON-блок обратно, Promise
фронтенда будет разрешен в ddgCallback
и запущен с data
, который был возвращен.
ddgCallback(data) {
console.log(data.data.RelatedTopics);
this.shadowRoot.querySelector("#ddgresult").innerHTML =
`<code><pre>
${JSON.stringify(data.data.RelatedTopics, null, 4)}
</pre></code>`;
}
Здесь мы видим, что это простая распечатка строгированной версии на страницу.
Это довольно простой паттерн, который возникает с микрофронтендами из моих построений нескольких.
- Имеем ввод
- сделать вызов стандартным способом (
Promise
, поэтомуasync
/await
по желанию) - получить вызов стандартным способом, вернуть данные стандартным способом (
json
с параметромdata
) - отображение / реакция на ввод
Теперь, когда у нас есть простой пример, давайте разберемся в абстракции front-end, которую я написал, чтобы свести вызов веб-сервисов к однострочным вызовам.