Почему Лакония? ну, давайте почитаем немного их документации

Laconia — это микрофреймворк для создания бессерверных приложений Node.js на основе функций. Laconia защищает вас от трудностей бессерверной разработки, предоставляя согласованные архитектурные шаблоны и опыт разработчиков.

Звучит интересно, но почему бы не использовать некоторые из самых известных фреймворков nodeJS, таких как Express, Koa, Nest и т. д.?
Большинство этих фреймворков и библиотек создано для Интернета, и если вы хотите использовать их с Serverless, у вас есть чтобы установить некоторые другие библиотеки поверх них.

Самая большая разница заключается в том, что ExpressJS создает сервер, и он будет все время прослушивать какой-то порт, но то, что предлагает serverless, — это использовать функцию как услугу (FaaS), которая выполняется по запросу, так зачем использовать что-то, чего нет? построен для этого?

Давайте продолжим и создадим простое бессерверное приложение с двумя конечными точками, которые используют этот API и развернут его в AWS Lambda (На данный момент Laconia поддерживает только AWS)

Покажи мне код! 💻

Установить бессерверную версию глобально

npm install -g serverless

Создадим папку, где будем работать.

mkdir laconia-example && cd laconia-example

Создайте сервис с помощью бессерверного CLI с шаблоном aws-nodejs.

serverless create --template aws-nodejs --name laconia-example

Начать проект npm

npm init -y

Laconia доступна в виде нескольких пакетов на NPM, установите @laconia/core и получите остальные в зависимости от ваших потребностей.

npm install --save @laconia/core
  • core: фреймворк внедрения микрозависимостей
  • адаптер: преобразует события AWS во входные данные вашего приложения.
  • adapter-api: преобразует события прокси-сервера шлюза API во входные данные вашего приложения.
  • event: анализирует и реагирует на входящие события
  • invoker: вызывает лямбда-выражения как обычные функции.
  • config: Экстернализует секрет приложения и конфигурацию.
  • пакет: считывает большое количество записей без ограничения по времени.
  • middleware-lambda-warmer: интегрирует Lambda с lambda-warmer.
  • middleware-serverless-plugin-warmup: короткое замыкание Lambda запускается при вызове serverless-plugin-warmup

Сделаем рефрактор, создадим папку src и внутри добавим три папки application, domain,иинфраструктура (DDD)

На уровне приложения он будет отвечать за определение и оркестровку лямбда-выражений, передавая им необходимые зависимости.
Уровень domain будет отвечать за наличие только лямбда-выражений. обработчики и бизнес-логика.
Уровень инфраструктуры — это то, что предоставляет реализации, поддерживающие ранее определенные уровни, внешние зависимости, сервисы.

├── .gitignore
├── package.json
├── package-lock.json
├── serverless.yml
└── src
    ├── application
    │   └── app.js
    ├── domain
    │   └── getPosts.js
    └── infrastructure
        ├── dependencies
        │   └── postsService.js
        └── httpServices
            ├── postService
            │   ├── keys.js
            │   └── Post.js
            └── request.js

Одним из преимуществ Laconia является зависимость инъекция 🌈

Laconia явно разделяет ответственность за создание объектов и выполнение функции Lambda.

Ваш app.js будет отвечать за то, чтобы обернуть вашу лямбду необходимыми зависимостями с помощью метода .register(), который получает объект.

const laconia = require('@laconia/core');
// Lambda
const getPosts = require('../domain/getPosts');
// Dependencie
const postsService = require('../infrastructure/dependencies/postsService');
module.exports.getPosts = laconia(getPosts).register(postsService)

Создайте postsService.js

const Post = require('../httpServices/postService/Post');
const postInstance = () => {
  const postsService = new Post();
  return { postsService }
};
module.exports = postInstance;

иPost.js

const api = require('./keys')
const request = require('../request')
class Post {
  constructor() {
    this.posts = 'posts',
    this.request = request
  }
  getPosts() {
    const url = `${api.baseURL}/${this.posts}`;
    return this.request(url);
  }
}
module.exports = Post;

Наконец, request.js и key.js, я использую node-fetch, но вы можете использовать все, что захотите. а файл key предназначен только для сохранения базы URL.

запрос.js

const fetch = require('node-fetch');
const request = (url) => {
  return fetch(url)
    .then(response => response.json())
    .then(data => data)
    .catch(err => err);
}
module.exports = request

Также установите зависимость node-fetch

npm install --save node-fetch

key.js

const baseURL = 'https://jsonplaceholder.typicode.com';
module.exports = { baseURL }

Ваш getPost.js будет отвечать за бизнес-логику, получать зависимости/службы, которые он использует в качестве параметров.

Прежде всего, давайте прочитаем очень важный момент о Laconia.

Поскольку Laconia меняет подпись вашего обработчика с event, context, callback на input, LaconiaContext, вы можете задаться вопросом, как можно получить исходный контекст или событие AWS. Эти переменные доступны для доступа через LaconiaContext с ключом context и event. Laconia рекомендует не использовать context или event в ядре вашего приложения, поскольку это зависит от AWS, а вместо этого использовать их в своем адаптере.

Лямбда получает зависимости через LaconiaContext.

getPosts.js

const handler = async (event, { postsService }) => {
  
  const data = await postsService.getPosts();
  
  return {
    statusCode: 200,
    body: JSON.stringify(
      {
        response: data,
      }
    ),
  };
};
module.exports = handler;

И это оно! теперь давайте попробуем это локально 🚀

Отредактируйте файл serverless.yml, удалите все комментарии и добавьте триггер события HTTP.

service: laconia-example
provider:  
  name: aws  
  runtime: nodejs12.x
functions:
  getPosts:
    handler: src/application/app.getPosts
    events:
      - http:
          path: /posts
          method: get

Теперь давайте установим библиотеку для работы офлайн.

npm install --save aws-sdk
npm install --save-dev serverless-offline

Добавьте плагин в файл serverless.yml.

service: laconia-example
plugins:
  - serverless-offline
...

добавьте скрипт для запуска проекта

{
  "name": "laconia-example",
  "version": "1.0.0",
  "description": "",
  "scripts": {
    "dev": "serverless offline"
  },
  ...
}

Проверьте это, вы увидите что-то вроде этого.

npm run dev

И вуаля 🙌

Мы проделали большую работу, продолжим пробовать еще одно преимущество Laconia.

npm install --save @laconia/event

Разбирает и реагирует на входящие события. Помогает вам легко создать свой собственный адаптер.

Вы можете использовать его со шлюзом S3, SNS, SQS, Kinesis и API.
Мы будем использовать шлюз API для получения запроса и ответа.

Создайте apiGateway.js в src/infrastructure/dependencies.

const api = require('@laconia/event').apigateway;
const apiGateway = async () => {
  return { api };
};
module.exports = apiGateway;

в вашем app.jsдобавьте зависимости и зарегистрируйте в обработчике

...
const apiGateway = require('../infrastructure/dependencies/apiGateway');
module.exports.getPosts = laconia(getPosts)
          .register(postsService)
          .register(apiGateway)

Теперь мы будем получать эту зависимость в обработчике, используйте объект req, чтобы вернуть ответ более чистым способом.

const handler = async (event, { postsService, api }) => {
  const { req, res } = api;
  const data = await postsService.getPosts();
  
  return res({ data }, 200);
};
module.exports = handler;

Красиво, правда? Давайте добавим еще несколько зависимостей.

npm install --save loggy

Loggy: красочный std stream предельно простой регистратор для node.js

Создайте loggy.js в src/infrastructure/dependencies.

const logger = require('loggy');
const loggy = async () => {
  return { logger };
};
module.exports = loggy;

в вашем app.js сначала добавьте зависимости и зарегистрируйте в обработчике, почему сначала? Потому что в Лаконии есть нечто, называемое цепной инъекцией 🌈.

Вы можете связать свою инъекцию в Лаконии, вызвав register несколько раз. Каждая из зарегистрированных вами фабрик будет вызываться последовательно, и следующие фабрики смогут получить доступ к экземплярам, ​​созданным предыдущими фабриками.

...
const logger = require('../infrastructure/dependencies/loggy')
module.exports.getPosts = laconia(getPosts)
          .register(logger)
          .register(postsService)
          .register(apiGateway)

Таким образом, в вашем postsService у вас будет регистратор, если вы хотите использовать его для регистрации всех вызовов других служб.

const Post = require('../httpServices/postService/Post');
const postInstance = ({ logger }) => {
  logger.info('Hi from postService');
  const postsService = new Post();
  return { postsService }
};
module.exports = postInstance;

Вы увидите нечто подобное в консоли.

15:34:19 - info: Hi from postService

И, конечно же, вы можете использовать его в обработчике.

const handler = async (event, { logger, postsService, api }) => {
  logger.info('Hi from the handler getPosts');
  const { req, res } = api;
  const data = await postsService.getPosts();
  
  return res({ data }, 200);
};
module.exports = handler;

Наконец, создайте новую конечную точку, в которой мы используем конкретный пост, добавьте новый обработчик функции в serverless.yml.

getPost:
    handler: src/application/app.getPost
    events:
      - http:
          path: /posts/{id}
          method: get

В приложении требуется обработчик getPost, оберните его зависимостями и экспортируйте объект с помощью двух обработчиков.

...
const getPost = require('../domain/getPost');
...
module.exports = {
  getPost: laconia(getPost)
             .register(logger)
             .register(postsService)
             .register(apiGateway),
  getPosts: laconia(getPosts)
             .register(logger)
             .register(postsService)
             .register(apiGateway),
}

Создайте файл getPost.js.

const handler = async (event, { logger, postsService, api }) => {
  const { req, res } = api;
  const request = req(event);
  logger.info('Hello', 'loggy');
const data = await postsService.getPost(request.params.id);
return res({ data }, 200);
};
module.exports = handler;

В Post.js добавьте новый метод для получения одного сообщения.

...
getPost(id) {
    const url = `${api.baseURL}/${this.posts}/${id}`;
    return this.request(url);
}
...

Попробуйте! 🚀

И все, мы сделали много, но это было основой для всего, что мы хотели сделать, в Лаконии есть гораздо больше, я рекомендую прочитать документацию и поддержать проект.

Наконец, чтобы развернуть это на AWS Lambda, нам нужно установить aws-cli и настроить профиль с вашими acces_key и secret_key. .

Как только вы это сделаете, разверните его!

serverless deploy

Вот репозиторий, где вы можете найти весь код. 🤙🏼