В этом руководстве мы создадим бессерверное платежное приложение с помощью Stripe и AWS Lambda.

Попробуйте Живое демо!

Исходный код для внутренней части и интерфейса доступен на GitHub.

Что такое бессерверное?

Когда мы говорим «бессерверный», это не означает, что серверы больше не задействованы. Это означает, что вам больше не нужно думать о них.

Бессерверный – это подход, направленный на устранение необходимости в управлении инфраструктурой за счет:

  • Использование управляемого сервиса вычислений FaaS, такого как AWS Lambda, Webtask, Google Cloud Functions и IBM OpenWhisk, для выполнения кода,
  • Использование сторонних сервисов и API, а также
  • Применение бессерверных архитектур и шаблонов.

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

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

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

Функции как услуга (FaaS)

Поставщики FaaS, такие как AWS Lambda, предлагают платформу нового типа, которая позволяет развертывать и вызывать эфемерные (краткосрочные) функциональные процессы через события для обработки отдельных запросов.

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

Думайте о бессерверных функциях как об «MeeSeeks» облачных вычислений.

Мизики — существа, созданные для одной единственной цели, ради которой они готовы пойти на все. После того, как они выполнили свою задачу, срок их действия истекает, и они исчезают в воздухе.

– из вики «Рик и Морти»

Как и MeeSeeks, функциональные процессы исчезают после выполнения.

Планирование емкости в FaaS

При традиционных развертываниях вы можете выделять вычислительные мощности с избытком или недостатком. Это особенно актуально, когда нагрузка на ваш трафик непредсказуема.

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

При использовании FaaS предоставленная мощность всегда будет равна фактическому использованию. Недостаточной или избыточной подготовки нет. Каждый запрос порождает эфемерный функциональный процесс, который выполняет вашу функцию. Поставщик FaaS автоматически обрабатывает горизонтальное масштабирование. Если вашей системе необходимо обрабатывать 100 запросов параллельно, провайдер создаст столько вызовов функций без какой-либо дополнительной настройки с вашей стороны. Вы платите только за используемые вычислительные мощности.

PaaS против FaaS

В таблице ниже показаны различия между PaaS и Faas:

Платформа как услуга (PaaS) Функция как услуга (FaaS) Время запуска Запускается в минутах Запускается в миллисекундах Время работы Запускается 24/7 Запускается при обработке входящего запроса Стоимость Ежемесячные циклы выставления счетов Плата за использование, в вызовах и продолжительность Единица кода Монолитное приложение Одноцелевые автономные функции

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

Предпосылки

Если вы хотите продолжить, убедитесь, что вы выполнили следующие шаги.

1. Полоса

Stripe — платформа для обработки онлайн-платежей. Убедитесь, что вы зарегистрировались в Stripe, и получите доступ к своей панели инструментов.

Переключите режим Test и получите на панели управления API следующее:

  • Ваш публикуемый ключ API
  • Ваш секретный ключ API

Нам нужны оба ключа для нашего приложения. Публикуемый ключ API используется для токенизации карт клиентов, а секретный ключ API используется для обработки платежей через API Stripe.

2. Без сервера

Бессерверная среда (serverless) — это интерфейс командной строки (CLI) Node.js, который помогает разрабатывать и развертывать бессерверные функции вместе с необходимыми для них инфраструктурными ресурсами.

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

serverless не зависит от поставщика и среды выполнения, поэтому вы можете использовать любой из поддерживаемых поставщиков FaaS и языков.

Установите модуль узла serverless с помощью npm или yarn. В вашем терминале выполните:

$ yarn global add serverless

Проверим, что serverless установлен:

$ serverless --version 
1.13.2

3. Веб-сервисы Amazon (AWS)

Мы будем использовать AWS в качестве поставщика FaaS. Если вы еще этого не сделали, зарегистрируйте учетную запись AWS.

Следующее, что нам нужно сделать, это создать пользователя AWS, который имеет административный доступ к вашей учетной записи. Это позволяет Serverless Framework настраивать сервисы в вашей учетной записи AWS.

  1. Войти в свой аккаунт AWS
  2. Перейдите на страницу управления идентификацией и доступом (IAM).

  1. Нажмите ссылку Пользователи на боковой панели.
  2. Нажмите кнопку Добавить пользователя.
  3. Введите serverless-admin, установите флажок Программный доступ Тип доступа и нажмите Далее:Разрешения.

  1. Нажмите Прикрепить существующие политики напрямую, установите флажок Доступ администратора и нажмите Далее: проверить.

  1. Проверьте свой выбор и нажмите Создать пользователя.

  1. Сохраните Идентификатор ключа доступа и Секретный ключ доступа вновь созданного пользователя.

Сделанный! Теперь мы создали пользователя, который может выполнять действия в нашей учетной записи AWS от нашего имени (благодаря политике доступа администратора).

Наконец, мы передадим API-ключ и секрет пользователя в serverless. С фреймворком serverless, установленным на вашем компьютере, выполните:

serverless config credentials --provider aws --key <your_aws_key> --secret <your_aws_secret>

4. Пряжа

Мы используем yarn вместо npm.

Руки вверх

Использование информации о карте с Stripe — это двухэтапный процесс:

  1. Безопасный сбор платежной информации с помощью токенизации (выполняется во внешнем интерфейсе с помощью опубликуемого API-ключа Stripe)
  2. Используйте платежную информацию в запросе на оплату или сохраните ее на потом (выполняется в серверной части с использованием секретного ключа API Stripe)

Когда пользователь вводит свою платежную информацию во встраиваемую форму Stripe Checkout, происходит токенизация на стороне клиента.

Токенизация на стороне клиента — это метод, который Stripe использует для безопасного сбора информации о картах непосредственно от ваших клиентов. Во время этого процесса токен, представляющий эту информацию, возвращается на ваш внутренний сервер для использования в запросе на оплату (или для сохранения данных карты для последующего использования). Токены можно использовать только один раз, срок их действия истекает в течение нескольких минут.

Токенизация гарантирует, что никакие конфиденциальные данные карты никогда не должны касаться вашего сервера, поэтому ваша интеграция может работать в соответствии с требованиями PCI. Если какие-либо данные карты будут передаваться или храниться на вашем сервере, вы будете нести ответственность за выполнение всех требований PCI DSS и необходимых аудитов.

Фронтенд-приложение

Фронтенд отвечает за безопасный сбор информации о картах ваших клиентов. Написано на Next.js и React.

Ниже представлен компонент PayButton, который является ключевым для нашего потока платежей.

import fetch from 'isomorphic-unfetch';
import PropTypes from 'prop-types';
import React from 'react';
import StripeCheckout from 'react-stripe-checkout';

import config from '../config';

class PayButton extends React.Component {
  constructor(props) {
    super(props);
    this.onToken = this.onToken.bind(this);
  }

  async onToken(token) { // On a successful tokenization request,
    const res = await fetch(config.stripe.apiUrl, { // POST to our backend server with the token and charge details
      method: 'POST',
      body: JSON.stringify({
        token,
        charge: {
          amount: this.props.amount,
          currency: config.stripe.currency,
        },
      }),
    });
    const data = await res.json();
    console.log('onToken');
    console.log(data);
  }

  render() {
    return (
      <StripeCheckout
        name="Serverless Stripe Store Inc."
        token={this.onToken}
        amount={this.props.amount}
        currency={config.stripe.currency}
        stripeKey={config.stripe.apiKey} // Stripe publishable API Key
        allowRememberMe={false}
      />
    );
  }
}

PayButton.propTypes = {
  amount: PropTypes.number.isRequired,
};

export default PayButton;

После успешного запроса токенизации к Stripe API мы отправляем POST-запрос, содержащий токен и детали платежа, на URL-адрес, где находится наш сервер.

Серверное приложение

Токен, возвращенный нашим интерфейсом, используется нашим бэкендом для обработки фактического платежа (через вызов Stripe API).

Наш бессерверный «наносервис» состоит из одноцелевых, детализированных функций. Фреймворк Serverless использует DSL, который описывает форму наших сервисов. Взгляните на serverless.yml, который описывает наш проект:

# Serverless.yml is the configuration the CLI
# uses to deploy your code to your provider of choice
service: serverless-stripe-backend

# Configuration variables
custom:
  secrets: ${file(secrets.json)}

# The `provider` block defines where your service will be deployed
provider:
  name: aws
  runtime: nodejs6.10
  stage: dev
  profile: personal
  region: ap-southeast-1
  environment:
    STRIPE_SECRET_KEY: ${self:custom.secrets.stripeSecretKey} # Stripe secret API key

# The `functions` block defines what code to deploy
functions:
  createCharge:
    handler: functions/createCharge.handler
    # The `events` block defines how to trigger the handler.createCharge code
    events:
      - http:
          path: charges
          method: post
          cors: true

Наш сервис имеет единственную функцию createCharge, исходный код которой находится в /functions/createCharge.js и может запускаться событиями HTTP POST для маршрута /charges.

Три ключевые абстракции serverless приложений – это Функции, События и Ресурсы:

  • Функции – это фрагменты одноцелевого кода, развернутые в облаке. Решая, что должно быть в функции, подумайте о принципе единой ответственности или философии Unix.
  • События настроены для запуска ваших функций. В AWS к таким событиям относятся инфраструктурные события, такие как HTTP-запрос конечной точки AWS API Gateway (полезно для HTTP API) и загрузка корзины AWS S3 (полезно для загрузки файлов).
  • Ресурсы — это компоненты инфраструктуры, с которыми взаимодействуют ваши Функции. Поскольку функции не имеют состояния, нам нужен какой-то способ захвата и сохранения состояния (например, в таблице AWS DynamoDB или корзине AWS S3).

Далее давайте посмотрим на функцию createCharge. Это функция, которая принимает токен Stripe и использует его для создания заряда Stripe:

const stripe = require('stripe')(process.env.STRIPE_SECRET_KEY);

module.exports.handler = (event, context, callback) => {
  const requestBody = JSON.parse(event.body);
  const token = requestBody.token.id;
  const amount = requestBody.charge.amount;
  const currency = requestBody.charge.currency;

  return stripe.charges.create({ // Create Stripe charge with token
    amount,
    currency,
    description: 'Serverless Stripe Test charge',
    source: token,
  })
    .then((charge) => { // Success response
      const response = {
        statusCode: 200,
        headers: {
          'Access-Control-Allow-Origin': '*',
        },
        body: JSON.stringify({
          message: `Charge processed succesfully!`,
          charge,
        }),
      };
      callback(null, response);
    })
    .catch((err) => { // Error response
      const response = {
        statusCode: 500,
        headers: {
          'Access-Control-Allow-Origin': '*',
        },
        body: JSON.stringify({
          error: err.message,
        }),
      };
      callback(null, response);
    })
};

Функция обработчика AWS Lambda (Node.js) принимает три аргумента:

  • event — AWS Lambda использует этот параметр для передачи данных события обработчику.
  • context — AWS Lambda использует этот параметр, чтобы предоставить вашему обработчику информацию о времени выполнения выполняемой функции Lambda.
  • callback — вы можете использовать необязательный обратный вызов для возврата информации вызывающей стороне, в противном случае возвращаемое значение равно нулю.

В функции createCharge мы извлекаем детали платежа из тела HTTP-запроса и вызываем API Stripe для обработки списания. Затем мы возвращаем ответ HTTP, содержащий созданный объект оплаты.

Развертывание

Чтобы развернуть наше приложение serverless на AWS, выполните следующие действия:

git clone [email protected]:yosriady/serverless-stripe-backend.git 
cd serverless-stripe-backend 
serverless deploy

serverless CLI переведет serverless.yml на язык конкретного поставщика, например AWS CloudFormation, и настроит вашу облачную инфраструктуру.

$ serverless deploy
Serverless: Packaging service...
Serverless: Uploading CloudFormation file to S3...
Serverless: Uploading artifacts...
Serverless: Uploading service .zip file to S3 (18.17 MB)...
Serverless: Updating Stack...
Serverless: Checking Stack update progress...
..............
Serverless: Stack update finished...
Service Information
service: serverless-stripe-backend
stage: dev
region: ap-southeast-1
api keys:
  None
endpoints:
  POST - https://kpygs0yhak.execute-api.ap-southeast-1.amazonaws.com/dev/charges
functions:
  createCharge: serverless-stripe-backend-dev-createCharge
Serverless: Removing old service versions...

Поскольку мы указали триггер события HTTP для нашей функции createCharge в serverless.yml, мы получаем конечную точку URL-адреса https://kpygs0yhak.execute-api.ap-southeast-1.amazonaws.com/dev/charges, которую мы можем использовать для запуска нашей функции. За кулисами serverless создал ресурс AWS API Gateway и сопоставление между нашим HTTP-маршрутом и лямбда-функцией. Это внутренний URL-адрес, с которым взаимодействует наше внешнее приложение.

Тестирование

Так как каждая наша функция делает только одну вещь, это модуль и по определению подлежит модульному тестированию! Проверьте createCharge.test.js:

const LambdaTester = require('lambda-tester');
const nock = require('nock');

const createCharge = require('../../functions/createCharge');

const testJsonBody = '{"token":{"id":"tok_1AXRROEFsRbbjUA2h7qgz1nD","object":"token","card":{"id":"card_1AXRROEFsRbbjUA2qznZttIr","object":"card","address_city":null,"address_country":null,"address_line1":null,"address_line1_check":null,"address_line2":null,"address_state":null,"address_zip":null,"address_zip_check":null,"brand":"Visa","country":"US","cvc_check":"pass","dynamic_last4":null,"exp_month":12,"exp_year":2020,"funding":"credit","last4":"4242","metadata":{},"name":"[email protected]","tokenization_method":null},"client_ip":"103.224.167.33","created":1498123050,"email":"[email protected]","livemode":false,"type":"card","used":false},"charge":{"amount":200,"currency":"USD"}}';
const testEvent = {
  body: testJsonBody
};

beforeAll(() => nock.disableNetConnect());
afterAll(() => nock.enableNetConnect());

describe('createCharge', () => {
  describe('success cases', () => {
    beforeEach(() => {
      nock('https://api.stripe.com/v1')
          .post('/charges')
          .reply(200, { success: true });
    });

    test('returns response', () => {
      return LambdaTester(createCharge.handler)
			.event(testEvent)
			.expectResult((data) => {
        expect(data).toMatchSnapshot();
      });
    });
  });

  describe('error cases', () => {
    beforeEach(() => {
      nock('https://api.stripe.com/v1')
          .post('/charges')
          .reply(500);
    });

    test('returns response', () => {
      return LambdaTester(createCharge.handler)
      .event(testEvent)
      .expectResult((data) => {
        expect(data).toMatchSnapshot();
      });
    });
  });
});

Мы используем тестовую среду Jest, лямбда-тестер для локального вызова наших функций и nock для заглушки внешних API. Мы используем моментальные снимки Jest, чтобы упростить процесс создания тестовых случаев.

Поздравляем! Вы остались без сервера!

Мы создали масштабируемую платежную систему, используя внешний сервис вычислений и сторонние API. По пути мы узнали:

  • Что такое бессерверные технологии и почему они привлекательны
  • Как поток платежей работает с Stripe как на внешнем, так и на внутреннем интерфейсе
  • Основные абстракции без сервера: события, функции, ресурсы
  • Как создать серверную часть без сервера с помощью Node.js и AWS Lambda
  • Как настроить триггеры событий для функций
  • Как развернуть функции
  • Как писать и запускать модульные тесты

Спасибо за чтение!

Исходный код

Не стесняйтесь клонировать и проверять исходный код:

Запросы на вытягивание и отзывы приветствуются.

Получить книгу!

В этой статье пропущено значительное количество деталей в пользу краткости. Например, в примерах внешнего и внутреннего приложений вообще не настроены механизмы аутентификации! Если вы хотите узнать больше, ознакомьтесь с книгой Going Serverless!

Going Serverless — это практическое руководство по созданию масштабируемых приложений с помощью Serverless Framework и AWS Lambda. Эта книга научит вас проектировать, разрабатывать, тестировать, развертывать, отслеживать и защищать бессерверные приложения от планирования до производства.

В этой книге мы создадим три практических проекта с использованием Javascript, Amazon Web Services (AWS) и бессерверной среды. Проекты, которые вы создадите, включают:

  • Конвейер обработки изображений, управляемый событиями
  • Масштабируемый парсер
  • Бессерверный микросервис

Каждый практический проект — это реальная реализация шаблона бессерверного проектирования. Вы также узнаете, как использовать набор технологий AWS помимо Lambda, таких как API Gateway, DynamoDB, SNS, S3 и другие.

Получите первые три главы бесплатно!

Первоначально опубликовано на yos.io 22 июня 2017 г.