In the rich domain model recommended by Martin Fowler, Persistence technologies such as Hibernate are responsible for instantiating domain objects that implements domain logic. Sometimes, it is necessary for a domain object to have some dependencies on other modules of the system to finish the job that the domain logic requires.
The persistence layer has no way to know about such dependencies in order to inject the right objects to them. So it becomes a problem to resolve the dependencies for these domain objects.
It is always possible to set these dependencies manually, for example:
However, such manual approach may become difficult when the dependencies to inject scale up. It is then a natural thought to try to use the container for the job because it is already a dependency injection framework.
Some propose to use AOP together with IOC container such as Spring to get the dependencies set up automatically. It looks like a handy approach but we are not sure if this is a perfect fit for AOP because the dependency injection here is really a necessary part of the integral work, rather than a cross-cut concern. The introduction of AOP in this senario makes the code less understandable and less unit-testable because we will always have to have AOP in place to keep the domain object in a consistent state.
In this article we will show a solution without using AOP.
Dependency Injection with no dependency on container
The idea is simple: it would be nice if the Data Access Object returns us a domain object with all the dependency works done.
We can then just forget about the "dependency headache" and focus on the domain logic.
This class does not require AOP to work. We can always opt to do manual-injection in the Dao injected into this object. Thus, it is easy to understand and unit-testable.
Suppose we have BankAccountDao interface designed as:
And a few implementation class such as HibernateBankAccountDao, JdbcBankAccountDao, XmlBankAccountDao, etc.
It is typically not a good design though to force these implementations to set the dependencies. These dependencies are a concern of the domain logic, not part of the persistence layer.
Yep, I hear ya. You mean I'm contradicting myself now:
- The service layer expects the Dao object to set the dependencies.
- While the Dao implementations don't want to even know about the dependencies.
Well, not really. If neither party wants to do it, and they have their reasons, we can always bring in a 3rd party who's willing to do the job.
I'll create a proxy class of Dao to set dependencies for the domain objects. A naive implementation can be:
A few draw backs with this implementation are:
- Dependency injection is all manual. This is error prone and not configurable.
- Every method in BankAccountDao that returns BankAccount will need to be proxied. Even those not returning BankAccount need a line of code to forward control. This is no interesting work.
- Similar code needs to be copy-paste-modified for many other kinds of DAOs. BankDao, LenderDao, InvestmentDao, blah blah blah.
This naive implementation could still be useful during unit-testing. But we need to look for a more generic mechanism that's free of the drawbacks mentioned above.
For the 2nd and the 3rd problem, dynamic proxy is the key. Don't worry if you don't already know dynamic proxy. Yan hides the complexity of it for you.
For the 1st problem however, some kind of dependency management technique needs to be used.
Next, let's see the solution that Yan offers.
Inject dependency from container
Yan can inject dependencies into objects that are not managed by Yan. Such logic can be implemented in a Binder object.
Here's an example:
This Binder object will create a Component object that injects dependency for the "smtpService" property to obj.
Next, the helper class InjectorHelper gives us everything we need:
- new InjectorHelper() creates an InjectorHelper object.
- helper.getProxyComponentReturningInjected(...) creates a Component object that instantiates a proxy. This proxy object implements the BankAccountDao interface, and injects dependencies on every return value whose type is "BankAccount" or its sub-type.
- The 1st parameter of getProxyComponentReturningInjected() is the target interface that the proxy should implement. In this case, we are trying to implement BankAccountDao interface.
- The 2nd parameter is a Component that instantiates the object to be proxied.
- The 3rd parameter is the filter type. Only the dependencies of return values of this type gets injected.
- The last parameter is the Binder object encapsulating the injection logic.
- Finally, the Component that instantiates proxy is configured as the first parameter of the constructor of BankAccountService.
What if more than one return types need injection?
We don't know if this is practical. But in case you have a Dao interface that returns more than one domain object types, you can always call the getProxyComponentReturningInjected() method once for each different injectable return type.
In the above example, getProxyComponentReturningInjected() is called once to proxy for the methods returning BankAccount, and it is called yet another time to also proxy for those methods returning Bank. And the result is a proxy that injects dependency for both BankAccount and Bank.
What about those domain objects not created by Dao?
The solution presented above wraps around Dao objects to provide dependency injection. But what about those objects not created by Dao? What if class BankAccountUser elects to create BankAccount object directly?
Obviously the above code won't work because it attempts to use smtp service when the dependency is not set up yet.
For such requirement, we recommend to use abstract factory and avoid direct use of "new". For example:
By accepting injection for the factory object, we can expect a proper implementation of BankAccountFactory handles dependency injection transparently for us.
A simple implementation of BankAccountFactory may use manual injection:
And when configured by Yan, a factory implementation that takes advantage of the container can be injected:
- xxx.factory(BankAccountFactory.class) creates a Compoennt object who will instantiate a BankAccountFactory object. This BankAccountFactory object uses the object creation logic encapsulated in the Component xxx to create BankAccount, which, sets the dependencies.
Configure through XML
In this article, we presented a simple dependency injection solution for rich domain objects. The writers of the domain objects, dao and service layer do not need to worry about the dependencies. Code can be written as if the dependencies are handled by Dao.
Out of Yan's flexible dependency injection framework, dependencies of the rich domain objects can be injected via a dynamic proxy of the Dao interfaces. Such injection, same as any other injections, is configured through the container and is totally transparent to the domain object writers and Dao writers.
This way, dependencies can be set in a simple and transparent way without the cost of a service locator and a mandatory dependency on the container that many solutions today in the industry require.
Created by benyu benyu
On Mon Oct 31 07:43:51 CST 2005