Electra tranzakciók infrastruktúrája

Dr. Vermes Mátyás1

2002. november 9.

1.  Áttekintés
2.  Electra kötegek
3.  Köteg objektumok
4.  Kötegfogadás
5.  Feldolgozás
6.  Config objektumok
7.  Process metódus

1.  Áttekintés

Jelen dokumentum leírja az Electra tranzakciók feldolgozására kialakított környezetet. A leírás alapján reményeim szerint a programozók implementálni tudják az egyes tranzakciófajtákat.

Az Electrától az rpcelectra.exe XMLRPC szerver veszi át a tranzakciós kötegeket. A szerver a kötegeket ellenőrzés után letárolja az electra/elkoteg.dat filébe. Megjegyzendő, hogy az XMLRPC szerver sosem könyvel, hanem feladata csupán a kötegek adminisztrálása: átvétel, tárolás, jelentés a státuszokról.

A KELECTRA Kontó program figyeli az előbbi electra/elkoteg.dat-ban megjelenő kötegeket, és feldolgozza a bennük levő tételeket. A feldolgozás eredményét KELECTRA beteszi az electra/elnaplo.dat filébe, ahonnan az lekérdezhető.2

Az XMLRPC szerver is és a KELECTRA is a kötegeket objektumkönyvtárakon keresztül kezeli. Ez azt jelenti, hogy minden köteg, illetve tranzakciófajtához van egy objektumkönyvtár, amelyik az adott tranzakció adminisztrálását és lekönyvelését végzi. Jelenleg a következő Electra tranzakciós könyvtárak léteznek:

tran_bleord
bleord, bfeord (betét lekötés, felmondás, Thamo),
tran_devord
devord, dveord (deviza átutalás, átvezetés, Bacsa),
tran_huford
huford, hveord (forint átutalás, átvezetés, Vermes),
tran_inkord
inkord (inkasszó, Vermes),
tran_vibord
vibord (viber átutalás, Baffia),
ahol feltüntettem az egyes könyvtárak által kezelt kötegfajtákat, és a könyvtár karbantartójának nevét. A tran_* könyvtárakat a konto/devel/konto.rpc./rpcelectra-ban lehet megtalálni.

Az objektumkönyvtárak Linuxon shared libként (so) vannak megvalósítva. Ez lehetővé teszi, hogy az implementáció javítgatásakor ne kelljen mindent újrafordítani, újralinkelni, hanem elegendő a javított objektumkönyvtár binárisát (az so filét) átadni a banknak. Az objektum implementációk majdnem teljesen készen vannak, egyetlen (a legfontosabb) metódus hiányzik csak belőlük, a tételek tényleges feldolgozását végző process metódus:

A továbbiak háttérinformációt szolgáltatnak a hiányzó process metódusok megírásához.

2.  Electra kötegek

Minden előbb felsorolt tranzakcióhoz tartozik egy XML kötegformátum. A kötegformátumokat nem én találtam ki, hanem készen kaptam a Cardináltól. Példaként nézzük az inkasszók kötegformátumát:

<!ELEMENT INKORD  - - ( HD& ACCNO& DETAILS ) >
<!ELEMENT HD      - - ( OSN ) >
<!ELEMENT OSN     - O ( #PCDATA) -- A csomag egyedi azonositoja -->
<!ELEMENT ACCNO   - - ( ACC& CSN ) >
<!ELEMENT ACC     - O ( #PCDATA ) -- Terhelendo szamlaszam -->
<!ELEMENT CSN     - O ( #PCDATA ) -- A szamla devizaneme -->
<!ELEMENT DETAILS - - ( INK* )   >
<!ELEMENT INK     - - ( SQN?& DAC?& DNM?& DBN?& BSZ?& 
                        KZ1?& KZ2?& KZ3?& AP?& AMT? ) > 
<!ELEMENT SQN     - O ( #PCDATA ) -- Tetelsorszam a csomagon belul -->
<!ELEMENT DAC     - O ( #PCDATA ) -- Fizetesre kotelezett szamlaszama -->
<!ELEMENT DNM     - O ( #PCDATA ) -- Fizetesre kotelezett neve -->
<!ELEMENT DBN     - O ( #PCDATA ) -- Fizetesre kotelezett bankja -->
<!ELEMENT BSZ     - O ( #PCDATA ) -- Bizonylatszam -->
<!ELEMENT KZ1     - O ( #PCDATA ) -- Kozlemeny -->
<!ELEMENT KZ2     - O ( #PCDATA ) -- Kozlemeny -->
<!ELEMENT KZ3     - O ( #PCDATA ) -- Kozlemeny -->
<!ELEMENT AMT     - O ( #PCDATA ) -- Beszedendo osszeg ( 999999999999,00 )-->
<!ELEMENT IKA     - O ( #PCDATA ) -- Benyujtas inkoda: 
                                     'a'=fizeto bejelentese alapjan, 
                                     'e'=jogszabaly alapjan, 
                                     'z'=hataridos -->
<!ELEMENT IKK     - O ( #PCDATA ) -- Jogszabalyszam -->
<!ELEMENT IKH     - O ( #PCDATA ) -- Hataridos inkasszo hatarideje -->

A formátumokat ún. DTD-kben (Document Type Definition) adták. A DTD-k részleteivel nem kell foglalkoznunk, a lényeg a következő:

A többi tranzakciófajta ugyanilyen kötegben jön, csak a kötegtípus nevében, illetve a tételek mezőiben van eltérés. A tételekben levő <sqn> tag (kötegen belüli azonosító) egységesen mindenhol definiálva van.

3.  Köteg objektumok

Minden kötgetípushoz készítettem egy objektumot, ami az adott köteg adatait tudja tárolni. A kötegobjektumok közös őse az electrakoteg objektum, melynek definíciója:

*****************************************************************************
// electra köteg 
//
// Az osztály absztrakt (nincs definiálva electrakotegNew());
// a származtatott osztálynak definiálnia kell az alábbiakat:
//
//  xmlname     : a köteget azonosító tag neve
//  xmlitemname : a details-ban levő tagok neve
//  newitem     : csinál egy új (megfelelő osztályú) itemet

****************************************************************************
function electrakotegClass() 
static clid:=electrakotegRegister()  
    return clid

****************************************************************************
static function electrakotegRegister()   
local clid:=classRegister("electrakoteg",{objectClass()}) 
    classMethod(clid,"initialize",{|this,osn|electrakotegIni(this,osn)})
     
    classAttrib(clid,"osn")       // csomag azonosító
    classAttrib(clid,"acc")       // számlaszám
    classAttrib(clid,"csn")       // számla devizaneme
    classAttrib(clid,"details")   // tételek listája

    classMethod(clid,"additem",{|this,item|_electrakoteg_additem(this,item)}) 
    classMethod(clid,"obj2xml",{|this|_electrakoteg_obj2xml(this)}) //konverzió xml-re
    classMethod(clid,"xml2obj",{|this,xml,opt|_electrakoteg_xml2obj(this,xml,opt)}) //betöltés xml-ből
    classMethod(clid,"list",{|this,xml|_electrakoteg_list(this,xml)}) //listázás
    return clid

****************************************************************************
function electrakotegIni(this,osn) 
    objectIni(this)
    this:osn:=""
    this:acc:=""
    this:csn:=""
    this:details:={}
    if( osn!=NIL )
        this:osn:=osn
    end
    return this
*****************************************************************************

Látjuk, hogy a kötegobjektum

A konkrét tranzakciós kötegeket a fenti electrakoteg osztályból származtatjuk. Példaként nézzük az inkasszók esetét. Az electrainkord osztály kiegészíti electrakoteg-et néhány fajtaspecifikus metódussal.

****************************************************************************
function electrainkordClass() 
static clid:=electrainkordRegister()  
    return clid

****************************************************************************
static function electrainkordRegister()  
local clid:=classRegister("electrainkord",{electrakotegClass()}) 
    classMethod(clid,"initialize",{|this,osn|electrainkordIni(this,osn)})
    
    //a különféle típusú kötegek abban különböznek
    //hogy az alábbi metódusokat másképp definiálják
    
    classMethod(clid,"xmlname",{||"inkord"})
    classMethod(clid,"xmlitemname",{||"ink"})
    classMethod(clid,"newitem",{||electrainkorditemNew()})
    return clid

****************************************************************************
function electrainkordNew(osn) 
local clid:=electrainkordClass()
    return objectNew(clid):initialize(osn)

****************************************************************************
function electrainkordIni(this,osn) 
    electrakotegIni(this,osn)
    return this

*****************************************************************************

Végül definiálni kell az inkasszó tételeket tartalmazó objektumot, az electrainkroditem-et (ilyenek vannak a detailsben). Ennek pontosan olyan attribútumai vannak, mint a DTD-nek. Minden attribútum üresre van inicializálva, ami biztosítja, hogy a CCC-ben meghatározott típusa legyen.

****************************************************************************
function electrainkorditemClass() 
static clid:=electrainkorditemRegister()  
    return clid

****************************************************************************
static function electrainkorditemRegister()  
local clid:=classRegister("electrainkorditem",{objectClass()})
    classMethod(clid,"initialize",{|this|electrainkorditemIni(this)})
    classAttrib(clid,"sqn")  //csomagon belüli tételsorszám
    classAttrib(clid,"dac")  //fizetésre kötelezett számla
    classAttrib(clid,"dnm")  //fizetésre kötelezett neve
    classAttrib(clid,"dbn")  //fizetésre kötelezett bankja
    classAttrib(clid,"bsz")  //bizonylatszám
    classAttrib(clid,"kz1")  //közlemény
    classAttrib(clid,"kz2")  //közlemény
    classAttrib(clid,"kz3")  //közlemény
    classAttrib(clid,"amt")  //átutalt összeg
    classAttrib(clid,"ika")  //benyújtás indoka: a, e, z
    classAttrib(clid,"ikk")  //jogszabályszám
    classAttrib(clid,"ikh")  //határidős inkasszó határideje
 
    classMethod(clid,"obj2xml",{|this|_electrainkorditem_obj2xml(this)}) //konverzió xml-re
    classMethod(clid,"process",{|this,ktg|_electrainkorditem_process(this,ktg)}) 

    return clid

****************************************************************************
function electrainkorditemNew() 
local clid:=electrainkorditemClass()
    return objectNew(clid):initialize()

****************************************************************************
function electrainkorditemIni(this) 
    objectIni(this)
    this:sqn:=0
    this:dac:=""
    this:dnm:=""
    this:dbn:="" 
    this:bsz:="" 
    this:kz1:=""
    this:kz2:="" 
    this:kz3:="" 
    this:amt:=0
    this:ika:=""
    this:ikk:=""
    this:ikh:=ctod("")
    return this

*****************************************************************************

A tételobjektumok process metódusa fogja elvégezni a tétel tényleges Kontóbeli feldolgozását.

4.  Kötegfogadás

Az Electra az rpcelectra.exe XMLRPC szerver metódushívásaival adogatja a kötegeket. A szerver főprogramja a következő:

function main(port)

local server

    set printer to log-rpcelectra additive
    set printer on
    set console off

    alertblock({|t,a|xmlrpc_alert(t,a)})

    kontoini()
    
    server:=xmlrpcserverNew(port) 
    server:keepalive:=.t.
    //server:debug:=.t.
    //server:recover:=.f.

    server:addmethod("electra.getversion" ,{|sid|_electra_getversion(sid)})
    server:addmethod("electra.konyvnap"   ,{|sid|_electra_konyvnap(sid)})
    server:addmethod("electra.sysstate"   ,{|sid|_electra_sysstate(sid)})

    server:addmethod("electra.huford"     ,{|sid,data,chk|_electra_huford(sid,data,chk)})
    server:addmethod("electra.hveord"     ,{|sid,data,chk|_electra_hveord(sid,data,chk)})
    server:addmethod("electra.inkord"     ,{|sid,data,chk|_electra_inkord(sid,data,chk)})
    server:addmethod("electra.vibord"     ,{|sid,data,chk|_electra_vibord(sid,data,chk)})
    server:addmethod("electra.devord"     ,{|sid,data,chk|_electra_devord(sid,data,chk)})
    server:addmethod("electra.dveord"     ,{|sid,data,chk|_electra_dveord(sid,data,chk)})
    server:addmethod("electra.bleord"     ,{|sid,data,chk|_electra_bleord(sid,data,chk)})
    server:addmethod("electra.bfeord"     ,{|sid,data,chk|_electra_bfeord(sid,data,chk)})
 
    server:addmethod("electra.naplo"      ,{|sid,ktg|_electra_naplo(sid,ktg)})
 
    server:closeblock:={|s,r|xmlrpc_verifyconnection(s,r)}
    server:loopfreq:=5000
    server:loopblock:={||fflush()}
 
    xmlrpc_register(server,"electra",VERSION)
    server:loop

    return NIL

Nézzük példaként az inkord (inkasszó) köteg fogadását:

static function _electra_inkord(sid,data,chk)  
local o,s:=session_validate_sid(sid)    
    validate_checksum(data,chk)
    o:=electrainkordNew()
    o:xml2obj( base64_decode(data) )
    store(s,o)
    return NIL

A sid és a checksum ellenőrzése után létrehozunk egy üres electrainkord objektumot, amit feltöltünk a (base64 dekódolt) XML leírásából, majd letároljuk az objektumot:

static function store(session,ktg)
local e

    session:access(ktg:xmlname,ktg:acc)

    if( .t.==ELKOTEG:insert(ktg:osn) )
        ELKOTEG_TIPUS      := ktg:xmlname
        ELKOTEG_KOTEGID    := ktg:osn
        ELKOTEG_SZAMLASZAM := ktg:acc
        ELKOTEG_TETELSZAM  := len(ktg:details)
        ELKOTEG_DBMDATA    := ktg:obj2xml
        ELKOTEG_ERKEZETT   := dtos(date())+time()
        ELKOTEG:unlock
    else
        e:=errorNew()
        e:operation:="store "+ktg:xmlname
        e:description:="kötegazonosító nem egyedi"
        e:args:={ktg:xmlname, ktg:osn, ktg:acc}
        eval(errorblock(),e)
    end
    return NIL

Ezzel az XMLRPC szerver be is töltötte a fő faladatát.

5.  Feldolgozás

A tényleges feldolgozás a KELECTRA program feladata. Az alábbi process függvény az ELKOTEG adatbázis aktuális rekordjában tárolt köteg feldolgozását végzi.

static function process()

local allapot:=ELKOTEG_ALLAPOT 
local tipus, ktg, retry:=.f. 
local result, e, n

    if( allapot=="OK" )
        return NIL
    end

Ha a köteg állapota OK, akkor már fel van dolgozva, és szükségtelen újra feldolgozni. A feldolgozás alatti időre a köteg állapotát !-ra állítjuk, ha ilyen végállapotú köteget találunk, akkor annak a feldolgozása megszakadt.
    ELKOTEG_ALLAPOT:="!"
    ELKOTEG_VEGREHAJT:=dtos(date())+time() 
    ELKOTEG:commit
    
    begin sequence
    
        tipus:=lower(alltrim(ELKOTEG_TIPUS))
    
        if( tipus=="huford" )
            ktg:=electrahufordNew()
        elseif( tipus=="hveord" )
            ktg:=electrahveordNew()
        elseif( tipus=="inkord" )
            ktg:=electrainkordNew()
        elseif( tipus=="vibord" )
            ktg:=electravibordNew()
        elseif( tipus=="devord" )
            ktg:=electradevordNew()
        elseif( tipus=="dveord" )
            ktg:=electradveordNew()
        elseif( tipus=="bleord" )
            ktg:=electrableordNew()
        elseif( tipus=="bfeord" )
            ktg:=electrabfeordNew()
        else
            ELKOTEG_ALLAPOT:="XTIP"
            break()
        end

Létrehozunk egy megfelelő típusú köteg objektumot, és feltöltjük azt az XML leírásból.
        ktg:xml2obj( ELKOTEG_DBMDATA )
    
        for n:=1 to len( ktg:details )
        
            if( !empty(allapot) .and. ELNAPLO:seek({ktg:osn,ktg:details[n]:sqn}) )
                if( !ELNAPLO_ISMETELHET )
                    loop
                end
                ELNAPLO:rlock
            else
                trancount++
                ELNAPLO:append
                ELNAPLO_KOTEGID:=ktg:osn 
                ELNAPLO_SORSZAM:=ktg:details[n]:sqn 
                ELNAPLO_ISMETELHET:=.t.
                ELNAPLO:commit
            end

A köteg minden tételére meghívjuk a process metódust. A feldolgozás tranzakcióban történik.
            begin sequence
                tranBegin()
                result:=ktg:details[n]:process(ktg) 
                tranCommit()
            recover using result
                tranRollback()
            end sequence
            
            if( result:classname=="error" )
                e:=result
                result:=electraresultNew()
                result:state:="XERR"
                result:canretry:=.t.
                result:message:=_any2str(e)
            end

A feldolgozás eredményét, vagy az esetleges hibát rögzítjük a tételszintű naplóban.
            ELNAPLO_UGYSZAM    := result:itemid
            ELNAPLO_ALLAPOT    := result:state
            ELNAPLO_ISMETELHET := result:canretry 
            ELNAPLO_VEGREHAJT  := dtos(date())+time() 

            if( !empty(result:message) )
                ELNAPLO_DBMUZENET:=result:message 
            elseif( !empty(ELNAPLO_DBMUZENET) )
                ELNAPLO_DBMUZENET:=""
            end
            ELNAPLO:unlock
 
            retry:=retry.or.result:canretry
        next

        ELKOTEG_ALLAPOT:=if(retry,"AG","OK")  //AG, ha ismételhető

Beállítjuk a köteg státuszát.
    end sequence
    ELKOTEG:unlock
    return NIL

6.  Config objektumok

Az infrastruktúra részét képezi az electra.config filé, ami XML formátumban tárolja a tranzakciók kiegészítő adatait (tranzakciókódok, jelölőadatok, stb.), amiket az Electrában nem rögzítenek. Példaként bemutatom az inkasszónál használt konfigurációs objektumot.

function inkordconfigClass() 
static clid
    if( clid==NIL )
        clid:=classRegister("inkordconfig",{xmlconfigClass()})
        classMethod(clid,"initialize",{|this|inkordconfigIni(this)})
        classAttrib(clid,"jeloloadat")
        classAttrib(clid,"tranzkod")
    end
    return clid

function inkordconfigNew() 
local clid:=inkordconfigClass()
    return objectNew(clid):initialize()

static function inkordconfigIni(this) 
    xmlconfigIni(this)

    //default értékek
    this:tranzkod:="610"
    this:jeloloadat:="4815"
    return this

A konfigurációs objektumokat az xmlconfig-ból kell származtatni, ezzel öröklik azt a képességet, hogy automatikusan feltöltődjenek az electra.config filéből. A objektumban tetszőleges attribútumokat definiálhatunk, amiket a megfelelő default értékekkel kell inicializálni.

7.  Process metódus

Álljon itt példaként a devord tranzakció üres process metódusa.

*****************************************************************************
function _electradevorditem_process(this,ktg) 
    return process(this,ktg,devordconfig())  

*****************************************************************************
static function process(this,ktg,config) 

//tranzakcióban fut,
//sikertelen könyvelés esetén break-kel kell kilépni,
//break-ben egy electraresult-ot adunk vissza,
//ami az error osztály leszármazottja

local result:=electraresultNew()  
    
    result:operation    := "_electradevorditem_process"
    result:canretry     := .f.
    result:state        := "XIMP"
    result:message      := "nincs implementálva"
    result:description  := "nincs implementálva"
    break(result)

    return result
 
*****************************************************************************

A devordconfig() hívás adja konfigurációs objektumot (jelenleg az is üres). A metódus mindig ,,nincs implementálva'' hibaüzenetet ad. Teljesen kidolgozott példa található többek között a proc_inkord.prg modulban.


Jegyzetek:

1ComFirm BT.

2 Folyamatos vita tárgya, hogy mi legyen a hosszabb kifutású tranzakciók állapotának jelentésével. A KELECTRA csak a közvetlenül általa végzett művelet eredményét ismerheti, pl. lekönyvelt egy átutalást, és az sorbaállítódott, vagy sikeresen elindított egy inkasszót. A tranzakciók későbbi sorsáról azonban nincs információ, pl. a sorbanálló tételt menesztik, az inkasszót visszautasítják. Ennek a problémának a megoldására az rpcelectra-kelectra hatáskörében nincs mód.

3 A Cardinal eredetileg nagybetűs tagokat definiált, később azonban áttértünk kisbetűk használatára. A XML-ben a kis/nagybetűk különbözőnek számítanak.