Rich Domain Injection

In the Transparent Dependency Injection for Rich Domain Objects, I presented a way to create proxy of the Data Access Objects and transparently inject dependencies managed by container.

The example in that article uses direct Java code, which is not very practical. In this article, we'll translate the solution into Nuts.

Analysis

Firstly, let's analyze the problem. What we need are:

  1. The name of the Dao interface(s) to proxy.
  2. The name of the domain object types to inject dependencies to.
  3. The injection logic for each different domain object type.

And the tools we have are:

  • <binder> tag to describe injection logic.
  • The optional <typecase> tag to glue different injection logics together.
  • The optional <inject> tag to create proxy for injecting dependencies.
  • The optional <if> and <same> tag to check if the dependency is already set.
  • The <synchronized> tag to provide thread safety.
  • The optional <foreach> tag to traverse elements in an array or collection.

Secondly, let's do a brief rephrase of the problem to be resolved.

  • We have a BankAccountDao that could return BankAccount and Bank objects.
  • BankAccount object has a dependency on SmtpService. This needs to be injected.
  • Bank object has a dependency on IRSService. It needs to be injected as well.

The java code that highlights the problems:

public interface BankAccountDao{
  BankAccount getAccountById(...);
  Bank readBank(...);
  List getAccounts();
  ...
}
public class BankAccount{
  ...
  public SmtpService getSmtpService(){
    return smtp;
  }
  public void setSmtpService(SmtpService smtp){
    this.smtp = smtp;
  }
}
public class Bank{
  ...
  public IRSService getIRSService(){
    return irs;
  }
  public void setIRSService(IRSService irs){
    this.irs = irs;
  }
}

Solution Step by Step

Finally, with all the problems laid out and armed with tools, let's roll.

The injection logic for BankAccount is:

<binder id="BankAccount_injection" var="account">
  <getter component="$account" name="smtpService" var="old"/>
  <same val1="$old" val2="$null" var="isnull"/>
  <if cond="$isnull">
    <then>
      <setter component="$account" name="smtpService" val="$smtpservice"/>
    </then>
  </if>
</binder>

In order not to repeatedly set the smtp service, and to show how complex logic can be expressed in Nuts, we check the property first. Only when the property is null do we call the setter.

The injection logic for Bank looks very similar:

<binder id="Bank_injection" var="bank">
  <getter component="$bank" name="iRSService" var="old"/>
  <same val1="$old" val2="$null" var="isnull"/>
  <if cond="$isnull">
    <then>
      <setter component="$bank" name="iRSService" val="$irsservice"/>
    </then>
  </if>
</binder>

.
Don't worry about the "$irsservice" and "$smtpservice" yet. They are just two global components that you are gonna see.

Because the proxy of BankAccountDao needs to inject for both Bank and BankAccount, we need to glue the two injection logic together:

<typecase id="injection">
  <case type="Bank" binder="$Bank_injection"/>
  <case type="BankAccount" binder="$BankAccount_injection"/>
</typecase>

Still not done yet. Since the read and write of the same property are not one atomic operation, we need to synchronize to ensure thread safety:

<synchronized id="safe_injection" binder="$injection"/>

So far, the "getAccountById" method is taken care of. But what about the "getAccounts" method? It returns a "java.util.List" object. The <setter> tag obviously doesn't work on a List.

The first time when I thought about this, I was like:"Hmm, that's ugly...". But it didn't hold me long. Nuts is an extensible system, all we have to do is to create a custom tag. The tag I created was a bit more general than just for this requirement. It is the <foreach> tag that can loop over every element in an array/Collection/Map.

The java pseudo code is:

inject(Object v){
  if(v instanceof Object[]){
    foreach(e:v){
      inject(e);
    }
  }
  else if(v instanceof List){
    foreach(e:v){
      inject(e);
    }
  }
  else{
    inject_safe(v);
  }
}
inject_safe(Object v){
  synchronized(v){
    if(v instanceof Bank){
      inject_bank(v);
    }
    else if(v instanceof BankAccount){
      inject_bankaccount(v);
    }
  }
}

As you may have noticed, the pseudo code has a recursion. It is for dealing with multi-dimensional collections such as list of list, array of list, array of array etc.

Using <foreach>, the code becomes:

<typecase id="final_injection">
  <case type="java.lang.Object[]" binder="$loop"/>
  <case type="java.util.Collection" binder="$loop"/>
  <default binder="$safe_injection"/>
</typecase>
<foreach id="loop" binder="$final_injection"/>

Ok, the injection logic is done. Now we weave it into the BankAccountDao:

<ctor id="original_dao" class="BankAccountDaoImpl"/>
<inject id="rich_dao" type="BankAccountDao"
    component="$original_dao" injectee="java.lang.Object"
    injection="$final_injection"
/>

When wiring up objects, we use the "rich_dao" instead of the "original_dao". And the dependency is automatically set!

Put it together

The complete xml looks like: (To make it more interesting, we assume that the smtpservice and irsservice objects are from a jndi registry.)

<module name="rich domain injection test"
  export="smtpservice, irsservice, rich_dao"
>
<nut name="if" class="jfun.yan.xml.nuts.optional.IfElseNut"/>
<nut name="same" class="jfun.yan.xml.nuts.optional.SameNut"/>
<nut name="typecase" class="jfun.yan.xml.nuts.optional.TypeCaseNut"/>
<nut name="inject" class="jfun.yan.xml.nuts.optional.InjectNut"/>
<nut name="foreach" class="jfun.yan.xml.nuts.optional.ForeachNut"/>

<body>
  <method id="jndi" class="JndiFactory" name="create" singleton="true"/>
  <method id="smtpservice" component="$jndi" name="lookup">
    <arg ind="0" val="smtpservice"/>
  </method>
  <method id="irsservice" component="$jndi" name="lookup">
    <arg ind="0" val="irsservice"/>
  </method>
  <inject id="rich_dao" type="BankAccountDao"
      component="$original_dao" injectee="java.lang.Object"
      injection="$final_injection">
    <local>
      <ctor id="original_dao" class="BankAccountDaoImpl"/>
      <binder id="BankAccount_injection" var="account">
        <getter component="$account" name="smtpService" var="old"/>
        <same val1="$old" val2="$null" var="isnull"/>
        <if cond="$isnull">
          <then>
            <setter component="$account" name="smtpService" val="$smtpservice"/>
          </then>
        </if>
      </binder>
      <binder id="Bank_injection" var="bank">
        <getter component="$bank" name="iRSService" var="old"/>
        <same val1="$old" val2="$null" var="isnull"/>
        <if cond="$isnull">
          <then>
            <setter component="$bank" name="iRSService" val="$irsservice"/>
          </then>
        </if>
      </binder>
      <typecase id="injection">
        <case type="Bank" binder="$Bank_injection"/>
        <case type="BankAccount" binder="$BankAccount_injection"/>
      </typecase>
      <synchronized id="safe_injection" binder="$injection"/>
      <typecase id="final_injection">
        <case type="java.lang.Object[]" binder="$loop"/>
        <case type="java.util.Collection" binder="$loop"/>
        <default binder="$safe_injection"/>
      </typecase>
      <foreach id="loop" binder="$final_injection"/>
    </local>
  </inject>
</body>

</module>

Created by benyu
On Sat Nov 12 18:40:00 CST 2005
Using TimTam

Labels

 
(None)