{"id":391714,"date":"2024-06-29T09:59:22","date_gmt":"2024-06-29T09:59:22","guid":{"rendered":"http:\/\/savepearlharbor.com\/?p=391714"},"modified":"-0001-11-30T00:00:00","modified_gmt":"-0001-11-29T21:00:00","slug":"","status":"publish","type":"post","link":"https:\/\/savepearlharbor.com\/?p=391714","title":{"rendered":"<span>Modern COBOL: Microservice Tutorial<\/span>"},"content":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>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.<\/p>\n<p><a name=\"habracut\"><\/a>  <\/p>\n<h3 id=\"preconditions\">Preconditions<\/h3>\n<p>  <\/p>\n<p>You have learned basic principles, methods and standards of COBOL. In this tutorial we\u2019ll use <strong>GnuCOBOL<\/strong> \u2014 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.<\/p>\n<p>  <\/p>\n<p>You are familiar with <strong>HTTP protocol<\/strong> \u2014 request and response formats.<\/p>\n<p>  <\/p>\n<p>You have <strong>Docker<\/strong>, a command-line virtualization tool, installed.<\/p>\n<p>  <\/p>\n<p>You have <strong>NPM<\/strong>, a package manager for JavaScript programming language, installed.<\/p>\n<p>  <\/p>\n<p>You have <strong>Git<\/strong>, an open source distributed version control client, installed.<\/p>\n<p>  <\/p>\n<p>You have <strong>GitHub<\/strong> account for publishing of the microservice.<\/p>\n<p>  <\/p>\n<p>You may use any text editor you like, but I recommend <strong>Visual Studio Code<\/strong> (or its open-source version <strong>VSCodium<\/strong>) with COBOL-syntax extension <em>bitlang.cobol<\/em> installed.<\/p>\n<p>  <\/p>\n<h3 id=\"tldr\">TLDR<\/h3>\n<p>  <\/p>\n<p>Complete source-code of this tutorial you can see on <a href=\"https:\/\/github.com\/OlegKunitsyn\/gnucobol-examples\/tree\/master\/microservice\" rel=\"nofollow\">GitHub<\/a>.<\/p>\n<p>  <\/p>\n<h2 id=\"specifications\">Specifications<\/h2>\n<p>  <\/p>\n<p>One of strengths of COBOL is a decimal mathematics. In this tutorial we\u2019ll create a high-precision currency exchange microservice that exposes HTTP API and returns EUR amount in JSON format.<\/p>\n<p>  <\/p>\n<p>Let\u2019s say, the microservice awaits HTTP request <em>GET \/currency\/amount<\/em> on port <em>8000<\/em> and respond JSON <em>{&#171;amount&#187;: amount}<\/em>, where<\/p>\n<p>  <\/p>\n<ul>\n<li><em>currency<\/em> is a tree-letter ISO currency code, i.e. USD<\/li>\n<li><em>amount<\/em> is a numeric value separated by dot, i.e. 999.999<\/li>\n<\/ul>\n<p>  <\/p>\n<p>Any mismatching requests, unsupported currencies, as well as calculation errors will result in <em>404 Not Found<\/em> responses.<\/p>\n<p>  <\/p>\n<p>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.<\/p>\n<p>  <\/p>\n<h2 id=\"structure\">Structure<\/h2>\n<p>  <\/p>\n<p>We need 3 directories \u2014 <em>src<\/em> for main program, <em>tests<\/em> for test program and <em>resources<\/em> for static files. Please download <strong>CSV (.zip)<\/strong> from <a href=\"https:\/\/www.ecb.europa.eu\/stats\/policy_and_exchange_rates\/euro_reference_exchange_rates\/html\/index.en.html\" rel=\"nofollow\">ECB website<\/a> and extract to <em>resources<\/em> directory. The file contains exchange rates to Euro for 32 currencies. As defined in the specification, we\u2019ll keep the rates static.<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">$ ls resources  src  tests $ ls resources eurofxref.csv<\/code><\/pre>\n<p>  <\/p>\n<p>Finally, please create empty <em>microservice.cbl<\/em> and <em>microservice-test.cbl<\/em> files in <em>src<\/em> and <em>tests<\/em> directories respectively. We will need them later.<\/p>\n<p>  <\/p>\n<h2 id=\"dependencies\">Dependencies<\/h2>\n<p>  <\/p>\n<p>Our microservice depends on <a href=\"https:\/\/cobolget.com\/package\/core-network\" rel=\"nofollow\">HTTP server<\/a> for handling requests, <a href=\"https:\/\/cobolget.com\/package\/core-string\" rel=\"nofollow\">ECB parser<\/a> for CSV and <a href=\"https:\/\/cobolget.com\/package\/gcblunit\" rel=\"nofollow\">GCBLUnit<\/a> testing framework. All these components are available on <a href=\"https:\/\/cobolget.com\/\" rel=\"nofollow\">COBOL Package Registry \u2014 cobolget.com<\/a>. We can simply integrate these dependencies by using an open-source COBOL package management tool \u2014 <em>cobolget<\/em>. Here\u2019s complete listing:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">$ 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<\/code><\/pre>\n<p>  <\/p>\n<p>We use Team Token in the last command because <em>core-network<\/em> is private package owned by <em>Cobolget<\/em> but freely shared with the community. You will see long installation log which ends with<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">Modules modules.cpy and modules.cbl updated.<\/code><\/pre>\n<p>  <\/p>\n<p>The file <em>modules.cpy<\/em>, already known as <em>COBOL Copybook<\/em>, includes all direct and inherited dependencies for the microservice. We\u2019ll use it inside our program in the next step.<\/p>\n<p>  <\/p>\n<h2 id=\"program\">Program<\/h2>\n<p>  <\/p>\n<p>Basically, our program must<\/p>\n<p>  <\/p>\n<ul>\n<li>read CSV file<\/li>\n<li>convert CSV text into the list of Currency-Rate pairs<\/li>\n<li>launch local TCP\/IP server on port 8000 by<\/li>\n<li>implementing a callback which handles HTTP requests<\/li>\n<\/ul>\n<p>  <\/p>\n<pre><code class=\"plaintext\">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 \/&lt;currency>\/&lt;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\".<\/code><\/pre>\n<p>  <\/p>\n<p>The <em>receive-tcp<\/em> 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.<\/p>\n<p>  <\/p>\n<p>Let\u2019s install GnuCOBOL Docker execution environment.<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">$ 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 &lt;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\"<\/code><\/pre>\n<p>  <\/p>\n<p>In this tutorial we\u2019ll 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.<\/p>\n<p>  <\/p>\n<h2 id=\"test\">Test<\/h2>\n<p>  <\/p>\n<p>Our microservice will follow <strong>Continuous Integration<\/strong> practices, when the developers integrate source-code into the shared repository, where each integration is getting tested automatically. For testing we\u2019ll use simple <strong>GCBLUnit<\/strong> testing framework which was already installed as a debug-dependency earlier.<\/p>\n<p>  <\/p>\n<p>Let\u2019s create <em>Dockerfile<\/em> of the microservice:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">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'<\/code><\/pre>\n<p>  <\/p>\n<p>We expose port <em>8000<\/em> and execute <em>microservice-test<\/em> job upon each build of the image. The last element of the whole picture is a test-file <em>microservice-test.cbl<\/em>:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">       >>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\".<\/code><\/pre>\n<p>  <\/p>\n<p>For testing purposes I\u2019ve prepared minimal CSV content with one single currency <em>USD<\/em>. As you can see in definition of the buffer, the test requests the conversion of <em>1 USD<\/em>. We expect non-nullable HTTP headers, as well as high-precision exchanged amount <em>0.8750437521876093<\/em>. The last line includes the main program we test to.<\/p>\n<p>  <\/p>\n<h2 id=\"container\">Container<\/h2>\n<p>  <\/p>\n<p>Let\u2019s create Docker image:<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">$ docker build --tag microservice . ... OK Tests: 0000000001, Skipped: 0000000000 Assertions: 0000000004, Failures: 0000000000, Exceptions: 0000000000 ...<\/code><\/pre>\n<p>  <\/p>\n<p>Well done! Our Docker image successfully passed the test by evaluating 4 assertions and ready for launch.<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">$ 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.<\/code><\/pre>\n<p>  <\/p>\n<p>Open <em><a href=\"http:\/\/localhost:8000\/USD\/99.99\" rel=\"nofollow\">http:\/\/localhost:8000\/USD\/99.99<\/a><\/em> and <em><a href=\"http:\/\/localhost:8000\/ABC\/1\" rel=\"nofollow\">http:\/\/localhost:8000\/ABC\/1<\/a><\/em> in the browser and see what happens. To stop and remove the container, run<\/p>\n<p>  <\/p>\n<pre><code class=\"bash\">$ docker rm --force microservice<\/code><\/pre>\n<p>  <\/p>\n<h2 id=\"github\">GitHub<\/h2>\n<p>  <\/p>\n<p>At last, we\u2019ll publish the microservice enabling GitHub Actions workflow, where each pull request or push to the repository triggers an execution of <em>microservice-test<\/em>. All you need is <em>docker-image.yml<\/em> file in <em>.github\/workflows directory<\/em>:<\/p>\n<p>  <\/p>\n<pre><code class=\"plaintext\">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)<\/code><\/pre>\n<p>  <\/p>\n<h2 id=\"conclusion\">Conclusion<\/h2>\n<p>  <\/p>\n<p>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.<\/p>\n<p>  <\/p>\n<p>Please contact me if you have any corrections or feedback.<\/p>\n<\/div>\n<\/div>\n<\/div>\n<p><!----><!----><\/div>\n<p><!----><!----><br \/> \u0441\u0441\u044b\u043b\u043a\u0430 \u043d\u0430 \u043e\u0440\u0438\u0433\u0438\u043d\u0430\u043b \u0441\u0442\u0430\u0442\u044c\u0438 <a href=\"https:\/\/habr.com\/ru\/articles\/512676\/\"> https:\/\/habr.com\/ru\/articles\/512676\/<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<div><!--[--><!--]--><\/div>\n<div id=\"post-content-body\">\n<div>\n<div class=\"article-formatted-body article-formatted-body article-formatted-body_version-1\">\n<div xmlns=\"http:\/\/www.w3.org\/1999\/xhtml\">\n<p>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.<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[],"tags":[],"class_list":["post-391714","post","type-post","status-publish","format-standard","hentry"],"_links":{"self":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/391714","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=391714"}],"version-history":[{"count":0,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=\/wp\/v2\/posts\/391714\/revisions"}],"wp:attachment":[{"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=391714"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=391714"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/savepearlharbor.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=391714"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}