Hello all.

First off, I really like iBatis.  I used it back in 2002 and I'm
really excited to see it here in Apache.  However, I've never been
happy with the DAO framework and would really love to see it
refactored (no offense to the current developers). :)

Specifically, I'm interested in adding some Inversion of Control (IoC)
features to the iBatis DAO Framework.  I'm including one simple patch
with this email, but what I'd like to do would involve a more
extensive refactoring which I'll describe below.

IoC is more of a programming practice than a design pattern.
Basically the idea is to manage who can control what.  Generally,
you consolidate control into a container and carefully manage access
methods.

Whether you realize it or not, the iBatis DaoManager class is a simple
container. It contains and manages DAOs. However, there is a lot of room for improvement, particularly in the form of IoC. For example:


 * Since the DaoManager can be accessed via static methods, DAOs can
   get a hold of their own container and manipulate it.  In general,
   this isn't a good practice.  DAOs should never need to access the
   container directly.

 * DAO implementation have to manage their own dependencies.  Right
   now they can do this by manipulating the container (noted above).
   The DaoManager should be able to handle all the DAOs dependencies
   such as inter-DAO dependencies, getting a handle on the local
   transaction, or accessing other external resources all without the
   DAO needing to directly call the DaoManager.

 * The DaoManager provides no special lifecycle support for DAOs
   beyond the basic constructor.

Right now the DaoManager class methods represent an amalgamation of
several aspects:

  1. The DAO Container: the container configures and starts up
     managers and allows clients to access the managers.  Right
     now this would be the current static methods.

  2. The DAO Manager: allows access of DAOs to clients.  This includes
     some of the public methods like getDao(String str) and
     startTransaction()

  3. DAO Internals: there are some public methods which are designed
     for access by DAOs not DAO clients such as getLocalTransaction
     or getInstance(Dao).  Via IoC the need for these methods
     disappears.

Ultimately, refactoring the DAO Framework would split out these
aspects into different classes, ensuring that client code only got
access to what it needed.

There are a couple of steps to apply IoC to the DAO Framework.  I'm
not sure which steps you are interested in applying, but here they
are:

1. Replace DaoManager daoMap with a PicoContainer

PicoContainer is a simple IoC container and I suggest we use it
internally to the DAO Framework.  It's a small library dependency that
gives us huge benefits.

The first step is to replace the current daoMap HashMap in the
DaoManager with a PicoContainer.  This is very simple and I've
attached a patch with does so (you'd also need to add
picocontainer-1.1.jar to the /lib directory).  It makes no changes to
the API and all the tests and examples still pass.

With this patch, DAOs can be inter-dependent and get access to one
another without needing to access the DaoManager. Instead, developers
could use contructor dependency injection. For example, suppose the
ShoppingCartDAO implementation at some point needs access to an AccountDAO instance. Instead of having the ShoppingCartDAO lookup the AccountDAO via the DaoManager, the ShoppingCartDAO would simple include the AccountDAO as a dependency in its contructor:


  public class ShoppingCartDaoImpl implements ShoppingCartDao
           extends BaseDao {

        private AccountDao _accountDAO;

        public ShoppingCartDaoImpl(AccountDao account){
              _accountDAO = account;
        }
  ...

The PicoContainer takes care of figuring out and handling these
dependencies, even circular ones.

2. Introduce a TransactionManager

In order to avoid requiring DAOs to access the local transaction via
the DaoManager (as seen in examples.dao.impl.map.BaseMapDao), a new,
relatively simple class could be introduced called the
TransactionManager.  The DaoManager would have control of the
TransactionManager but DAOs could access the local transaction via
the TransactionManager.

How would the DAOs get a handle on the TransactionManager?  Via
dependency injection again.  The TransactionManager would be added to
the picocontainer during DaoManager initialization and all DAOs would
have access to it by declaring a dependency in their contructor:

  public class ShoppingCartDaoImpl implements ShoppingCartDao
           extends BaseDao {

        private AccountDao _accountDAO;
        private TransactionManager _transManager;

        public ShoppingCartDaoImpl(AccountDao account,
                                   TransactionManager manager){
              _accountDAO = account;
              _transManager = manager;
        }

public void performSomeAction(....){
SqlMapDaoTransaction trans = (SqlMapDaoTransaction) _transManager.getLocalTransaction();
...
}


  ...

3. Include extra-properties in the picocontainer

At this point, the only reason a DAO might need access to the
DaoManager is to access extra-properties.  These two could be added to
the picocontainer just like the transaction manager example above.

What I'd actually like to see is to allow extra-properties to be more
than just Strings.  One option is to allow scripts to create the
extra-properties map.  This allows for some more advanced
configuration of DAOs:

dao.xml:

<dao-factory>
<dao name="Account" implementation="examples.dao.impl.map.AccountMapDao"/>
<dao name="ShoppingCart" implementation="examples.dao.impl.map.ShoppingCartDaoImpl"/>
</dao-factory>
<extra-properties>
<property name="sqlmap.path"
value="com/ibatis/example/persistence/sql-map-config.xml" />
<script name="com/ibatis/example/script/properties.bsh"
</extra-properties>


...

properties.bsh (BeanShell Script)

  import java.util.HashMap;
  import java.io.File;

  HashMap context = new HashMap();
  context.put("name","value");

  File file = new File("src/conf/context-example.xconf");
  context.put("file",file);

  return context;

...

  public class ShoppingCartDaoImpl implements ShoppingCartDao
           extends BaseDao {

        private AccountDao _accountDAO;
        private File _contextFile;

public ShoppingCartDaoImpl(Map extraProperties, AccountDao account){
_accountDAO = account;
_contextFile = (File) extraProperties.get("file");
}
...



The final step would be considering refactoring the DaoManager into two classes: a DaoContainer and a DaoManager. The DaoContainer would include most of the static methods we currently have in DaoManager and would be responsible for the configuration and initialization steps. However, this would be a pretty big change and break the current API (the other steps wouldn't have to), so I'm not sure if the developer team would want to go in this direction.


I hope I still have one or two readers at this point. :) Thanks for taking the time to consider my proposal. I'm not sure if it's anything the iBatis team is interested in, but if so, I'd be willing to do all the patch work and the unit tests.

Thanks again!

jaaron
Index: DaoManager.java
===================================================================
RCS file: /cvsroot/ibatisdb/ibatisdb/src/com/ibatis/db/dao/DaoManager.java,v
retrieving revision 1.21
diff -u -r1.21 DaoManager.java
--- DaoManager.java     10 Jan 2004 01:42:01 -0000      1.21
+++ DaoManager.java     30 Jan 2005 19:35:07 -0000
@@ -8,6 +8,8 @@
 
 import java.util.*;
 import java.io.*;
+import org.picocontainer.MutablePicoContainer;
+import org.picocontainer.defaults.DefaultPicoContainer;
 
 /**
  * DaoManager is a facade class that provides convenient access to the rest
@@ -43,7 +45,8 @@
   protected Properties extraProperties = new Properties();
   protected ThreadLocal localTransaction = new ThreadLocal();
   protected Map daoClassMap = new HashMap();
-  protected Map daoMap = new HashMap();
+//  protected Map daoMap = new HashMap();
+  protected MutablePicoContainer daos = new DefaultPicoContainer();
 
   protected DaoManager() {
   }
@@ -106,7 +109,7 @@
   }
 
   public Dao getDao(String name) {
-    return (Dao) daoMap.get(name);
+    return (Dao) daos.getComponentInstance(name);
   }
 
   public void addDaoClass(String name, String daoClass) {
@@ -221,24 +224,24 @@
       String name = (String) i.next();
       String implementation = (String) daoClassMap.get(name);
       try {
-        Class c = Class.forName(implementation);
-        Object dao = c.newInstance();
-        registerDao(name, (Dao) dao);
+        Class dao = Class.forName(implementation);
+        registerDao(name, dao);
       } catch (ClassNotFoundException e) {
         throw new DaoException("DaoManager could not configure DaoFactory.  
The DAO named '" + name + "' failed. Cause: " + e, e);
-      } catch (InstantiationException e) {
-        throw new DaoException("DaoManager could not configure DaoFactory.  
The DAO named '" + name + "' failed. Cause: " + e, e);
-      } catch (IllegalAccessException e) {
-        throw new DaoException("DaoManager could not configure DaoFactory.  
The DAO named '" + name + "' failed. Cause: " + e, e);
       } catch (Throwable t) {
         throw new DaoException("DaoManager could not configure DaoFactory.  
The DAO named '" + name + "' failed. Cause: " + t, t);
       }
     }
+    daos.start();
+    List daoList = daos.getComponentInstances();
+    for(int j =0; j < daoList.size(); j++){
+       daoManagerReverseLookup.put(daoList.get(j),this);
+    }
   }
 
-  protected void registerDao(String name, Dao dao) {
-    daoMap.put(name, dao);
-    daoManagerReverseLookup.put(dao, this);
+  protected void registerDao(String name, Class dao) {
+    daos.registerComponentImplementation(name, dao);
+ //   daoManagerReverseLookup.put(dao, this);
   }
 
   protected static void registerDaoManager(Object contextName, DaoManager 
daoManager) {

Reply via email to