Sonntag, 12. Oktober 2014

BARACUS from Scratch - Part 9 - Using interface and implementations & context bootstrap


Table of Contents

In this tutorial I am going to explain You how to use the BARACUS framework to work with alternative Implementations of an Interface plus hot-restarting the context to make use of them. Also, I will demonstrate the afterContextInitialized hook which enables You to perform post-container-startup operations.

Previous Tutorial : Lifecycle support with migr8

You can download the sources of this tutorial here

0 - Basics


Since Version 0.8, Baracus offers the capability to register an implementation to an interface. This can be very useful in combination with the bootstrap feature (reinitialize context), because it allows You to switch between several application varieties using different implementations of the interface. The registration is simply an alternative call on BaracusApplicationContext.registerBeanClass.

For this example, we will define a BankAccountLoadService, which we are going to wire twice to the BankAccountDao, logging each call on it.

1 - Defining the interface


Simply define the interface in the service package :


 
public interface BankAccountLoadService {

    List<BankAccount> loadAllAccountsByCustomerId(Long id);

}



2 - Implement the interface 


Next, we implement the interface :

 
public class BankAccountLoadServiceImpl1 implements BankAccountLoadService {



    @Bean
    BankAccountDao bankAccountDao;

    final Logger logger = new Logger(this.getClass());

    @Override
    public List<BankAccount> loadAllAccountsByCustomerId(Long id) {
        // In this demo, this is the primary implementation
        logger.info("Primary implementation called!");
        return bankAccountDao.getByCustomerId(id);

    }

}


3 - Implement the alternative (dummy here)


Then we implement an alternative; this could be a test class, a webservice or whatever. Interface implementations also can be used to improve testability.

 
/**
 * Created by marcus on 30.07.14.
 */
public class BankAccountLoadServiceImpl2 implements BankAccountLoadService {

    @Bean
    BankAccountDao bankAccountDao;

    final Logger logger = new Logger(this.getClass());

    @Override
    public List<BankAccount> loadAllAccountsByCustomerId(Long id) {
        // In this demo, this is the alternative implementation
        logger.info("Alternative implementation called!");
        return bankAccountDao.getByCustomerId(id);
    }
}


4 - Register interface


The interface now is used to register the implementation

 
public class ApplicationContext extends BaracusApplicationContext {

    static {
        ....

        registerBeanClass(BankAccountLoadService.class, BankAccountLoadServiceImpl1.class);

    }
}


This enables the Baracus DI container to inject the implementation wherever the interface is used.


5 - Use interface for injection


Then we will pimp the RowMapper to use the implementation :

public class CustomerDao extends BaseDao<Customer> {



    @Bean

    BankAccountLoadService bankAccountLoadService;

 

...



    private final RowMapper<Customer> rowMapper = new RowMapper<Customer>() {

        @Override

        public Customer from(Cursor c) {



  ....

  

         result.setAccounts(new LazyCollection<BankAccount>(new LazyCollection.LazyLoader<BankAccount>() {

                @Override

                public List<BankAccount> loadReference() {

                    return bankAccountLoadService.loadAllAccountsByCustomerId(id);

                }

            }));



That's all. Now we are able to use the Interface for DI. But that's not all, right? We want to make use of the ability to switch the implementation. Therefore, we will implement a ApplicationContextInitializer setting the Implementation "by random" - just for demonstration purpose. Since the decision, which implementation shall be used only happens on the boot of the app, we use a small semaphore to avoid permanent reinit. Imagine a basic context, you define and then, by configuration data (e.g. thru built-in configurationDao) you decide wether to build a thin client or a fat client - that is the one of the purposes of this class.

public class AfterContextInitialized implements ApplicationContextInitializer {

    static boolean reinit = true;

    @Override
    public void afterContextIsBuilt() {
        if ((new Date().getTime() % 2 ) == 0) {
            ApplicationContext.registerBeanClass(BankAccountLoadService.class, BankAccountLoadServiceImpl1.class);
        } else {
            ApplicationContext.registerBeanClass(BankAccountLoadService.class, BankAccountLoadServiceImpl2.class);
        }
        if (reinit) {
            reinit = false;
            ApplicationContext.reinitializeContext();;
        }
    }
}


This initializer also is registered in the application config :

public class ApplicationContext extends BaracusApplicationContext {

        static { 
          ....
          setApplicationContextInitializer(new AfterContextInitialized());
        }
}


That's it, now You can check by restarting the app several time, which implementation is used by checking the logs when expanding the customer details:

07-30 14:56:51.531    5316-5316/org.baracustutorial I/TUTORIAL_APP﹕ -BankAccountLoadServiceImpl1 Primary implementation called!

other try :

07-30 15:00:55.639    8815-8815/org.baracustutorial I/TUTORIAL_APP﹕ BankAccountLoadServiceImpl2 Alternative implementation called!


Conclusion :


As You can see, the implementation of alternatives can be used - in combination with configuration information - as a very powerful utility to create varying types of implementations / behaviour within the same codebase. The classic can be a client to be run locally using the local dao as a data service or a thin client pipelining all requests thru json requests on a remote system.

Also, it can be very useful to make the software more testable.

Next Tutorial :Writing custom validators




Keine Kommentare:

Kommentar veröffentlichen