Pull to refresh

Modern COBOL: Microservice Tutorial

Reading time6 min
Views1.1K

You will learn and implement a microservice in COBOL without Mainframe. You will structure the project, manage dependencies, implement automatic tests and build virtualized execution environment. Finally, you will publish the microservice on GitHub under Continuous Integration workflow.


Preconditions


You have learned basic principles, methods and standards of COBOL. In this tutorial we’ll use GnuCOBOL — a free COBOL compiler which implements a substantial part of the COBOL 85, COBOL 2002 and COBOL 2014 standards and X/Open COBOL, as well as many extensions included in other COBOL compilers.


You are familiar with HTTP protocol — request and response formats.


You have Docker, a command-line virtualization tool, installed.


You have NPM, a package manager for JavaScript programming language, installed.


You have Git, an open source distributed version control client, installed.


You have GitHub account for publishing of the microservice.


You may use any text editor you like, but I recommend Visual Studio Code (or its open-source version VSCodium) with COBOL-syntax extension bitlang.cobol installed.


TLDR


Complete source-code of this tutorial you can see on GitHub.


Specifications


One of strengths of COBOL is a decimal mathematics. In this tutorial we’ll create a high-precision currency exchange microservice that exposes HTTP API and returns EUR amount in JSON format.


Let’s say, the microservice awaits HTTP request GET /currency/amount on port 8000 and respond JSON {"amount": amount}, where


  • currency is a tree-letter ISO currency code, i.e. USD
  • amount is a numeric value separated by dot, i.e. 999.999

Any mismatching requests, unsupported currencies, as well as calculation errors will result in 404 Not Found responses.


Exchange rates are Euro foreign exchange reference rates published by the European Central Bank. Daily renewals of the exchange rates and multi-threading are out of scope in this tutorial.


Structure


We need 3 directories — src for main program, tests for test program and resources for static files. Please download CSV (.zip) from ECB website and extract to resources directory. The file contains exchange rates to Euro for 32 currencies. As defined in the specification, we’ll keep the rates static.


$ ls
resources  src  tests
$ ls resources
eurofxref.csv

Finally, please create empty microservice.cbl and microservice-test.cbl files in src and tests directories respectively. We will need them later.


Dependencies


Our microservice depends on HTTP server for handling requests, ECB parser for CSV and GCBLUnit testing framework. All these components are available on COBOL Package Registry — cobolget.com. We can simply integrate these dependencies by using an open-source COBOL package management tool — cobolget. Here’s complete listing:


$ 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

We use Team Token in the last command because core-network is private package owned by Cobolget but freely shared with the community. You will see long installation log which ends with


Modules modules.cpy and modules.cbl updated.

The file modules.cpy, already known as COBOL Copybook, includes all direct and inherited dependencies for the microservice. We’ll use it inside our program in the next step.


Program


Basically, our program must


  • read CSV file
  • convert CSV text into the list of Currency-Rate pairs
  • launch local TCP/IP server on port 8000 by
  • implementing a callback which handles HTTP requests

identification division.
program-id. microservice.
...
procedure division.
  *> read CSV file into csv-content
  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.  *> convert csv-content to the list of key-value pairs
  move csv-ecb-rates(csv-content) to dataset.*> start HTTP server with http-handler callback
  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.
  *> initialize exchange rates
  set address of exchange-rates to dataset-ptr.

  *> parse request as "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.  *> find currency and calculate eur-amount
  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.  *> or nothing
  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".

The receive-tcp program is a server which accepts incoming connections, reads the content of the request into the buffer and shares the buffer with the callback program. The callback parses the content and replaces the buffer with a response. The server sends the response back to the client. Full listing of the program you can find on GitHub.


Let’s install GnuCOBOL Docker execution environment.


$ 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"

In this tutorial we’ll use GnuCOBOL 2.2, only stable GnuCOBOL compiler available in the binary distributions at the moment. You may find and install it natively on your machine, too.


Test


Our microservice will follow Continuous Integration practices, when the developers integrate source-code into the shared repository, where each integration is getting tested automatically. For testing we’ll use simple GCBLUnit testing framework which was already installed as a debug-dependency earlier.


Let’s create Dockerfile of the microservice:


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'

We expose port 8000 and execute microservice-test job upon each build of the image. The last element of the whole picture is a test-file 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".

For testing purposes I’ve prepared minimal CSV content with one single currency USD. As you can see in definition of the buffer, the test requests the conversion of 1 USD. We expect non-nullable HTTP headers, as well as high-precision exchanged amount 0.8750437521876093. The last line includes the main program we test to.


Container


Let’s create Docker image:


$ docker build --tag microservice .
...
OK
Tests: 0000000001, Skipped: 0000000000
Assertions: 0000000004, Failures: 0000000000, Exceptions: 0000000000
...

Well done! Our Docker image successfully passed the test by evaluating 4 assertions and ready for launch.


$ 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.

Open http://localhost:8000/USD/99.99 and http://localhost:8000/ABC/1 in the browser and see what happens. To stop and remove the container, run


$ docker rm --force microservice

GitHub


At last, we’ll publish the microservice enabling GitHub Actions workflow, where each pull request or push to the repository triggers an execution of microservice-test. All you need is docker-image.yml file in .github/workflows directory:


name: Docker Image CIon:
  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)

Conclusion


You have implemented the microservice by using Git libraries, package management, unit-testing and virtualization together with Continuous Integration approach. 60-years old COBOL fits modern software engineering.


Please contact me if you have any corrections or feedback.

Tags:
Hubs:
Rating0
Comments0

Articles