Simplify by using @transactional

Spring Python solves these problems with its TransactionTemplate. This utility class makes it easy to wrap business methods with transactional functionality that solves all of the problems listed earlier. Spring Python makes it easy to wrap our existing business functions with the TransactionTemplate using its @transactional decorator.

  1. First, let's take our simple transfer function, and put into a Bank class.
    from springpython.database.core import *
    from springpython.database.factory import *
    class Bank(object):
    def __init__(self, connectionFactory):
    self.factory = connectionFactory
    self.dt = DatabaseTemplate(self.factory)
    def transfer(self, transfer_amt, source_act, target_act):
    self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE - %s
    where ACCOUNT_NUM = %s""",
    (transfer_amt, source_act))
    self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE + %s
    where ACCOUNT_NUM = %s""",
    (transfer_amt, target_act))
    

    In this situation, we have stripped out all the hand-coded transaction code. Instead, we have the simple, concise business logic that defines a transfer operation.

    Note

    Please note, this version of the Bank application is NOT yet safe.

    The steps are easily shown with the following sequence diagram:

    Simplify by using @transactional
  2. Now let's wrap our transfer method with transaction protection by using Spring Python's @transactional decorator.
    from springpython.database.core import *
    from springpython.database.factory import *
    from springpython.database.transaction import *
    class Bank(object):
    def __init__(self, connectionFactory):
    self.factory = connectionFactory
    self.dt = DatabaseTemplate(self.factory)
    @transactional
    def transfer(self, transfer_amt, source_act, target_act):
    self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE - %s
    where ACCOUNT_NUM = %s""",
    (transfer_amt, source_act))
    self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE + %s
    where ACCOUNT_NUM = %s""",
    (transfer_amt, target_act))
    

@transactional is a decorator that uses a hidden instance of TransactionTemplate to execute our transfer function inside a very robust version of the transaction pattern. Our interactions are better shown in the following diagram.

Simplify by using @transactional
  • Because @transactional wraps our transfer function, when the user invokes transfer, they are hitting the decorator first
  • @transactional passes all the context of the requested method call to its private instance of TransactionTemplate
  • TransactionTemplate starts a transaction
  • TransactionTemplate then calls the original Bank transfer function
  • Bank carries out its business, totally unaware it is inside a transaction
  • When complete, Bank hands control back to the TransactionTemplate, which issues a commit
  • TransactionTemplate hands control back to @transactional, and finally back to the caller

Note

This configuration totally decouples transactional logic from our transfer operation.

  • Since TransactionTemplate is the caller of transfer, it can easily handle any number of return statements. If an exception is raised anywhere inside transfer, TransactionTemplate will rollback instead of commit.

With this clean separation of concerns, we can work on the business code without having to worry about getting transactions right.

More about TransactionTemplate

Our first version of the transaction pattern was simple and naïve. We then examined the list of issues with that pattern. TransactionTemplate has a much more sophisticated pattern that handles these extra situations:

  • It handles the simple case of catching any exception thrown by catching it, issuing a rollback, and then re-throwing the exception
  • It handles all return statements by catching the return value of the method, issuing the necessary commit, and then finally returning the results
  • @transactional is coded by default to start new transactions if one isn't currently in progress, and to join a transaction if one already exists. We will look at the other transactional options later in this chapter

Another integrity gap exists in our transfer code. It lies somewhere between the transaction pattern and our business logic. Can you spot it?

There are no checks to make sure that we even have $10,000 to transfer! Also, there is no type of security check ensuring that we own either of these two accounts. We will address this deficiency later on, when filling in the transactional details.

We're not done yet. In order to have @transactional do its job, we need to link it with a Transaction Manager through an AutoTransactionalObject. The Transaction Manager provides @transactional with a handle into the database to issue necessary commits and rollbacks. It also tracks the context of existing transactions and make appropriate decisions about when to start new transactions.

  1. To inject everything, let's define a pure Python IoC container that links ConnectionFactoryTransactionManager to @transaction through an AutoTransactionalObject.
    from springpython.database.transaction import *
    TransactionTemplateaboutclass BankAppConfig(PythonConfig):
    def __init__(self, factory):
    PythonConfig.__init__(self)
    self.factory = factory
    @Object
    def transactionalObject(self):
    return AutoTransactionalObject(self.tx_mgr())
    @Object
    def tx_mgr(self):
    return ConnectionFactoryTransactionManager(self.factory)
    @Object
    def bank(self):
    return Bank(self.factory)
    
    • tx_mgr defines our Transaction Manager, which uses an injected factory in order to perform the SQL transaction APIs. This is the same type of factory used by DatabaseTemplate. tx_mgr tracks when transactions begin and end, providing the necessary services for TransactionTemplate and @transactional.
    • transactionalObject defines an instance of AutoTransactionalObject, an IoC post processor. Its job is to find all instances of @transactional and link them with the tx_mgr. This is what empowers @transactional to do its job of ensuring data integrity through SQL transactions.
    • The bank class is our business class.
  2. Now, let's write some startup code to perform the transfer.
    if __name__ == "__main__":
    from springpython.context import ApplicationContext
    ctx = ApplicationContext(BankAppConfig(
    Sqlite3ConnectionFactory("/path/to/sqlite3db")))
    service = ctx.get_object("bank")
    bank.transfer(10000.0, "SAVINGS", "CHECKING")
    

The Spring Triangle—Portable Service Abstractions

We saw this diagram earlier in the book, as an illustration of the key principles behind Spring Python.

The Spring Triangle—Portable Service Abstractions

TransactionTemplate represents a Portable Service Abstraction.

  • It is portable because it uses Python's standardized API for SQL transactions, not tying us to any database vendor or custom database connection library
  • It provides the useful service of letting us easily wrap methods with a sophisticated and powerful transaction pattern
  • It offers a nice abstraction to writing transactional code, without requiring us to handle the SQL transaction APIs directly

Programmatic transactions

Our banking example has shown how to decorate some business logic with Spring Python's @transactional in order to make the operation transactional. Throughout the example, we have repeatedly mentioned TransactionTemplate.

In this section, we will use TransactionTemplate directly instead of @transactional. We will also explore how to do this with and without IoC configuration.

Configuring with the IoC container

  1. First, let's rewrite Bank, replacing @transactional with TransactionTemplate.
    class Bank(object):
    def __init__(self, connectionFactory):
    self.factory = connectionFactory:
    self.dt = DatabaseTemplate(self.factory)
    self.tx_mgr = ConnectionFactoryTransactionManager(self.factory)
    self.tx_template = TransactionTemplate(self.tx_mgr)
    def transfer(self, transfer_amt, source_act, target_act):
    class TxDefinition(TransactionCallbackWithoutResult):
    def doInTransactionWithoutResult(s, status):
    self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE - %s
    where ACCOUNT_NUM = %s""",
    (transfer_amt, source_act))
    self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE + %s
    where ACCOUNT_NUM = %s""",
    (transfer_amt, target_act))
    self.tx_template.execute(TxDefinition())
    

    This version of our Bank shows two more attributes: tx_mgr and tx_template. These could be injected into our class, but we chose to inject the connection factory only.

    • This inner class defines one method: doInTransactionWithoutResult, which contains our business logic.
    • To avoid confusion between self passed into transfer and self passed into TxDefinition, doInTransactionWithoutResult names its first argument s.
    • Because transfer is not expected to return anything, it uses TransactionCallbackWithoutResult as a base class. For return values, use TransactionCallback/doInTransaction instead.
  2. Next, let's adjust the IoC configuration to handle these changes.
    from springpython.database.transaction import *
    class BankAppConfig(PythonConfig):
    def __init__(self, factory):
    PythonConfig.__init__(self)
    self.factory = factory
    @Object
    def bank(self):
    return Bank(self.factory)
    
  3. This should have no effect on the code used to run our app and execute the same transfer.
    if __name__ == "__main__":
    from springpython.context import ApplicationContext
    ctx = ApplicationContext(BankAppConfig(
    Sqlite3ConnectionFactory("/path/to/sqlite3db")))
    service = ctx.get_object("bank")
    bank.transfer(10000.0, "SAVINGS", "CHECKING")
    

Configuring without the IoC container

In this situation, our IoC configuration is pretty simple. We could code the application without it. Just remember: IoC provides useful assistance in things like testing, mocking, and being able to swap out key objects.

We begin by rewriting the startup script, so that it doesn't need any IoC container.

if __name__ == "__main__":
service = Bank(Sqlite3ConnectionFactory("/path/to/sqlite3db"))
bank.transfer(10000.0, "SAVINGS", "CHECKING")

Because our Bank was written using simple constructor injection, there was no need to alter it in order to run it without a container. Since we are programmatically using TransactionTemplate, there is no requirement to use the IoC container. This offers developers an opportunity to evaluate Spring Python purely for the transactional features without having to try out the IoC container at the same time.

But it is important to remember that multiple examples of the value of IoC have already been shown in this book, and many more are coming.

@transactional versus programmatic

The Spring way includes giving developers options. In order to choose the right approach, here are some pros and cons:

@transactional

  • Pros:
    • Defines transactions with a single line.
    • Clear, concise, easy-to-read.
  • Cons:
    • Requires IoC to wire AutoTransactionalObject.
    • Requires editing existing code.

programmatic

  • Pros:
    • Explicitly shows the transactional behavior where it occurs.
    • Doesn't require IoC.
  • Cons:
    • Re-introduces code tangling.

      Requires editing existing code.

If the cons for either of these solutions are not acceptable, there is a third choice: declaring transactions from inside the IoC container. This allows easy wrapping of business code with transactions without code tangling and without editing already existing code. We will demonstrate this later in the chapter. But first let's look at adding new functions.

Making new functions play nice with existing transactions

So far, we have managed to build a bank that does one thing: transfer money. As with any software project, we typically have to grow the functionality. As we proceed with modifications to our Bank, we want to add new transactions without risking existing ones. Spring Python's transaction management makes this very simple.

In this section, we will go back to our @transactional Bank and add some new functionality.

  1. Let's extract a withdraw function and deposit function from the transfer function.
    class Bank(object):
    def __init__(self, connectionFactory):
    self.factory = connectionFactory):
    self.dt = DatabaseTemplate(self.factory)
    def withdraw(self, amt, act):
    self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE - %s
    where ACCOUNT_NUM = %s""",
    (amt, act))
    def deposit(self, amt, act):
    self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE + %s
    where ACCOUNT_NUM = %s""",
    (amt, act))
    @transactional
    def transfer(self, transfer_amt, source_act, target_act):
    self.withdraw(transfer_amt, source_act)
    self.deposit(transfer_amt, target_act)
    

    By moving the two SQL statements into separate functions, we have nicely defined transfer as withdraw followed by deposit. We are now ready to offer these two new functions to our clients. Do you notice anything wrong with this? Are the functions safe transaction-wise by themselves? What if another banking operation tried to reuse these primitives?

  2. Secure the withdraw and deposit methods with @transactional.
    class Bank(object):
    def __init__(self, connectionFactory):
    self.factory = connectionFactory):
    self.dt = DatabaseTemplate(self.factory)
    @transactional
    def withdraw(self, amt, act):
    self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE - %s
    where ACCOUNT_NUM = %s""",
    (amt, act))
    @transactional
    def deposit(self, amt, act):
    self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE + %s
    where ACCOUNT_NUM = %s""",
    (amt, act))
    @transactional
    def transfer(self, transfer_amt, source_act, target_act):
    self.withdraw(transfer_amt, source_act)
    self.deposit(transfer_amt, target_act)
    

    The only difference in our code is marking withdraw and deposit with @transactional.

  3. Now let's add an extra layer of integrity to our Bank by doing some checks before and after executing the SQL.
    class Bank(object):
    def __init__(self, connectionFactory):
    self.factory = connectionFactory):
    self.dt = DatabaseTemplate(self.factory)
    @transactional(["PROPAGATION_SUPPORTS"])
    def balance(self, act):
    return self.dt.queryForObject("""
    SELECT BALANCE
    FROM ACCOUNT
    WHERE ACCOUNT_NUM = ?""",
    (act,), types.FloatType)
    @transactional
    def withdraw(self, amt, act):
    if (self.balance(act) > amt):
    rows = self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE - %s
    where ACCOUNT_NUM = %s""",
    (amt, act))
    if (rows == 0):
    raise Exception("Account %s does not exist." % act)
    else:
    raise Exception("Account %s has insufficient funds." % act)
    @transactional
    def deposit(self, amt, act):
    rows = self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE + %s
    where ACCOUNT_NUM = %s""",
    (amt, act))
    if (rows == 0) {
    raise Exception("Account %s does not exist." % act)
    @transactional
    def transfer(self, transfer_amt, source_act, target_act):
    self.withdraw(transfer_amt, source_act)
    self.deposit(transfer_amt, target_act)
    

We have made several changes that start to make our application look like a real bank. They are as follows:

  • We created a balance function that allows us to look up the balance for an account
  • The withdraw function now checks the balance to make sure there is enough to withdraw
  • The withdraw function verifies that a row of data was updated, confirming the withdrawn account is real
  • The deposit function verifies that a row of data was updated, confirming that the deposited account is real

How Spring Python lets us define a transaction's ACID properties

As discussed earlier, the ACID properties of transactions are as follows:

  • Atomicity —We need to guarantee that all steps complete, or no steps complete.
  • Consistent—This is the concept of ensuring our data maintains a consistent state.
  • Isolated—When we withdraw money from the bank, it would be bad if our business partner was withdrawing at the same time, and we ended up with a negative balance.
  • Durable—We expect completed transactions to survive hardware failures

Regarding atomicity, we have already practiced defined the beginning and end points for transactions by using @transactional and TransactionTemplate.

Spring Python supports propagation. Earlier, we stated that the default policy of @transactional is to start a new transaction (if none existed) and join an existing transaction (if one was already in progress). Spring Python conveniently lets us take the safe, atomic operations of withdraw and deposit, and combine them together into transfer, without having to interact with the SQL transaction APIs at all.

We also created another function, balance, to lookup the current balance of accounts. Since balance performs no updates, it doesn't require a transaction when run by itself. However, when called upon by an existing transaction, we want it to join in as if it was part of the transaction. This is accomplished by providing @transactional with a propagation override:

@transactional(["PROPAGATION_SUPPORTS"])
def balance(self, act):
return self.dt.queryForObject("""
SELECT BALANCE
FROM ACCOUNT
WHERE ACCOUNT_NUM = ?""",
(act,), types.FloatType)

@transactional scans the list of transaction definitions. Currently, Spring Python supports the following definitions:

Property

Description

PROPAGATION_REQUIRED

A transaction is required. If a current one exists, join it. Otherwise, start a new one. This is the default for @transactional.

PROPAGATION_SUPPORTS

A transaction is not required. This code can run inside or outside a transaction.

PROPAGATION_MANDATORY

A transaction is required. If a current one exists, join it. Otherwise, raise an exception.

PROPAGATION_NEVER

A transaction is not allowed. If a current one exists, raise an exception. Otherwise, run the code.

Spring Python provides incredibly useful transaction context management, transaction API handling, and allows us clean demarcation of transactions.

As better definitions are added to Python's database specification for things like isolation, Spring Python will add more options to support it. This will increase our ability to cleanly declare the exact type of transaction needed to wrap our code.

Applying transactions to non-transactional code

An important aspect of Spring Python is its non-invasive nature. This was demonstrated in great detail in the chapter that introduced aspect oriented programming. Spring Python provides a convenient, non-intrusive method interceptor that allows the demarcation of existing code.

This solves the problem mentioned earlier, where neither editing existing source code nor tangling our business logic with transaction management are acceptable.

  1. Let's start with an alternative version of Bank class that has no transaction demarcation.
    class Bank(object):
    def __init__(self, connectionFactory):
    self.factory = connectionFactory:
    self.dt = DatabaseTemplate(self.factory)
    def balance(self, act):
    return self.dt.queryForObject("""
    SELECT BALANCE
    FROM ACCOUNT
    WHERE ACCOUNT_NUM = ?""",
    (act,), types.FloatType)
    def withdraw(self, amt, act):
    if (self.balance(act) > amt):
    rows = self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE - %s
    where ACCOUNT_NUM = %s""",
    (amt, act))
    if (rows == 0):
    raise Exception("Account %s does not exist." % act)
    else:
    raise Exception("Account %s has insufficient funds." % act)
    def deposit(self, amt, act):
    rows = self.dt.execute("""
    update ACCOUNT
    set BALANCE = BALANCE + %s
    where ACCOUNT_NUM = %s""",
    (amt, act))
    if (rows == 0) {
    raise Exception("Account %s does not exist." % act)
    def transfer(self, transfer_amt, source_act, target_act):
    self.withdraw(transfer_amt, source_act)
    self.deposit(transfer_amt, target_act)
    

    This Bank class is identical to the previous one, except for the fact that there are no @transactional decorators.

  2. Let's define an application context that has equivalent transaction demarcation points.
    class BankAppConfig(PythonConfig):
    def __init__(self, factory):
    PythonConfig.__init__(self)
    self.factory = factory
    @Object
    def bank_target(self):
    return Bank(self.factory)
    @Object
    def tx_mgr(self):
    return ConnectionFactoryTransactionManager(self.factory)
    @Object
    def bank(self):
    tx_attrs = []
    tx_attrs.append((".*transfer", ["PROPAGATION_REQUIRED"]))
    tx_attrs.append((".*withdraw", ["PROPAGATION_REQUIRED"]))
    tx_attrs.append((".*deposit", ["PROPAGATION_REQUIRED"]))
    tx_attrs.append((".*balance", ["PROPAGATION_SUPPORTS"]))
    return TransactionProxyFactoryObject(self.tx_mgr(),
    self.bank_target(),
    tx_attrs)
    

    With this alternative configuration, we use TransactionProxyFactoryObject. This is an out-of-the-box AOP interceptor that Spring Python offers to automatically wrap certain functions with TransactionTemplate. It requires a transaction manager as well as the target object, our Bank. It also needs a list of tuples, with each tuple defining a regular expression for method matching as well as a list of transaction properties just like we plugged into @transactional earlier in this chapter.

What is the right choice: @transactional or TransactionProxyFactoryObject?

  • @transactional is clear, concise, and easy-to-read. Its biggest drawback is the requirement to edit existing code. If we own the code, then this shouldn't be a problem.
  • If we are trying to wrap transactions around a 3rd party library that we don't directly control, TransactionProxyFactoryObject is the best choice.

Testing your transactions

Transactions are intrinsically tied to databases. Attempting to mock or stub this out would require an extreme amount of effort, and probably not be worth the effort. This is one area where I generally agree with testing against an actual database.

It is possible to use lightweight databases such as sqlite for this effort, but it may be risky if this isn't the target platform for production. In fact, the best testing effort would be a properly setup test bed using the same version of database engine as production. The important point is that it is easy to create lightweight tests against something small such as sqlite. This can confirm to the developers that things are working as expected. More extensive integration testing can be done with a production grade test-bed.

Note

Easily swapping out different database configurations is one of the major advantages of using Spring Python.

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.145.35.194