Aquí podrá encontrar un conjunto de programas de ejemplo que le permitirán validar técnicamente las posibilidades de migración de su código mainframe a un arquitectura open.
El código se ha simplificado con el objetivo de hacerlo entendible por cualquier persona con unos conocimientos mínimos de programación.
Puede descargarse el código accediendo al siguiente proyecto en GitHub
¿Quiere hacer un piloto con sus programas?
Póngase en contacto con nosotros.
1 - Hello World
Convierta un programa COBOL en una API REST.
Cómo darle una nueva vida a su código COBOL, aprenda a construir APIs REST usando Go cgo.
Transforme una COPYBOOK en un mensaje proto.
Sustituya el CICS IMS por un moderno y eficiente mecanismo basado en RPC (HTTP/2, compresión, cifrado, etc.).
En este ejemplo vamos a implementar nuestro programa COBOL “Hello, World” como un servidor gRPC.
IDENTIFICATIONDIVISION.PROGRAM-ID.hello.ENVIRONMENTDIVISION.CONFIGURATIONSECTION.DATADIVISION.WORKING-STORAGESECTION. * Declare program variables
LINKAGESECTION. * Data to share with COBOL subroutines
01 RECORD-TYPE.05 INPUT-NAMEPIC X(10).05 OUTPUT-PARM.10 PARM1PIC X(07).10 PARM2PIC X(10).PROCEDUREDIVISIONUSINGRECORD-TYPE.MOVE "Hello,"TOPARM1.IFINPUT-NAMEISEQUALTO (SPACESORLOW-VALUES)MOVE "World"TOPARM2MOVE2 TORETURN-CODEELSE
MOVEINPUT-NAMETOPARM2MOVE0 TORETURN-CODEEND-IF.GOBACK.
A continuación crearemos la definición del mensaje proto que nos servirá para exponer la COPYBOOK del programa COBOL. Para ello cree un fichero con el nombre hello.proto en el directorio d8grpc/hello y copie el siguiente fichero.
syntax="proto3";optiongo_package="github.com/driver8soft/examples/d8grpc/hello";packagehello;// d8grpc hello service definition.
serviceD8grpc{// Sends a greeting
rpcHello(MsgReq)returns(MsgRes){}}// The request message containing the user's name.
messageMsgReq{stringhello_name=1;}// The response message containing the greetings
messageMsgRes{stringresponse=1;}
Los campos de la COPYBOOK COBOL:
INPUT-NAME
OUTPUT-PARM
Están definidos como tipo CHAR (con longitudes 10 y 17) y se convierten a string.
Para compilar el mensaje proto ejecute el siguiente comando
Vamos a crear el servidor gRPC que realizará la llamada a la subrutina COBOL, en este caso la llamada se realizará de manera dinámica. Cree el fichero main.go en el directorio d8grpc/hello_server y copie el siguiente fichero.
packagemain/*
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libcob.h>
#cgo CFLAGS: -I/opt/homebrew/Cellar/gnucobol/3.2/include
#cgo LDFLAGS: -L/opt/homebrew/Cellar/gnucobol/3.2/lib -lcob
static void* allocArgv(int argc) {
return malloc(sizeof(char *) * argc);
}
*/import"C"import("context""errors""flag""fmt""log""net""time""unsafe"pb"github.com/driver8soft/examples/d8grpc/hello""google.golang.org/grpc")var(port=flag.Int("port",50051,"The server port"))typeserverstruct{pb.UnimplementedD8GrpcServer}func(s*server)Hello(ctxcontext.Context,in*pb.MsgReq)(out*pb.MsgRes,errerror){start:=time.Now()// define argc, argv
c_argc:=C.int(1)c_argv:=(*[0xfff]*C.char)(C.allocArgv(c_argc))deferC.free(unsafe.Pointer(c_argv))c_argv[0]=C.CString(in.GetHelloName())// check COBOL program
n:=C.cob_resolve(C.CString("hello"))ifn==nil{err:=errors.New("COBOL: program not found")log.Println(err)return&pb.MsgRes{},err}//Call COBOL program
log.Println("INFO: program hello started")ret:=C.cob_call(C.CString("hello"),c_argc,(*unsafe.Pointer)(unsafe.Pointer(c_argv)))log.Printf("INFO: program hello return-code %v",ret)//COBOL COPYBOOK is converted to Go String using COPYBOOK length
output:=C.GoStringN(c_argv[0],27)elapsed:=time.Since(start)log.Printf("INFO: Hello elapsed time %s",elapsed)return&pb.MsgRes{Response:output[9:]},nil}funcmain(){flag.Parse()// d8 Initialize gnucobol
C.cob_init(C.int(0),nil)lis,err:=net.Listen("tcp",fmt.Sprintf(":%d",*port))iferr!=nil{log.Fatalf("ERROR: failed to listen: %v",err)}varopts[]grpc.ServerOptiongrpcServer:=grpc.NewServer(opts...)pb.RegisterD8GrpcServer(grpcServer,&server{})log.Printf("INFO: server listening at %v",lis.Addr())iferr:=grpcServer.Serve(lis);err!=nil{log.Fatalf("ERROR: failed to serve: %v",err)}}
Compile la subrutina COBOL mediante el siguiente comando. El resultado será un módulo (shared library) que podremos llamar de manera dinámica desde el servidor Go gRPC mediante cgo.
cobc -m hello.cbl
El fichero resultante (*.so, *.dylib) puede dejarse en el directorio d8grpc/hello_server
Si decide dejar el módulo COBOL en otro directorio recuerde definirlo (export COB_LIBRARY_PATH=/…my_library…/)
Abra un terminal y ejecute el servidor gRPC mediante el siguiente comando
go run .
Por último, crearemos un cliente Go para realizar la llamada a nuestro servicio gRPC COBOL. Cree el fichero main.go en el directorio d8grpc/hello_client y copie el siguiente fichero.
packagemainimport("context""flag""log"pb"github.com/driver8soft/examples/d8grpc/hello""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure")var(addr=flag.String("addr","localhost:50051","the address to connect to"))var(name=flag.String("name","","name"))funcmain(){flag.Parse()// Set up a connection to the server.
conn,err:=grpc.NewClient(*addr,grpc.WithTransportCredentials(insecure.NewCredentials()))iferr!=nil{log.Fatalf("did not connect: %v",err)}deferconn.Close()client:=pb.NewD8GrpcClient(conn)// Contact the server and print out its response.
r,err:=client.Hello(context.Background(),&pb.MsgReq{HelloName:*name})iferr==nil{log.Printf("Output: %s",r.GetResponse())}else{log.Printf("ERROR: %v",err)}}
Para probar nuestro servicio COBOL gRPC abra un nuevo terminal y ejecute el comando.
go run main.go -name=Hooper
3 - Playing with PostgreSQL
Un ejemplo COBOL PostgreSQL.
¿El lenguaje COBOL sólo puede acceder a DB2?
En este sencillo ejemplo accederemos a una base de datos PostgreSQL desde un programa COBOL.
Sus programas pueden ser pre-compilados (EXEC SQL) para acceder a distintas bases de datos SQL
Oracle Pro*Cobol
IBM DB2 Cobol precompiler
OpenESQL para PostgreSQL
Para poder ejecutar este programa es necesario instalar PostgreSQL y crear la base de datos de ejemplo (dvdrental). Puede encontrar las instrucciones de como hacerlo aquí.
*****************************************************************
* Connect and get data from PostgreSQL
* Sample DB "dvdrental" table "actor"
*****************************************************************
IDENTIFICATIONDIVISION.PROGRAM-ID.pgcobol.AUTHOR.DATADIVISION.WORKING-STORAGESECTION. * CONNECT TO POSGRESQL
01 CONN-STR.05 FILLER PIC X(20)VALUE "dbname=dvdrental ".05 FILLER PIC X(20)VALUE "user=XXXXXXXX ".05 FILLER PIC X(20)VALUE "password=XXXXXXX ".05 FILLER PIC X(20)VALUE "host=localhost ".05 FILLER PIC X(20)VALUE "port=5432 ".05 FILLER PIC X(20)VALUE "sslmode=disable ".05 FILLER PIC X(01)VALUELOW-VALUES.01 CONNECTIONUSAGEPOINTER.01 CONN-STATUSUSAGEBINARY-LONG. * DECLARE CURSOR
01 SQL-QUERY.05 SQL-QUERY-DATAPIC X(4096)VALUESPACES.05 FILLER PIC X(01)VALUELOW-VALUES.01 DB-CURSORUSAGEPOINTER. * SQL ERROR
01 SQL-STATUSUSAGEBINARY-LONG.01 SQL-ERROR-PTRUSAGEPOINTER.01 SQL-ERROR-STRPIC X(4096)BASED.01 SQL-ERROR-MSGPIC X(100)VALUESPACES. * COUNTER
01 ROW-COUNTERUSAGEBINARY-LONG.01 COLUMN-COUNTERUSAGEBINARY-LONG. * FETCH
01 RESULT-PTRUSAGEPOINTER.01 RESULT-STRPIC X(4096)BASED.01 RESULT-DATAPIC X(4096)VALUESPACES.01 TABLE-ROW.02 actor_idPIC 9(4)VALUEZEROS.02 first_namePIC X(45)VALUESPACES.02 last_namePIC X(45)VALUESPACES.02 last_updatePIC X(22)VALUESPACES. * AUX VARIABLES
01 DB-ROWPIC 9(7)VALUEZEROS.01 DB-COLUMNPIC 9(3)VALUEZEROS. *> *********************************************************************
PROCEDUREDIVISION.PERFORMCONNECT-DB.MOVE "SELECT actor_id, first_name, "&"last_name, last_update "&"FROM actor;"TOSQL-QUERY-DATA.PERFORMDECLARE-CURSOR.PERFORMROW-COUNT.PERFORMCOLUMN-COUNT. * ITERATE OVER ROWS
PERFORMVARYINGDB-ROWFROM0 BY1
UNTILDB-ROW>=ROW-COUNTERPERFORMVARYINGDB-COLUMNFROM0 BY1
UNTILDB-COLUMN>=COLUMN-COUNTERPERFORMROW-FETCHEND-PERFORM
DISPLAYactor_id" - "first_name" - "last_name" - "last_updateEND-PERFORM.PERFORMDISCONNECT.GOBACK. *
CONNECT-DB. * CONNECT AND CHECK DB STATUS
CALL "PQconnectdb"USINGCONN-STRRETURNINGCONNECTION.CALL "PQstatus"USINGBYVALUECONNECTIONRETURNINGCONN-STATUS.IFCONN-STATUSNOTEQUAL0 THEN
DISPLAY "Connection error! "CONN-STATUSSTOPRUN
END-IF.DISCONNECT. * CLOSE CONNECTION DB
CALL "PQfinish"USINGBYVALUECONNECTIONRETURNINGOMITTED.DECLARE-CURSOR. * OPEN CURSOR
CALL "PQexec"USINGBYVALUECONNECTIONBYREFERENCESQL-QUERYRETURNINGDB-CURSOREND-CALL.CALL "PQresultStatus"USINGBYVALUEDB-CURSORRETURNINGSQL-STATUS.CALL "PQresStatus"USINGBYVALUESQL-STATUSRETURNINGSQL-ERROR-PTR.SETADDRESSOFSQL-ERROR-STRTOSQL-ERROR-PTR.STRINGSQL-ERROR-STRDELIMITEDBYx"00"INTOSQL-ERROR-MSGEND-STRING.IFSQL-STATUSNOTEQUAL2 THEN
DISPLAY "Open Cursor error! "SQL-STATUSSQL-ERROR-MSGSTOPRUN
END-IF.DISPLAY "sql_status: "SQL-STATUS" sql_error: "SQL-ERROR-MSG.ROW-COUNT. * GET NUMBER OF ROWS
CALL "PQntuples"USINGBYVALUEDB-CURSORRETURNINGROW-COUNTER.DISPLAY "number of rows: "ROW-COUNTER.COLUMN-COUNT. * GET NUMBER OF COLUMNS
CALL "PQnfields"USINGBYVALUEDB-CURSORRETURNINGCOLUMN-COUNTER.DISPLAY "number of fields: "COLUMN-COUNTER.ROW-FETCH. *> FETCH
CALL "PQgetvalue"USINGBYVALUEDB-CURSORBYVALUEDB-ROWBYVALUEDB-COLUMNRETURNINGRESULT-PTREND-CALL
SETADDRESSOFRESULT-STRTORESULT-PTRINITIALIZERESULT-DATA.STRINGRESULT-STRDELIMITEDBYx"00"INTORESULT-DATAEND-STRING.EVALUATEDB-COLUMNWHEN0
MOVERESULT-DATATOactor_idWHEN1
MOVERESULT-DATATOfirst_nameWHEN2
MOVERESULT-DATATOlast_nameWHEN3
MOVERESULT-DATATOlast_updateEND-EVALUATE.
Recuerde modificar los campos de WORKING CONN-STR con un usuario y password válidos para la conexión a la base de datos
Las funciones utilizadas por el programa COBOL necesitan la librería de postgreSQL “libpq”, localice donde está instalada dicha librería y añadala en el momento de compilar el programa, por ejemplo:
De manera equivalente al mecanismo del CICS para llamar a programas remotos (EXEC CICS LINK), realice llamadas entre programas COBOL desplegados en distintos contenedores.
A continuación se describe gráficamente el flujo de ejecución
En el directorio link definiremos nuestro mensaje proto (link.proto)
syntax="proto3";optiongo_package="github.com/driver8soft/examples/d8link/link";packagelink;// The Link service definition.
serviceLinkService{rpcCommArea(CommReq)returns(CommResp){}}// The request message containing program to link, commarea and commarea length.
messageCommReq{stringlink_prog=1;int32comm_len=2;bytesinput_msg=3;}// The response message containing commarea
messageCommResp{bytesoutput_msg=1;}
A continuación crearemos el programa d8link.go sobre el directorio link_client
packagemain/*
#include <string.h>
#include <stdlib.h>
*/import"C"import("context""flag""log""unsafe"pb"github.com/driver8soft/examples/d8link/link""google.golang.org/grpc""google.golang.org/grpc/credentials/insecure")var(addr=flag.String("addr","localhost:50051","the address to connect to"))//export D8link
funcD8link(c_program*C.char,c_commarea*C.char,c_commlen*C.int)C.int{flag.Parse()// C variables to Go variables
program:=C.GoStringN(c_program,8)// max length of COBOL mainframe program = 8
commarea:=C.GoBytes(unsafe.Pointer(c_commarea),*c_commlen)commlen:=int32(*c_commlen)log.Println("INFO: Call program -",program)// Set up a connection to the server.
conn,err:=grpc.NewClient(*addr,grpc.WithTransportCredentials(insecure.NewCredentials()))iferr!=nil{log.Fatalf("did not connect: %v",err)}deferconn.Close()client:=pb.NewLinkServiceClient(conn)// Contact the server
r,err:=client.CommArea(context.Background(),&pb.CommReq{LinkProg:program,CommLen:commlen,InputMsg:commarea})iferr!=nil{log.Fatalf("ERROR: calling program - %s - %v",program,err)}outMsg:=r.GetOutputMsg()C.memcpy(unsafe.Pointer(c_commarea),unsafe.Pointer(&outMsg[0]),C.size_t(commlen))return0}funcmain(){}
Vamos a exportar la función D8link para que pueda ser llamada desde un programa COBOL, para ello es necesario compilarla utilizando la opción c-shared de Go
El compilador de Go generará un objeto (D8link.dylib D8link.so) y un fichero (D8link.h) que serán llamados dinámicamente desde el código COBOL
Y para finalizar crearemos el servidor gRPC (main.go) en el directorio link_server que será el encargado de recibir el mensaje proto y llamar al programa COBOL destino.
packagemain/*
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libcob.h>
#cgo CFLAGS: -I/opt/homebrew/Cellar/gnucobol/3.2/include
#cgo LDFLAGS: -L/opt/homebrew/Cellar/gnucobol/3.2/lib -lcob
static void* allocArgv(int argc) {
return malloc(sizeof(char *) * argc);
}
*/import"C"import("context""flag""fmt""log""net""strings""time""unsafe"pb"github.com/driver8soft/examples/d8link/link""google.golang.org/grpc")var(port=flag.Int("port",50051,"The server port"))typeserverstruct{pb.UnimplementedLinkServiceServer}func(s*server)CommArea(ctxcontext.Context,in*pb.CommReq)(out*pb.CommResp,errerror){start:=time.Now()// remove trailing spaces from program name
program:=strings.TrimSpace(in.GetLinkProg())c_program:=C.CString(program)deferC.free(unsafe.Pointer(c_program))c_commlen:=C.int(in.GetCommLen())// allocate argc & argv variables
c_argc:=C.int(1)c_argv:=(*[0xfff]*C.char)(C.allocArgv(c_argc))deferC.free(unsafe.Pointer(c_argv))c_argv[0]=C.CString(string(in.GetInputMsg()))deferC.free(unsafe.Pointer(c_argv[0]))// check COBOL program
n:=C.cob_resolve(c_program)ifn==nil{log.Println("ERROR: Module not found. Program name =",program)}else{log.Printf("INFO: %s started",program)ret:=C.cob_call(c_program,c_argc,(*unsafe.Pointer)(unsafe.Pointer(c_argv)))log.Printf("INFO: %s return-code %v",program,ret)}c_msg_output:=C.GoStringN(c_argv[0],c_commlen)elapsed:=time.Since(start)log.Printf("INFO: %s elapsed time %s",program,elapsed)return&pb.CommResp{OutputMsg:[]byte(c_msg_output)},nil}funcmain(){flag.Parse()// d8 Initialize gnucobol
C.cob_init(C.int(0),nil)lis,err:=net.Listen("tcp",fmt.Sprintf(":%d",*port))iferr!=nil{log.Fatalf("ERROR: failed to listen: %v",err)}grpcServer:=grpc.NewServer()pb.RegisterLinkServiceServer(grpcServer,&server{})log.Printf("INFO: server listening at %v",lis.Addr())iferr:=grpcServer.Serve(lis);err!=nil{log.Fatalf("ERROR: failed to serve: %v",err)}}
Pruebe a realizar llamadas remotas entre programas COBOL intercambiando un área de datos (COPYBOOK).
Para ello recuerde que:
El programa llamador debe compilarse para generar un ejecutable (opción -x GNUCobol)
El programa llamado debe compilarse para generar un objeto (opción -m GNUCobol)
Ambos programas deben compilarse utilizando el mismo “byteorder” para compartir datos binarios
Para simplificar la prueba los programas COBOL puede residir en los directorios definidos anteriormente (link_client link_server)
Puede utilizar los programas COBOL de ejemplo loanmain.cbl y loancalc.cbl.
******************************************************************
*
* Loan Calculator Main Program
* ==========================
*
* A sample program to demonstrate how to create a gRPC COBOL
* microservice.
*
******************************************************************
IDENTIFICATIONDIVISION.PROGRAM-ID.loanmain.ENVIRONMENTDIVISION.CONFIGURATIONSECTION.DATADIVISION.FILESECTION.WORKING-STORAGESECTION. * Declare program variables
01 PROG-NAMEPIC X(8)VALUE "loancalc".01 COMMLENPIC 9(9)COMP.01 COMMAREA.05 INPUT-MSG.10 PRIN-AMTPIC S9(7) USAGEISDISPLAY.10 INT-RATEPIC S9(2)V9(2)USAGEISDISPLAY.10 TIMEYRPIC S9(2) USAGEISDISPLAY.05 OUTPUT-MSG.10 PAYMENTPIC S9(7)V9(2)USAGEISDISPLAY.10 ERROR-MSGPIC X(20).PROCEDUREDIVISION. * code goes here!
INITIALIZECOMMAREA.DISPLAY "Compound Interest Calculator"DISPLAY "Principal amount: "WITHNOADVANCING.ACCEPTPRIN-AMT.DISPLAY "Interest rate: "WITHNOADVANCING.ACCEPTINT-RATE.DISPLAY "Number of years: "WITHNOADVANCING.ACCEPTTIMEYR.COMPUTECOMMLEN=LENGTHOFCOMMAREA.CALL "D8link"USINGPROG-NAMECOMMAREACOMMLEN.DISPLAY "Error Msg: "ERROR-MSG.DISPLAY "Couta: "PAYMENT.GOBACK.
******************************************************************
*
* Loan Calculator Subroutine
* ==========================
*
* A sample program to demonstrate how to create a gRPC COBOL
* microservice.
*
******************************************************************
IDENTIFICATIONDIVISION.PROGRAM-ID.loancalc.ENVIRONMENTDIVISION.CONFIGURATIONSECTION.DATADIVISION.FILESECTION.WORKING-STORAGESECTION. * Declare program variables
01 WS-MSG.05 WS-ERRORPIC X(01).05 WS-MSG00PIC X(20)VALUE 'OK'.05 WS-MSG10PIC X(20)VALUE 'INVALID INT. RATE'.05 WS-MSG12PIC X(20)VALUE 'INVALID NUMBER YEARS'.01 AUX-VARS.05 MONTHLY-RATEUSAGEISCOMP-2.05 AUX-XUSAGEISCOMP-2.05 AUX-YUSAGEISCOMP-2.05 AUX-ZUSAGEISCOMP-2.LINKAGESECTION. * Data to share with COBOL subroutines
01 LOAN-PARAMS.05 INPUT-MSG.10 PRIN-AMTPIC S9(7) USAGEISDISPLAY.10 INT-RATEPIC S9(2)V9(2)USAGEISDISPLAY.10 TIMEYRPIC S9(2) USAGEISDISPLAY.05 OUTPUT-MSG.10 PAYMENTPIC S9(7)V9(2)USAGEISDISPLAY.10 ERROR-MSGPIC X(20).PROCEDUREDIVISIONUSINGBYREFERENCELOAN-PARAMS. * code goes here!
000-MAIN.MOVE "N"TOWS-ERROR.DISPLAY "PRIN-AMT: "PRIN-AMT.DISPLAY "INT-RATE: "INT-RATE.DISPLAY "TIMEYR: "TIMEYR.PERFORM100-INIT.IFWS-ERROR='N'PERFORM200-PROCESSEND-IF.PERFORM300-WRAPUP.100-INIT.IFINT-RATE<=0
MOVEWS-MSG10TOERROR-MSGMOVE10 TORETURN-CODEMOVE 'Y'TOWS-ERRORELSE
IFTIMEYR<=0
MOVEWS-MSG12TOERROR-MSGMOVE12 TORETURN-CODEMOVE 'Y'TOWS-ERROREND-IF
END-IF.200-PROCESS.INITIALIZEAUX-VARS.COMPUTEMONTHLY-RATE=(INT-RATE/12 /100).COMPUTEAUX-X=((1 +MONTHLY-RATE)**(TIMEYR*12)).COMPUTEAUX-Y=AUX-X*MONTHLY-RATE.COMPUTEAUX-Z=(AUX-X-1)/AUX-Y.COMPUTEPAYMENT=PRIN-AMT/AUX-Z.MOVEWS-MSG00TOERROR-MSG.MOVE0 TORETURN-CODE.DISPLAY "PAYMENT: "PAYMENT.DISPLAY "ERROR-MSG: "ERROR-MSG.300-WRAPUP.GOBACK.
5 - COBOL & Kafka
Convierta su programa COBOL en un Kafka consumer/producer.
Quiere integrar sus programas COBOL en un modelo de proceso basado en eventos.
Aprenda cómo convertir un programa COBOL en un Kafka consumer / producer.
Desde el programa COBOL realizaremos una llamada al módulo D8kafka pasándole:
El topic Kafka
Una lista de valores (key : value) separados por comas
A continuación ejecutaremos un programa batch de lectura/escritura de ficheros usando esta configuración.
El programa principal bcuota.cbl lee un fichero de entrada, llama a la rutina COBOL loancalc.cbl para calcular la cuota a pagar de un préstamo y escribe el resultado en el fichero de salida.
Compile ambos programas para generar una librería compartida (*.so, *dylib)
cobc -m bcouta.cbl loancalc.cbl
El controlador d8parti será el encargado de reemplazar al JES, a continuación se muestra una versión simplificada de dicho módulo, cree un fichero d8parti.go y copie el siguiente código.
packagemain/*
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libcob.h>
#cgo CFLAGS: -I/opt/homebrew/Cellar/gnucobol/3.2/include
#cgo LDFLAGS: -L/opt/homebrew/Cellar/gnucobol/3.2/lib -lcob
*/import"C"import("fmt""log""os""time""unsafe""github.com/spf13/viper")typestepstruct{Stepnamestring`mapstructure:"stepname"`ExecexecDd[]dd}typeexecstruct{Pgmstring`mapstructure:"pgm"`}typeddstruct{Namestring`mapstructure:"name"`Dsnstring`mapstructure:"dsn"`Dispstring`mapstructure:"disp"`Normaldispstring`mapstructure:"normaldisp"`Abnormaldispstring`mapstructure:"abnormaldisp"`}varStep*stepfuncconfig()error{// Read yaml config file
viper.SetConfigName("step")viper.SetConfigType("yaml")viper.AddConfigPath(".")iferr:=viper.ReadInConfig();err!=nil{returnerr}// Unmarshal yaml config file
iferr:=viper.Unmarshal(&Step);err!=nil{returnerr}// Create Symlink
fori:=0;i<len(Step.Dd);i++{err:=os.Symlink(Step.Dd[i].Dsn,Step.Dd[i].Name)iferr!=nil{switch{caseos.IsExist(err):// DDNAME already exist
log.Printf("INFO: DDNAME=%s already exists. %s",Step.Dd[i].Name,err)caseos.IsNotExist(err):// DDNAME invalid
log.Printf("ERROR: DDNAME=%s invalid ddname. %s",Step.Dd[i].Name,err)returnerrdefault:log.Println(err)returnerr}}}returnnil}funccobCall(pstring)error{deferdelSymlink()c_progName:=C.CString(p)deferC.free(unsafe.Pointer(c_progName))n:=C.cob_resolve(c_progName)ifn==nil{returnfmt.Errorf("ERROR: Program %s not found",p)}else{log.Printf("INFO: PGM=%s started",p)r:=C.cob_call_with_exception_check(c_progName,C.int(0),nil)rc:=int(C.cob_last_exit_code())err:=C.GoString(C.cob_last_runtime_error())switchint(r){case0:log.Printf("INFO: program %s exited with return-code: %v",p,rc)C.cob_tidy()case1:log.Printf("INFO: program %s STOP RUN with return-code: %v",p,rc)case-1:returnfmt.Errorf("ERROR: program %s exit with return-code: %v and error: %s",p,rc,err)case-2:returnfmt.Errorf("FATAL: program %s exit with return-code: %v and error: %s",p,rc,err)case-3:returnfmt.Errorf("ERROR: program %s signal handler exit with signal: %v and error: %s",p,rc,err)default:returnfmt.Errorf("ERROR: program %s unexpected return exit code: %v and error: %s",p,rc,err)}returnnil}}funcdelSymlink(){fori:=0;i<len(Step.Dd);i++{err:=os.Remove(Step.Dd[i].Name)iferr!=nil{log.Printf("INFO: DDNAME=%s does not exists. %s",Step.Dd[i].Name,err)}}}funcmain(){start:=time.Now()// Initialize gnucobol
C.cob_init(C.int(0),nil)log.Println("INFO: gnucobol initialized")// Load config file
iferr:=config();err!=nil{log.Printf("ERROR: reading yaml config file. %s",err)os.Exit(12)}// Call COBOL program -> EXEC PGM defined in JCL
iferr:=cobCall(Step.Exec.Pgm);err!=nil{log.Println(err)os.Exit(12)}elapsed:=time.Since(start)log.Printf("INFO: %s elapsed time %s",Step.Exec.Pgm,elapsed)}
Para ejecutar el programa COBOL batch de pruebas, simplemente abra una consola y ejecute lo siguiente:
go run d8parti.go
¿Como crear un fichero de entrada de ejemplo (infile)?
Consta de un número de cuenta (10 bytes), un importe (7 bytes), un tipo de interés (4 bytes con dos posiciones decimales) y un periodo en años (2 bytes). Los campos se delimitan mediante un separador (FILLER 1 byte) para facilitar la lectura del fichero de entrada.
Puede utilizar el siguiente programa de ejemplo para generar el fichero de entrada.
packagemainimport("flag""fmt""math/rand""os""strconv""time")varr1*rand.Randvar(rows=flag.Int("rows",1000,"number of rows to generate"))var(file=flag.String("file","test.txt","input file name"))funcmain(){flag.Parse()s1:=rand.NewSource(time.Now().UnixNano())r1=rand.New(s1)f,err:=os.Create(*file)iferr!=nil{fmt.Println(err)return}fori:=0;i!=*rows;i++{output:=account(i)+"-"+amount()+"-"+interest()+"-"+yearsPending()+"\n"_,err:=f.WriteString(output)iferr!=nil{fmt.Println(err)f.Close()return}}err=f.Close()iferr!=nil{fmt.Println(err)return}}funcaccount(idint)string{return"id:"+fmt.Sprintf("%07d",id+1)}funcamount()string{min:=1000max:=1000000a:=strconv.Itoa(r1.Intn(max-min+1)+min)fori:=len(a);i!=7;i++{a="0"+a}returna}funcinterest()string{return"0450"}funcyearsPending()string{min:=5max:=25y:=strconv.Itoa(r1.Intn(max-min+1)+min)iflen(y)<2{y="0"+y}returny}
7 - COBOL to Go
Convierta el código COBOL a Go.
Los avances en IA Gen permiten vislumbrar un futuro en el que la conversión de código entre distintos lenguajes de programación pueda realizarse de manera automática y transparente.
Sin embargo, deben tenerse en cuenta las características del lenguaje COBOL para seleccionar una opción que permita reconocer el código convertido de forma que pueda seguir siendo mantenido por el equipo responsable.
Vamos a utilizar la rutina COBOL de ejemplo que calcula la cuota de un préstamo.
******************************************************************
*
* Loan Calculator Subroutine
* ==========================
*
* A sample program to demonstrate how to create a gRPC COBOL
* microservice.
*
******************************************************************
IDENTIFICATIONDIVISION.PROGRAM-ID.loancalc.ENVIRONMENTDIVISION.CONFIGURATIONSECTION.DATADIVISION.FILESECTION.WORKING-STORAGESECTION. * Declare program variables
01 WS-MSG.05 WS-ERRORPIC X(01).05 WS-MSG00PIC X(20)VALUE 'OK'.05 WS-MSG10PIC X(20)VALUE 'INVALID INT. RATE'.05 WS-MSG12PIC X(20)VALUE 'INVALID NUMBER YEARS'.01 AUX-VARS.05 MONTHLY-RATEUSAGEISCOMP-2.05 AUX-XUSAGEISCOMP-2.05 AUX-YUSAGEISCOMP-2.05 AUX-ZUSAGEISCOMP-2.LINKAGESECTION. * Data to share with COBOL subroutines
01 LOAN-PARAMS.05 INPUT-MSG.10 PRIN-AMTPIC S9(7) USAGEISDISPLAY.10 INT-RATEPIC S9(2)V9(2)USAGEISDISPLAY.10 TIMEYRPIC S9(2) USAGEISDISPLAY.05 OUTPUT-MSG.10 PAYMENTPIC S9(7)V9(2)USAGEISDISPLAY.10 ERROR-MSGPIC X(20).PROCEDUREDIVISIONUSINGBYREFERENCELOAN-PARAMS. * code goes here!
000-MAIN.MOVE "N"TOWS-ERROR.DISPLAY "PRIN-AMT: "PRIN-AMT.DISPLAY "INT-RATE: "INT-RATE.DISPLAY "TIMEYR: "TIMEYR.PERFORM100-INIT.IFWS-ERROR='N'PERFORM200-PROCESSEND-IF.PERFORM300-WRAPUP.100-INIT.IFINT-RATE<=0
MOVEWS-MSG10TOERROR-MSGMOVE10 TORETURN-CODEMOVE 'Y'TOWS-ERRORELSE
IFTIMEYR<=0
MOVEWS-MSG12TOERROR-MSGMOVE12 TORETURN-CODEMOVE 'Y'TOWS-ERROREND-IF
END-IF.200-PROCESS.INITIALIZEAUX-VARS.COMPUTEMONTHLY-RATE=(INT-RATE/12 /100).COMPUTEAUX-X=((1 +MONTHLY-RATE)**(TIMEYR*12)).COMPUTEAUX-Y=AUX-X*MONTHLY-RATE.COMPUTEAUX-Z=(AUX-X-1)/AUX-Y.COMPUTEPAYMENT=PRIN-AMT/AUX-Z.MOVEWS-MSG00TOERROR-MSG.MOVE0 TORETURN-CODE.DISPLAY "PAYMENT: "PAYMENT.DISPLAY "ERROR-MSG: "ERROR-MSG.300-WRAPUP.GOBACK.
Una primera aproximación pasa por mantener la estructura del código COBOL:
Una subrutina COBOL equivale a una función en Go
Las variables definidas en la WORKING-STORAGE se agrupan y transforman en variables Go
El código de la PROCEDUCE DIVISION se compone de uno o varias secciones (PARAGRAPHS), estas se pueden transformar a su vez en funciones muy sencillas
Por último, las variables de la LINKAGE SECTION definen los parámetros de la función principal y se comparten (puntero) entre todas las funciones
// Declare variables in the working storage section
var(WS_ERRORstringWS_MSG00string="OK"WS_MSG10string="INVALID INT. RATE"WS_MSG12string="INVALID NUMBER YEARS"MONTHLY_RATEfloat64AUX_Xfloat64AUX_Yfloat64AUX_Zfloat64)// Data to share with COBOL subroutines
typeLoanParamsstruct{PrinAmtfloat64IntRatefloat64TimeYrint32Paymentfloat64ErrorMsgstring}funcloancalc(amountfloat64,interestfloat64,nyearsint32)(paymentfloat64,errmsgstring){WS_ERROR="N"loanParams:=LoanParams{PrinAmt:amount,IntRate:interest,TimeYr:nyears,}fmt.Println("PRIN-AMT:",loanParams.PrinAmt)fmt.Println("INT-RATE:",loanParams.IntRate)fmt.Println("TIMEYR:",loanParams.TimeYr)initial(&loanParams)ifWS_ERROR=="N"{process(&loanParams)}wrapup(&loanParams)returnloanParams.Payment,loanParams.ErrorMsg}funcinitial(loanParams*LoanParams){ifloanParams.IntRate<=0{loanParams.ErrorMsg=WS_MSG10WS_ERROR="Y"}else{ifloanParams.TimeYr<=0{loanParams.ErrorMsg=WS_MSG12WS_ERROR="Y"}}}funcprocess(loanParams*LoanParams){MONTHLY_RATE=loanParams.IntRate/12/100AUX_X=math.Pow((1+MONTHLY_RATE),float64(loanParams.TimeYr*12))AUX_Y=AUX_X*MONTHLY_RATEAUX_Z=(AUX_X-1)/AUX_YloanParams.Payment=loanParams.PrinAmt/AUX_ZloanParams.ErrorMsg=WS_MSG00}funcwrapup(loanParams*LoanParams){fmt.Println("PAYMENT:",loanParams.Payment)fmt.Println("ERROR-MSG:",loanParams.ErrorMsg)}
Hay que recordar que el código COBOL ya ha sido expuesto mediante una interfaz estándar que define los parámetros de entrada/salida de la función (por ejemplo, mediante un mensaje proto).
Utilizando la definición de dicha interfaz, es posible volver a refactorizar el código simplificando el resultado final.
funcloancalc(amount,interestfloat64,nyearsint32)(paymentfloat64,errmsgstring){ifinterest<=0{return0,"Invalid int. rate"}ifnyears<=0{return0,"Invalid number of years"}monthlyRate:=(interest/12/100)x:=math.Pow((1+monthlyRate),float64(nyears*12))y:=x*monthlyRatepayment=amount/((x-1)/y)returnpayment,"OK"}
8 - Python
Desea utilizar Python
La tecnología gRPC nos permite conectar programas escritos en distintos lenguajes de programación de manera sencilla.
En este ejemplo, crearemos un cliente Python para llamar a nuestro servicio gRPC COBOL (hello.cbl).
Para ello primero necesitaremos compilar el mensaje proto para el lenguaje Python.
syntax="proto3";optiongo_package="github.com/driver8soft/examples/d8grpc/hello";packagehello;// d8grpc hello service definition.
serviceD8grpc{// Sends a greeting
rpcHello(MsgReq)returns(MsgRes){}}// The request message containing the user's name.
messageMsgReq{stringhello_name=1;}// The response message containing the greetings
messageMsgRes{stringresponse=1;}
Instale el compilador correspondiente al lenguaje Python y ejecute el siguiente comando
La compilación del fichero proto creará los stubs necesarios para nuestro cliente Python
hello_pb2.py
hello_pb2_grpc.py
A continuación crearemos un cliente Python gRPC, cree un fichero client.py y copie el siguiente código.
importgrpcimporthello_pb2importhello_pb2_grpcdefrun(inputname):withgrpc.insecure_channel('localhost:50051')aschannel:stub=hello_pb2_grpc.D8grpcStub(channel)r=stub.Hello(hello_pb2.MsgReq(hello_name=inputname))print(f"Result: {r.response}")if__name__=='__main__':# Get user Input inputname=input("Please enter name: ")run(inputname)
Para probar el nuevo cliente Python, abra un terminal y ejecute