To the more experienced reader…if you are still here! This series of blog-posts are intentionally slow and evolutionary in style….a lot of this stuff you skip and do implicitly out of experience.
I’m actually enjoying this. Its the first time I am having to write down my thoughts in so much detail….So what’s next? Lets see….we have a decent application getting built right now…it keeps a track of my balance, am able to post a credit or a debit to it.
Its a shame I can’t see a list of transactions that I have performed….with a nice little description of each….ah! I told you its going to come….the ability to get the details of the last 10 transactions. Or more generally, take a look at where my money is going!
So lets take the requirement on-board. What do we have so far?
Starting like we did previously, I need a method in my MyStashService that can retrieve, for me, a list of credits and debits I have recorded so far….lets keep it simple for now….it returns me the last 10 transactions recorded, be it credit or debit.
public List<Transaction> getTransactions() {
// TBD - should return last 10 credits or debits
return new ArrayList<>();
}
Wait a minute…..how do we plan on retrieving this information when we do not have it. Needless to say – before we proceed further we need to start saving this information. So what is the additional information that I would like to save as part of each credit or debit? At the very least, a description would be nice. Lets do that. We have to do the inevitable….change existing code for this new requirement. I hope I remember all that code I wrote ;-)…….we need to change the debit and credit methods in MyStashService to take a description that the user enters. Lets attack one at a time….the credit first as always….I like getting money :-). This is what we have so far with the additional description parameter added
// Record an income
public void credit(double amount, String description)
throws SQLException {
// Get Balance
double balance = myStashDB.fetchBalance();
// Increase the balance by the amount
balance = BalanceModifier.credit(balance, amount);
// Update Balance
myStashDB.saveBalance(balance);
}
Reviewing what we have, “MyStashDB.fetchBalance()” call remains the same, “BalanceModifier.credit()” call remains the same, “MyStashDB.saveBalance()”….hmm…everything remains the same….I just need to shove the amount with the description into a “transaction” table in the database….naturally, this needs to be delegated to the “Database Guy” – MyStashDB – since he is the database guy and his name suggests, he takes care of all database requirements for MyStashService. So lets add the following to it:
public void saveCredit(double amount, String description)
throws SQLException {
// Update Balance
try (Connection conn = DriverManager.getConnection(
dbConnectionUrl, dbUserName, dbPassword)) {
conn.setAutoCommit(false);
try (PreparedStatement stmt = conn.prepareStatement(
"insert into transaction_log (amount, description, " +
"txntype) values (?, ?, ?)")) {
stmt.setDouble(1, amount);
stmt.setString(2, description);
stmt.setString(3, "CREDIT");
int rowsUpdated = stmt.executeUpdate();
if (rowsUpdated != 1) {
throw new SQLException("Should have inserted 1 " +
"transaction, rows inserted were " +
rowsUpdated);
}
conn.commit();
} catch (SQLException e) {
conn.rollback();
throw e;
}
}
}
And update MyStaskService.credit() to call it:
// Record an income
public void credit(double amount, String description)
throws SQLException {
// Get Balance
double balance = myStashDB.fetchBalance();
// Increase the balance by the amount
balance = BalanceModifier.credit(balance, amount);
// Update Balance
myStashDB.saveBalance(balance);
// Save Transaction
myStashDB.saveCredit(amount, description); // ADDED THIS LINE
}
Now to do the same routine for MyStashService.debit() flow. First add “MyStashDB.saveDebit()” as shown below:
public void saveDebit(double amount, String description)
throws SQLException {
// Update Balance
try (Connection conn = DriverManager.getConnection(
dbConnectionUrl, dbUserName, dbPassword)) {
conn.setAutoCommit(false);
try (PreparedStatement stmt = conn.prepareStatement(
"insert into transaction_log (amount, description, " +
"txntype) values (?, ?, ?)")) {
stmt.setDouble(1, amount);
stmt.setString(2, description);
stmt.setString(3, "DEBIT");
int rowsUpdated = stmt.executeUpdate();
if (rowsUpdated != 1) {
throw new SQLException("Should have inserted 1 " +
"transaction, rows inserted were " +
rowsUpdated);
}
conn.commit();
} catch (SQLException e) {
conn.rollback();
throw e;
}
}
}
And update MyStashService.debit() to call it:
// Record an expense
public void debit(double amount, String description)
throws SQLException {
// Get Balance
double balance = myStashDB.fetchBalance();
// Increase the balance by the amount
balance = BalanceModifier.debit(balance, amount);
// Update Balance
myStashDB.saveBalance(balance);
// Save Transaction
myStashDB.saveDebit(amount, description); // ADDED THIS LINE
}
Cool, now we are finally good to implement “MyStashService.getTransactions()” method!
Oops! Forgot..before jumping into the implementation of “MyStashService.getTransactions()” method….could we also let me enter the transaction date? And incidentally have we updated the bunch of various tests we “may” have written so far…that sure would have been quite a pain given that the signatures have changed and is supposedly going to change again if we continue following this design any more, not to mention the new tests I have to add as well for each new field getting added?
Okay, okay…hold on now…something’s going on here….take a step back. Don’t you feel that we are repeating ourselves?….life henceforth in “MyStash” is just going to be such mundane repeated work I guess, well at least most of the time….am sure there will be many more requests for field additions as life goes on in “MyStash”…is that what software engineering boils down to – after the initial run of putting something that works together……the boring drudgery of – add a field here, add a method there, hook everything up over there……..deja vu? Something I mentioned at the beginning of the series….feels like the comfort zone we spoke about….if we had a new team member joining us, we would have ready instructions for him… “if we get a request to add a new field for a transaction, just follow these steps….”…and he would get bored and run away!
Can we do something different to avoid all this pain & boredom? Yes we can, the trick is to aim for writing code in such a way that you don’t ever have to touch it again – except of course to fix a bug or two. SRP actually contributes quite actively to this mindset…you want to keep things that don’t belong together – separate – so that one does not have to change in case the other one changes.
Lets take an example of the credit method of MyStashService. When we started off writing that method we said: okay we need a credit method…and first put down a signature for it:
// Record an income
public void credit(double amount) {...}
Before proceeding further, the trick is to ask the question “Will this ever change?” – more specifically I mean “Will the signature of the credit method ever change?”. In this case, you probably would have easily come up with the suggestion yourself….yes, probably would be good to add a description of the credit…and may be the date on which it happened. Going by this line of thought you would agree with yourself and go ahead and evolve the signature to probably this – just like the one we came to after a bunch of blog-posts :-).
But is that all? No. The real trick to uncover is the type of change that got described above – the credit that is being recorded could have more information about it aside from just its amount – for now – description and date…so how can we avoid the credit method’s signature from changing every-time new information needs to be included?
Am sure you’ve guessed it by now….why not have a class “CreditInfo” which “encapsulates” the information about the credit and pass that in! We know, the credit() method implementation doesn’t care about anything but the balance….its not his responsibility to care and keep changing his signature every time new information about the credit needs to be added. Its the SRP of the Credit Data Structure to know what information makes up a Credit. MyStashService.credit() just passes the additional information on to the MyStashDB guy. So…..“encapsulation” is just a fancy OO word for SRP – the Single Responsibility Principle. At least, that’s what I think :-).
Okay so, lets change the signature of credit method and create a CreditInfo class which contains amount, description and credit date:
Revised Code for MyStashService
-------------------------------
// Record an income
public void credit(CreditInfo theCredit) throws SQLException {
// Get Balance
double balance = myStashDB.fetchBalance();
// Increase the balance by the amount
balance = BalanceModifier.credit(
balance, theCredit.getAmount());
// Update Balance
myStashDB.saveBalance(balance);
// Save the Credit
myStashDB.saveCredit(theCredit);
}
Revised Code for MyStashDB
--------------------------
public void saveCredit(CreditInfo theCredit) throws SQLException {
// Update Balance
try (Connection conn = DriverManager.getConnection(
dbConnectionUrl, dbUserName, dbPassword)) {
conn.setAutoCommit(false);
try (PreparedStatement stmt = conn.prepareStatement(
"insert into transaction_log (amount, description, " +
"txntime, txntype) " +
"values (?, ?, ?, ?)")) {
stmt.setDouble(1,
theCredit.getAmount());
stmt.setString(2,
theCredit.getDescription());
stmt.setTimestamp(3,
new Timestamp(theCredit.getDate().getTime()));
stmt.setString(4,
"CREDIT");
int rowsUpdated = stmt.executeUpdate();
if (rowsUpdated != 1) {
throw new SQLException("Should have inserted 1 " +
"transaction, rows inserted were " +
rowsUpdated);
}
conn.commit();
} catch (SQLException e) {
conn.rollback();
throw e;
}
}
}
So, this I think, pretty much ensures that next time around when additional information needs to be included while recording a credit, the MyStashService.credit() method remains unchanged….to put it in terms of the SOLID principles….the method MyStashService.credit() is closed for modification for changes of this nature.
Lets quickly do a similar update to the debit() method with a DebitInfo class….offline 😉
