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) {
