Предварительные условия
Вы знаете основные принципы, методы и стандарты COBOL. В этом руководстве мы используем GnuCOBOL — бесплатный компилятор COBOL, реализующий значительную часть стандартов COBOL 85, COBOL 2002, COBOL 2014 и X/Open COBOL, а также многие расширения, включённые в другие компиляторы COBOL.
Вы знакомы с протоколом HTTP — форматами запросов и ответов.
У вас установлены:
- Docker — инструмент виртуализации, работающий из командной строки.
- NPM — менеджер пакетов для JavaScript.
- Git — клиент с открытым исходным кодом для управления версиями.
У вас есть аккаунт на GitHub для публикации микросервисов.
Вы можете использовать любой удобный текстовый редактор, но я рекомендую Visual Studio Code (или его версию с открытым исходным кодом VSCodium) с установленным расширением синтаксиса COBOL bitlang.cobol
.
TLDR
Полный исходный код этого руководства на GitHub.
Спецификации
Одна из сильных сторон COBOL — это десятичные вычисления. В этом руководстве мы создадим высокоточный микросервис обмена валют, обрабатывающий HTTP API и возвращающий сумму в евро в формате JSON.
Скажем, микросервис ожидает HTTP запрос GET /<currency>/<amount>
по порту 8000 и отвечает JSON {"amount": <amount>}
, где:
<currency>
— это трёхбуквенный ISO код валюты, например, USD;<amount>
— это числовое значение, разделённое точкой, например, 999.999
Любые несоответствующие запросы, неподдерживаемые валюты и ошибки вычисления приведут к ответу 404 Not Found
.
Обменные курсы — это курсы евро, опубликованные Европейским центральным банком. Ежедневные обновления курса и поддержка многопоточности выходят за рамки этого руководства.
Структура
Нам нужно 3 каталога — src
для основной программы, tests
для тестовой и resources
для статических файлов. Пожалуйста, скачайте CSV (.zip) с ECB и разархивируйте в каталог resources
. Файл содержит обменные курсы евро к 32 валютам. Как указано в спецификации, курсы остаются неизменными.
$ ls
resources src tests
$ ls resources
eurofxref.csv
Наконец, создадим пустые файлы microservice.cbl
и microservice-test.cbl
в каталогах src
и tests
соответственно. Они понадобятся нам позже.
Зависимости
Наш микросервис зависит от:
- HTTP-сервера для обработки запросов;
- парсера ECB для CSV;
- среды тестирования GCBLUnit.
Все эти компоненты доступны в реестре пакетов COBOL — cobolget.com. Мы с лёгкостью можем интегрировать эти зависимости с помощью инструмента управления пакетами COBOL с открытым исходным кодом cobolget
. Вот полный листинг:
$ npm install -g cobolget
$ cobolget init
Manifest modules.json created.
$ cobolget add core-network
Dependency 'core-network' has been added to the manifest.
$ cobolget add core-string
Dependency 'core-string' has been added to the manifest.
$ cobolget add --debug gcblunit
Debug dependency 'gcblunit' has been added to the manifest.
$ cobolget update
Lockfile modules-lock.json updated.
$ cobolget -t bca12d6c4efed0627c87f2e576b72bdb5ab88e34 install
В последней команде используется Team Token, поскольку core-network
является закрытым пакетом, принадлежащим Cobolget
,но свободно распространяемым в сообществе. Вы увидите длинный процесс загрузки, заканчивающийся строкой:
Copybook modules.cpy updated
Этот файл, уже известный как COBOL Copybook, включает в себя все прямые и унаследованные зависимости для микросервиса. Мы используем его внутри нашей программы на следующем этапе.
Программа
По сути наша программа должна:
- читать CSV-файл;
- преобразовывать CSV-текст в список пар валюта-курс;
- запускать локальный TCP/IP сервер на 8000 порту с помощью реализации обратного вызова, который обрабатывает HTTP запросы.
identification division.
program-id. microservice.
...
procedure division.
*> чтение CSV-файла в csv-содержимое
open input file-csv.
if not file-exists
display "Error reading file" upon syserr
stop run
end-if.
perform until exit
read file-csv at end exit perform end-read
end-perform.
close file-csv.
*> преобразование csv-содержимого в список пар ключ-значение
move csv-ecb-rates(csv-content) to dataset.
*> запуск HTTP сервера с обратным вызовов http-обработчика
call "receive-tcp" using "localhost", 8000, 0, address of entry "http-handler".
end program microservice.
identification division.
program-id. http-handler.
...
procedure division using l-buffer, l-length returning omitted.
*> инициализация обменного курса
set address of exchange-rates to dataset-ptr.
*> парсинг запроса как "GET /<currency>/<amount>"
unstring l-buffer(1:l-length) delimited by all SPACES into request-method, request-path.
if not http-get
perform response-NOK
end-if.
*> поиск валюты и расчёт суммы в евро
perform varying idx from 1 by 1 until idx > 64
if rate-currency(idx) = get-currency
compute eur-amount = numval(get-amount) / rate-value(idx)
on size error perform response-NOK
end-compute
perform response-OK
end-if
end-perform.
*> или ничего
perform response-NOK.
response-OK section.
move HTTP-OK to response-status.
move byte-length(response-content) to response-content-length.
perform response-any.
response-NOK section.
move HTTP-NOT-FOUND to response-status.
move 0 to response-content-length.
perform response-any.
response-any section.
move 1 to l-length.
string response delimited by size into l-buffer with pointer l-length.
subtract 1 from l-length.
goback.
end program http-handler.
copy "modules/modules.cpy".
Программа receive-tcp
— это сервер, который принимает входящие соединения, считывает содержимое запроса в буфер и делится буфером с программой обратного вызова. Обратный вызов парсит содержимое и заменяет буфер ответом. Сервер отправляет ответ обратно клиенту. Полный листинг программы на GitHub.
Давайте установим среду выполнения GnuCOBOL Docker:
$ docker run -d -i --name gnucobol olegkunitsyn/gnucobol:2.2
$ docker exec -i gnucobol cobc -V
cobc (GnuCOBOL) 2.2.0
Copyright (C) 2017 Free Software Foundation, Inc.
License GPLv3+: GNU GPL version 3 or later <http://gnu.org/licenses/gpl.html>
This is free software; see the source for copying conditions. There is NO warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
Written by Keisuke Nishida, Roger While, Ron Norman, Simon Sobisch, Edward Hart
Built Jul 26 2020 07:44:23
Packaged Sep 06 2017 18:45:29 UTC
C version "9.3.0"
В этом руководстве мы используем GnuCOBOL 2.2, единственный стабильный GnuCOBOL компилятор, доступный в настоящее время в бинарных дистрибутивах. Вы так же можете найти его и установить в исходном формате на свой компьютер.
Тест
Наш микросервис будет следовать практике непрерывной интеграции, когда разработчики интегрируют исходный код в общий репозиторий, где каждая интеграция тестируется автоматически. Для тестирования воспользуемся простой тестовой средой GCBLUnit, уже установленной ранее как зависимость для отладки.
Давайте создадим Dockerfile
микросервиса:
FROM olegkunitsyn/gnucobol:2.2
RUN mkdir /microservice
WORKDIR /microservice
COPY . .
EXPOSE 8000
RUN cobc -x -debug modules/gcblunit/gcblunit.cbl tests/* --job='microservice-test'
Мы открываем 8000 порт и выполняем работу microservice-test
при каждой сборке образа. Последний элемент всей картины — это тест-файл microservice-test.cbl
:
>>SOURCE FORMAT FREE
identification division.
program-id. microservice-test.
environment division.
configuration section.
repository.
function csv-ecb-rates
function substr-pos
function all intrinsic.
data division.
working-storage section.
01 dataset external.
05 dataset-ptr usage pointer.
01 buffer pic x(1024) value "GET /USD/1 HTTP1.1".
procedure division.
move csv-ecb-rates(concatenate("Date, USD, " x"0a" "17 July 2020, 1.1428, ")) to dataset.
call "http-handler" using buffer, byte-length(buffer).
perform http-handler-test.
goback.
http-handler-test section.
call "assert-notequals" using 0, substr-pos(buffer, "HTTP/1.1 200 OK").
call "assert-notequals" using 0, substr-pos(buffer, "Content-Type: application/json").
call "assert-notequals" using 0, substr-pos(buffer, "Content-Length: 44").
call "assert-equals" using 104, substr-pos(buffer, "0.8750437521876093").
end program microservice-test.
copy "src/microservice.cbl".
Для целей тестирования я подготовил минимальное CSV содержимое только с одной валютой — USD. Как видно из определения буфера, тест запрашивает конвертацию 1 USD. Мы ожидаем ненулевые HTTP-заголовки, а также обменную сумму с высокой точностью 0.8750437521876093. Последняя строка включает в себя основную тестируемую программу.
Контейнер
Создадим образ Docker:
$ docker build --tag microservice .
...
OK
Tests: 0000000001, Skipped: 0000000000
Assertions: 0000000004, Failures: 0000000000, Exceptions: 0000000000
...
Прекрасно! Наш образ Docker успешно прошёл тест, рассчитав 4 выражения, и готов к запуску:
$ docker run -d -i --name microservice -p 8000:8000 microservice
$ docker exec -i microservice cobc -j -x src/microservice.cbl
TCP server started on localhost:08000. Hit Ctrl+C to stop.
Откроем http://localhost:8000/USD/99.99
и http://localhost:8000/ABC/1
в браузере и посмотрим, что произойдёт. Чтобы остановить и удалить контейнер, запустим:
$ docker rm --force microservice
GitHub
Наконец, опубликуем микросервис, применяя GitHub Actions, где каждый запрос на включение внесённых изменений или пуш в репозиторий запускают выполнение microservice-test
. Всё, что вам нужно, — это файл docker-image.yml
в каталоге .github/workflows
:
name: Docker Image CI
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- name: Build the Docker image
run: docker build . --file Dockerfile --tag my-image-name:$(date +%s)
Заключение
Мы реализовали микросервис с помощью библиотеки Git, менеджера пакетов, модульного тестирования и виртуализации в рамках подхода непрерывной интеграции. COBOL, которому уже 60 лет, подходит для современной разработки программного обеспечения!
Читайте также:
- WebRTC: фреймворк ICE, STUN и сервера TURN
- Почему нельзя разрешать поля GraphQL как конечные точки REST
- Как сжимать коммиты в Git с помощью git squash
Читайте нас в Telegram, VK и Яндекс.Дзен
Перевод статьи Oleg Kunitsyn: Modern COBOL: Microservice Tutorial