Создание микросервисов с Node.js
Когда ваше JavaScript-приложение увеличивается в размерах, вы начинаете сталкиваться с проблемами, связанными с поддержкой кода, исправлением ошибок и внедрением новых функций. Кроме того, добавление новых разработчиков в проект становится сложным.
Приложения создаются из кусочков, таких как пакеты и модули, но в какой-то момент этих структур недостаточно для уменьшения размера и сложности приложения. Идея, лежащая в основе распределенных систем, состоит в том, чтобы разбить большие монолитные конструкции на маленькие независимые программы, которые обмениваются данными друг с другом для обмена данными и выполнения операций.
Одним из многих вариантов распределенных систем является архитектура микросервисов, которая структурирует приложение как набор слабо связанных сервисов. Службы детализированы, а протоколы связи легковесны (например, протокол HTTP).
Есть несколько вещей, которые стоит подчеркнуть о превосходстве микросервисов и распределенных систем в целом над монолитной архитектурой:
- Модульность - ответственность за конкретные операции возлагается на отдельные части приложения
- Однородность - интерфейсы микросервисов (API endpoints) состоят из базового URI, идентифицирующего объект данных, и стандартных методов HTTP (GET, POST, PUT, PATCH и DELETE), используемых для управления объектом
- Надежность - отказы компонентов приводят только к отсутствию или сокращению определенной функциональной единицы
- Ремонтопригодность - компоненты системы могут быть изменены и развернуты независимо
- Масштабируемость - экземпляры службы могут быть добавлены или удалены для реагирования на изменения спроса.
- Доступность - новые функции могут быть добавлены в систему при сохранении 100% доступности.
- Тестируемость - новые решения можно тестировать непосредственно на «поле битвы в продакшене», внедряя их для ограниченных сегментов пользователей, чтобы увидеть, как они ведут себя в реальной жизни.
Кроме того, каждый микросервис может быть написан с использованием языка, техники или структуры, которые наиболее соответствуют задачам, которые он выполняет. Единственная необходимая функция - это возможность публиковать RESTful API для связи с другими сервисами.
В этой статье вы узнаете, как создавать микросервисы, создавая базовую систему, состоящую из двух JavaScript-сервисов, работающих на Node.js.
Подготовка для построения архитектуры микросервисов с Node.js
Для выполнения задач в этом посте вам понадобится следующее:
- Node.js и npm (установка Node.js также установит npm.)
Чтобы наиболее эффективно учиться на этом посте, вы должны иметь следующее:
- Знание JavaScript и Node.js
- Некоторое знакомство с протоколом HTTP
Создаем сервис героев
Перейдите в каталог, в котором вы хотите создать проект, и создайте следующую структуру каталогов и файлов:
./heroes/heroes.js
./heroes/img/
Если вы хотите использовать систему контроля версий, самое время инициализировать репозиторий. Не забудьте добавить файл .gitignore, как этот, если вы используете Git.
Инициализируйте проект npm внутри каталога /heroes и установите необходимые зависимости, выполнив следующие команды:
npm init -y
npm install express body-parser
Пришло время реализовать сервис. Скопируйте этот код JavaScript в файл /heroes/heroes.js:
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const port = process.argv.slice(2)[0];
const app = express();
app.use(bodyParser.json());
const powers = [
{ id: 1, name: 'flying' },
{ id: 2, name: 'teleporting' },
{ id: 3, name: 'super strength' },
{ id: 4, name: 'clairvoyance'},
{ id: 5, name: 'mind reading' }
];
const heroes = [
{
id: 1,
type: 'spider-dog',
displayName: 'Cooper',
powers: [1, 4],
img: 'cooper.jpg',
busy: false
},
{
id: 2,
type: 'flying-dogs',
displayName: 'Jack & Buddy',
powers: [2, 5],
img: 'jack_buddy.jpg',
busy: false
},
{
id: 3,
type: 'dark-light-side',
displayName: 'Max & Charlie',
powers: [3, 2],
img: 'max_charlie.jpg',
busy: false
},
{
id: 4,
type: 'captain-dog',
displayName: 'Rocky',
powers: [1, 5],
img: 'rocky.jpg',
busy: false
}
];
app.get('/heroes', (req, res) => {
console.log('Returning heroes list');
res.send(heroes);
});
app.get('/powers', (req, res) => {
console.log('Returning powers list');
res.send(powers);
});
app.post('/hero/**', (req, res) => {
const heroId = parseInt(req.params[0]);
const foundHero = heroes.find(subject => subject.id === heroId);
if (foundHero) {
for (let attribute in foundHero) {
if (req.body[attribute]) {
foundHero[attribute] = req.body[attribute];
console.log(`Set ${attribute} to ${req.body[attribute]} in hero: ${heroId}`);
}
}
res.status(202).header({Location: `http://localhost:${port}/hero/${foundHero.id}`}).send(foundHero);
} else {
console.log(`Hero not found.`);
res.status(404).send();
}
});
app.use('/img', express.static(path.join(__dirname,'img')));
console.log(`Heroes service listening on port ${port}`);
app.listen(port);
Загрузите изображения и команды супергероев по следующим ссылкам и поместите их в каталог /heroes/img :
Код для сервиса героев обеспечивает:
- список суперспособностей героев
- список героев
- Конечные точки API для получения списка героев, обновления профиля героя и получения изображения героя
Протестируйте сервис heroes.js
Код службы героев, показанный ниже, позволяет запускать службу на любом порту HTTP, который вы хотите.
const port = process.argv.slice(2)[0];
...
app.listen(port);
Запустите службу, выполнив следующую инструкцию командной строки:
node ./heroes/heroes.js 8081
Вы можете проверить, работает ли служба должным образом, используя Postman, curl, PowerShell Invoke-WebRequest или ваш браузер. Например выполните команду curl указанную ниже:
curl -i --request GET localhost:8081/heroes
Если служба работает правильно, вы должны увидеть результаты, похожие на следующий вывод консоли из curl:
HTTP/1.1 200 OK
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 424
ETag: W/"1a8-BIZzoIRo/ZugcWv+LFVGSU1qIZU"
Date: Thu, 04 Apr 2019 12:07:07 GMT
Connection: keep-alive
[{"id":1,"type":"spider-dog","displayName":"Cooper","powers":[1,4],"img":"cooper.jpg","busy":false},{"id":2,"type":"flying-dogs","displayName":"Jack & Buddy","powers":[2,5],"img":"jack_buddy.jpg","busy":false},{"id":3,"type":"dark-light-side","displayName":"Max & Charlie","powers":[3,2],"img":"max_charlie.jpg","busy":false},{"id":4,"type":"captain-dog","displayName":"Rocky","powers":[1,5],"img":"rocky.jpg","busy":false}]
В Postman тело ответа должно выглядеть так:
Создаем сервис "угроз"
Какова цель супергероя, если нет опасности? Архитектура микросервисов нашего приложения использует отдельный сервис для представления задач, которые может преодолеть только супергерой. Он также предоставляет конечную точку API для сопоставления супергероев с угрозами.
Процедура создания сервиса угроз аналогична сервису героев.
В главном каталоге вашего проекта создайте следующую структуру каталогов и файлов:
./threats/threats.js
./threats/img/
В каталоге ./threats инициализируйте проект и установите его зависимости со следующими инструкциями командной строки npm:
npm init -y
npm install express body-parser request
Поместите этот код JavaScript в файл /threats/threats.js:
const express = require('express');
const path = require('path');
const bodyParser = require('body-parser');
const request = require('request');
const port = process.argv.slice(2)[0];
const app = express();
app.use(bodyParser.json());
const heroesService = 'http://localhost:8081';
const threats = [
{
id: 1,
displayName: 'Pisa tower is about to collapse.',
necessaryPowers: ['flying'],
img: 'tower.jpg',
assignedHero: 0
},
{
id: 2,
displayName: 'Engineer is going to clean up server-room.',
necessaryPowers: ['teleporting'],
img: 'mess.jpg',
assignedHero: 0
},
{
id: 3,
displayName: 'John will not understand the joke',
necessaryPowers: ['clairvoyance'],
img: 'joke.jpg',
assignedHero: 0
}
];
app.get('/threats', (req, res) => {
console.log('Returning threats list');
res.send(threats);
});
app.post('/assignment', (req, res) => {
request.post({
headers: {'content-type': 'application/json'},
url: `${heroesService}/hero/${req.body.heroId}`,
body: `{
"busy": true
}`
}, (err, heroResponse, body) => {
if (!err) {
const threatId = parseInt(req.body.threatId);
const threat = threats.find(subject => subject.id === threatId);
threat.assignedHero = req.body.heroId;
res.status(202).send(threat);
} else {
res.status(400).send({problem: `Hero Service responded with issue ${err}`});
}
});
});
app.use('/img', express.static(path.join(__dirname,'img')));
console.log(`Threats service listening on port ${port}`);
app.listen(port);
Вы можете скачать фотографии по следующим ссылкам и поместить в каталог /threat/img:
Помимо списка угроз и основных методов, таких как их перечисление, этот сервис также имеет метод POST /assignment , который прикрепляет героя к данной угрозе:
app.post('/assignment', (req, res) => {
request.post({
headers: {'content-type': 'application/json'},
url: `${heroesService}/hero/${req.body.heroId}`,
body: `{
"busy": true
}`
}, (err, heroResponse, body) => {
if (!err) {
const threat = threats.find(subject => subject.id === req.body.threatId);
threat.assignedHero = req.body.heroId;
res.status(202).send(threat);
} else {
res.status(400).send({problem: `Hero Service responded with issue ${err}`});
}
});
});
Поскольку код реализует взаимодействие между службами, ему необходимо знать адрес службы героев, как показано ниже. Если вы изменили порт, на котором работает служба героев, вам нужно отредактировать эту строку:
const heroesService = 'http://localhost:8081';
Тестируем службу "угроз"
Если вы остановили службу героев или закрыли окно терминала, перезапустите ее.
Откройте другое окно терминала и запустите службу угроз, выполнив следующую инструкцию командной строки:
node threats/threats.js 8082
Так же, как вы тестировали сервис героев, протестируйте сервис угроз, выполнив веб-запрос с помощью Postman, curl, PowerShell Invoke-WebRequest или вашего браузера. Обратите внимание, что на этот раз это запрос POST.
Инструкция командной строки curl:
curl -i --request POST --header "Content-Type: application/json" --data '{"heroId": 1, "threatId": 1}' localhost:8082/assignment
Если служба работает правильно, вы должны увидеть результаты, похожие на следующий вывод консоли из curl:
HTTP/1.1 202 Accepted
X-Powered-By: Express
Content-Type: application/json; charset=utf-8
Content-Length: 121
ETag: W/"79-ER1WRPW1305+Eomgfjq/A/Cgkp8"
Date: Thu, 04 Apr 2019 19:32:56 GMT
Connection: keep-alive
{"id":1,"displayName":"Pisa tower is about to collapse.","necessaryPowers":["flying"],"img":"tower.jpg","assignedHero":1}
В Postman тело ответа должно выглядеть так:
В окне терминала, где работает служба героев, вы должны увидеть:
Heroes service listening on port 8081
Set busy to true in hero: 1
Вы только что отправили Купера (localhost:8081/img/cooper.jpg) с заданием...
... полететь в Пизу и спасти исторический памятник! (localhost:8082/img/tower.jpg):
Вы можете использовать изображения героев и угроз для самостоятельного изучения функциональности сервисов. Эти объекты также будут частью проекта в следующей публикации, которая использует этот проект в качестве отправной точки.
Микросервисы JavaScript с Node.js
В этом посте вы узнали, как создать базовую архитектуру распределенной системы с Node.js. Вы видели, как делегировать ответственность за различные задачи отдельным приложениям и взаимодействовать между службами. Оба приложения взаимодействуют друг с другом через открытые REST API. Каждый из них манипулирует только данными, за которые несет ответственность, и может поддерживаться, расширяться и развертываться без привлечения другой службы.