Eating our own dogfood
I really like the TransactionManager, it allows me to clearly define a transaction as if it was processed in an assembly line, with each small reusable participant doing its own little part, and the TransactionManager giving useful profiling information.
But now we live in a RESTful world, we need to implement RESTful based services here and there.
A typical REST call implementation looks like this:
@Path("/customers/{customer_id}/wallets/{wallet_id}/credit")
public class WalletCredit extends WalletCreditDebitSupport {
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response credit (
@Context UriInfo uriInfo,
@PathParam("customer_id") String customerId,
@PathParam("wallet_id") String walletId,
@FormParam("rrn") String rrn,
@FormParam("detail") String detail,
@FormParam("amount") BigDecimal amount,
@FormParam("currency") String currency)
throws IOException, URISyntaxException, BLException, ISOException
{
…
…
…
}
}
so the first reaction is to just start coding the business logic as part of the body of that method. You need to:
- sanity check the parameters
- open a database connection
- create a TranLog record
- Validate the Customer, Account, etc.
- create a GL Transaction
- do some audit logging
- commit the JDBC transaction
This of course work fine, but hey, we already have code that does that:
- We have a standard transaction
Open
participant - We usually have a
CreateTranLog
participant - We have a
CheckCustomer
participant andCheckAccount
- We have participants that generate GLTransactions
- And of course, the
Close
andDebug
participants
Problem with a traditional implementation inside that body is that we need to reinvent the wheel, repeat code, add custom profiling or otherwise we wouldn't know which operations are fast and which ones are slow.
The solution was to eat our own dog food and use the TransactionManager.
So in this jCard case I've created a rest_txnmgr.xml that looks like this:
<!DOCTYPE txnmgr [
<!ENTITY PAN_PATTERN "^[\d]{10}$">
<!ENTITY AMOUNT_PATTERN "[+-]?(?:\d+(?:\.\d*)?|\.\d+)(?:[eE][+-]?\d+)?">
<!ENTITY CURRENCY_PATTERN "^\d{1,4}">
<!ENTITY RRN_PATTERN "^\d{1,12}">
<!ENTITY DETAIL_PATTERN "^.{0,255}$">
<!ENTITY WALLET_PATTERN "^[\d]{1,64}$">
<!ENTITY TEXT50_PATTERN "^[\w\s.\-\']{0,50}$">
...
...
]>
<txnmgr name='rest-txnmgr' class="org.jpos.transaction.TransactionManager" logger="Q2" realm='rest-txnmgr'>
<property name="queue" value="JCARD.RESTAPI.TXN" />
<property name="sessions" value="2" />
<property name="max-sessions" value="64" />
<property name="debug" value="true" />
<participant class="org.jpos.jcard.PrepareContext" logger="Q2" realm="prepare-context" />
<participant class="org.jpos.jcard.Switch" logger="Q2" realm="Switch">
<property name="WalletCreate" value="walletcreate close trigger-response" />
<property name="WalletCredit" value="walletcredit close trigger-response" />
<property name="WalletDebit" value="walletdebit close trigger-response" />
<property name="BalanceInquiry" value="balance-inquiry close trigger-response" />
<property name="MiniStatement" value="mini-statement close trigger-response" />
…
..
</participant>
…
…
<group name="walletcreate">
<participant class="org.jpos.jcard.rest.ValidateParams" logger="Q2">
<mandatory>
<param name="PAN">&PAN_PATTERN;</param>
<param name="WALLET_NUMBER">&WALLET_PATTERN;</param>
</mandatory>
<optional>
<!-- no optional fields -->
</optional>
</participant>
<participant class="org.jpos.transaction.Open" logger="Q2" realm="open">
<property name="checkpoint" value="open" />
<property name="timeout" value="300" />
</participant>
<participant class="org.jpos.jcard.CreateTranLog" logger="Q2"
realm="create-tranlog">
<property name="capture-date" value="capture-date" />
<property name="node" value="01" />
</participant>
<participant class="org.jpos.jcard.CheckCustomer"
logger="Q2" realm="checkcustomer">
</participant>
<participant class="org.jpos.jcard.rest.WalletCreateParticipant" logger="Q2" realm="create-wallet">
<property name="chart" value="jcard" />
<property name="customers-account" value="21" />
<property name="kid" value="&KID;" />
</participant>
</group>
...
...
</txnmgr>
Now the code looks like this:
@Path("/customers/{customer_id}/wallets/{wallet_id}/credit")
public class WalletCredit extends WalletCreditDebitSupport {
@POST
@Produces(MediaType.APPLICATION_JSON)
@Consumes(MediaType.APPLICATION_FORM_URLENCODED)
public Response credit (
@Context UriInfo uriInfo,
@PathParam("customer_id") String customerId,
@PathParam("wallet_id") String walletId,
@FormParam("rrn") String rrn,
@FormParam("detail") String detail,
@FormParam("amount") BigDecimal amount,
@FormParam("currency") String currency)
throws IOException, URISyntaxException, BLException, ISOException
{
return process(uriInfo, customerId, walletId, rrn, detail, amount, currency);
}
}
public abstract class WalletCreditDebitSupport extends RestSupport {
@SuppressWarnings("unchecked")
public Response process (
UriInfo uriInfo,
String customerId,
String walletId,
String rrn,
String detail,
BigDecimal amount,
String currency)
throws IOException, URISyntaxException, BLException, ISOException
{
org.jpos.transaction.Context ctx = new org.jpos.transaction.Context();
ctx.put(TXNNAME, getClass().getSimpleName());
ctx.put(PAN, customerId);
ctx.put(WALLET_NUMBER, walletId);
ctx.put(CURRENCY, currency);
ctx.put(RRN, rrn);
ctx.put(AMOUNT, amount);
ctx.put(DETAIL, detail);
int result = queryTxnMgr(ctx);
Map<String,Object> resp = createResponseMap();
if (result == TransactionManager.PREPARED) {
String irc = (String) ctx.get (IRC);
resp.put("success", TRAN_APPROVED.equals(irc));
resp.put("balance", ctx.get (LEDGER_BALANCE));
return Response.ok(toJson(resp), MediaType.APPLICATION_JSON)
.status(Response.Status.CREATED)
.location(URI.create(uriInfo.getAbsolutePath().toString())).build();
} else {
resp.put("success", false);
resp.put("message", ctx.getString(TxnConstants.RC));
return Response.status(Response.Status.BAD_REQUEST).entity(toJson(resp)).build();
}
}
}
And the best part of this, is that we get our familiar 'Debug' and 'Trace' events:
<log realm="rest-txnmgr" at="Wed Oct 09 20:07:01 UYST 2013.489" lifespan="925ms">
<debug>
rest-txnmgr-0:idle:1
prepare: org.jpos.jcard.PrepareContext NO_JOIN
prepare: org.jpos.jcard.Switch READONLY NO_JOIN
selector: walletcreate close trigger-response
prepare: org.jpos.jcard.rest.ValidateParams READONLY NO_JOIN
prepare: org.jpos.transaction.Open READONLY NO_JOIN
prepare: org.jpos.jcard.CreateTranLog NO_JOIN
prepare: org.jpos.jcard.CheckCustomer NO_JOIN
prepare: org.jpos.jcard.rest.WalletCreateParticipant READONLY NO_JOIN
prepare: org.jpos.transaction.Close READONLY
prepare: org.jpos.jcard.rest.TriggerResponse READONLY
prepare: org.jpos.transaction.Debug READONLY
commit: org.jpos.transaction.Close
commit: org.jpos.jcard.rest.TriggerResponse
commit: org.jpos.transaction.Debug
head=2, tail=2, outstanding=0, active-sessions=2/64, tps=0, peak=0, avg=0.00, elapsed=925ms
<profiler>
prepare: org.jpos.jcard.PrepareContext [0.1/0.1]
prepare: org.jpos.jcard.Switch [0.0/0.2]
prepare: org.jpos.jcard.rest.ValidateParams [0.2/0.5]
prepare: org.jpos.transaction.Open [0.7/1.2]
prepare: org.jpos.jcard.CreateTranLog [2.3/3.6]
prepare: org.jpos.jcard.CheckCustomer [8.5/12.2]
prepare: org.jpos.jcard.rest.WalletCreateParticipant [556.5/568.7]
prepare: org.jpos.transaction.Close [0.1/568.9]
prepare: org.jpos.jcard.rest.TriggerResponse [0.1/569.1]
prepare: org.jpos.transaction.Debug [0.1/569.2]
commit: org.jpos.transaction.Close [344.9/914.2]
commit: org.jpos.jcard.rest.TriggerResponse [0.6/914.8]
commit: org.jpos.transaction.Debug [9.8/924.7]
end [0.9/925.7]
</profiler>
</debug>
</log>
<log realm="debug" at="Wed Oct 09 20:07:03 UYST 2013.845">
<commit>
<id>2</id>
<context>
<entry key='PAN'>0000000005</entry>
<entry key='RRN'>000000000001</entry>
<entry key='LOGEVT'><log realm="" at="Wed Oct 09 20:07:03 UYST 2013.846" lifespan="38ms">
<log>
<![CDATA[
<transaction id="97" date="20131009200703" post-date="20131009" journal="jcard">
<detail>WalletCredit 2</detail>
<entry account="11.001.00" type="debit" layer="840">
<amount>100.00</amount>
</entry>
<entry account="21.0000000005.1" type="credit" layer="840">
<amount>100.00</amount>
</entry>
</transaction>
]]>
</log>
</log>
</entry>
<entry key='WALLET_NUMBER'>0000000005</entry>
<entry key='CARDHOLDER'>org.jpos.ee.CardHolder@64a5efa3[id=5]</entry>
<entry key='DB'>org.jpos.ee.DB@63130f0d</entry>
<entry key='LEDGER_BALANCE'>100.00</entry>
<entry key='TRANLOG'>org.jpos.ee.TranLog@76250246[id=172]</entry>
<entry key='AMOUNT'>100.00</entry>
<entry key='SWITCH'>WalletCredit (walletcredit close trigger-response)</entry>
<entry key='ACCOUNT'>org.jpos.gl.FinalAccount@20c0fe7c[id=49,code=21.0000000005.1]</entry>
<entry key='TXNRESULT'>1</entry>
<entry key='TXNMGR'>rest-txnmgr</entry>
<entry key='IRC'>0000</entry>
<entry key='CURRENCY'>840</entry>
<entry key='DETAIL'>Test wallet credit</entry>
<entry key='WALLET'>org.jpos.ee.Wallet@71df1c8a[id=1,number=0000000005]</entry>
<entry key='CAPTURE_DATE'>Wed Oct 09 00:00:00 UYST 2013</entry>
<entry key='TXNNAME'>WalletCredit</entry>
<entry key='TIMESTAMP'>Wed Oct 09 20:07:03 UYST 2013</entry>
<entry key='PROFILER'>
<profiler>
prepare-context [0.0/0.0]
open [1.1/1.2]
close [533.2/534.4]
end [10.9/545.4]
</profiler>
</entry>
<entry key='ISSUER'>org.jpos.ee.Issuer@6f59db4f[id=1,name=jcard]</entry>
</context>
</commit>
</log>
<log realm="rest-txnmgr" at="Wed Oct 09 20:07:03 UYST 2013.864" lifespan="553ms">
<debug>
rest-txnmgr-0:idle:2
prepare: org.jpos.jcard.PrepareContext NO_JOIN
prepare: org.jpos.jcard.Switch READONLY NO_JOIN
selector: walletcredit close trigger-response
prepare: org.jpos.jcard.rest.ValidateParams READONLY NO_JOIN
prepare: org.jpos.transaction.Open READONLY NO_JOIN
prepare: org.jpos.jcard.CreateTranLog NO_JOIN
prepare: org.jpos.jcard.CheckCustomer NO_JOIN
prepare: org.jpos.jcard.CheckWallet READONLY NO_JOIN
prepare: org.jpos.jcard.rest.WalletTransactionParticipant READONLY NO_JOIN
prepare: org.jpos.transaction.Close READONLY
prepare: org.jpos.jcard.rest.TriggerResponse READONLY
prepare: org.jpos.transaction.Debug READONLY
commit: org.jpos.transaction.Close
commit: org.jpos.jcard.rest.TriggerResponse
commit: org.jpos.transaction.Debug
head=3, tail=3, outstanding=0, active-sessions=2/64, tps=0, peak=1, avg=0.50, elapsed=553ms
<profiler>
prepare: org.jpos.jcard.PrepareContext [0.1/0.1]
prepare: org.jpos.jcard.Switch [0.1/0.2]
prepare: org.jpos.jcard.rest.ValidateParams [0.2/0.5]
prepare: org.jpos.transaction.Open [0.8/1.3]
prepare: org.jpos.jcard.CreateTranLog [2.4/3.8]
prepare: org.jpos.jcard.CheckCustomer [8.7/12.5]
prepare: org.jpos.jcard.CheckWallet [7.3/19.9]
prepare: org.jpos.jcard.rest.WalletTransactionParticipant [487.2/507.2]
prepare: org.jpos.transaction.Close [0.1/507.3]
prepare: org.jpos.jcard.rest.TriggerResponse [0.0/507.4]
prepare: org.jpos.transaction.Debug [0.0/507.5]
commit: org.jpos.transaction.Close [27.0/534.5]
commit: org.jpos.jcard.rest.TriggerResponse [0.1/534.7]
commit: org.jpos.transaction.Debug [18.3/553.0]
end [1.0/554.1]
</profiler>
</debug>
</log>
Hope you consider using the TransactionManager the next time you need to write a REST API call.