Value Object Dispatcher Pattern (Revised)

Discussions

J2EE patterns: Value Object Dispatcher Pattern (Revised)

  1. Value Object Dispatcher Pattern (Revised) (17 messages)

    First off, sorry I was so incoherent when I tried to describe this pattern the first time. Hopefully, this will go a little bit better.

    http://www.xmission.com/~mopar/voDispatcher_class_model.png

    I will now describe the function of each class and its purpose. I added a few sample classes in the model to illustrate how it would be used in an application.

    The main classes to the core framework are :
    1. MutableVOController - This is an interface containing all of all the basic methods used in a persistence model.
    2. BusinessDelegate - This is an interface extending the MutableVOController and contains methods for working with larger datasets. Allows for bulk inserts, deletes, etc. Proxies and EJB Business Delegates should extend/implement this.
    3. VODispatcher - This is the router where all the calls go to. This class will implement the MutableVOController methods. This class takes a methodod call from somewhere, generally a Business EJB/Facade Bean or compound DAO, along with a corresponding value object. It asks the value object(MutableVO) who its controller class is (This class is a variable set in the constructor and contained within the MutableVO parent class). The voDispatcher gets this fully qualified name as a string, creates an instance of that class, generally through some sort of DAOFactory, and calls the corresponding method on the controller passing the value object through as its parameter. If the method inside the dao is one other than one defined in the MutableVOController interface, there is a generic find method that takes as parameters a MutableVO in order to find the controller, a String with the method name, and an Object[] as parameters. The VODispatcher then uses reflection to find the correct method inside the controller class ant pass the parameters into it. This is useful when not finding objects when not looking up by a primary key field in the database.
    4. AbstractDAO - This class also implements the MutableVOController class and adds some functionality to the DAO's that extend it. It defines an abstract createVO method, and anything else that may be valuable to your DAO's.
    5. MutableVO - This class is the base class for all of the value objects in the system. This could be further refined for immutable value objects, etc. As you can see, this object contains all the information required to persist an object. It has a dirty flag, primary key field which is currently just defined as Object, it could be anything relevant, and a controller class. The controller class is set at runtime in the constructor of the value object extending this class. This is what makes this pattern work for the most part, but also removes compile-time checking. The fully qualified class name of the vo controller or dao would be specified here so that when the vo dispatcher askes for its controller, it can pass it through. This does make the dao - vo relationship very fine grained, but leaves room to have the value objects themselves very coarse grained and capable of utilizing multiple tables in a database, multiple cmp beans, or whatever else you desire.

    Thats pretty much it as far as what is required for this pattern to work for you. The pattern is flexible enough to utilize the dispatcher as a bean, or an ejb. The dao's while not consistent with the true meaning of dao can also be beans or ejb's. They can also perform in the traditional role of dao through jdbc, or they can be a proxy themselves to another ejb/ejb's, other app server, web service, flat file, etc. This allows for great flexibility in not being tied to a specific datasource. It also allows for great flexibility in controlling compound value objects and value objects with multi-field primary keys.

    I added a few more classes just as examples to show how I have implemented this pattern in my own system. I utilize data caching which has shown performance increases of 20x in retrieval of large collections of value objects on my system.
    1. CoreVODispatcher - This class extends the main VODispatcher class and adds the data caching functionality. This class has been created in such a way as to allow me to utilize a 3rd party caching mechanism, remote caching, or just build my own at any time with a very minimal impact on the rest of the system.
    2. CacheableMutableVO - This class extends the main MutableVO class and allows me to determine which value objects I want to cache.


    I'm sure this could be streamlined a bit more, and I've probably overlooked many items. That is why would welcome any comments. Hopefully I have explained myself a bit better this time. If you have any more questions, I will try and help as much as I can. I will consider this thread the main thread and hope the old one will just be disposed of.

    Thanks

    Threaded Messages (17)

  2. One of the most useful and well-used design patterns, it's time to change the name of this design pattern. Sun has changed this to <italic>Transfer Object</italic>. Martin Fowler's book, Patterns of Enterprise Application Architecture calls this pattern <italic>Data Transfer Object</italic>. Value Object is an old name, used for years to mean something very different. For years, I called this guy <italic>Data Carrier</italic>. However, I'm happy to live with the current verbiage as long as we can settle the controversy.
  3. I can't agree more with Chuck ...... I still remember when I learned the Value Object pattern, I felt very strange to name the instance VO. Data Transfer Object is just more appropriate and intuitive.
  4. DTO Dispatcher would fit just as well. After all I am actually trying to describe a pattern to dispatch DTO's in a meaningful way.

    Thanks.
  5. I was thinking "Jiggly Puff" would actually be a better name.

    Sorry, I had to throw that in :-P

    SAF
  6. CMP2 finder methods?[ Go to top ]

    Can you still use the J2EE container's CMP2 xml-configured finders for entity attributes?
  7. That shouldn't be any problem[ Go to top ]

    The way I have tried to set this up is that if you decide to use cmp entity beans, the DAO at that point becomes a wrapper. You still have the call through the system, and the DAO then makes the calls into the cmp bean and does the data translation between the cmp ejb and the vo. This way, the rest of the system doesn't need to know if you are using cmp, web services, corba, jdbc, etc. The DAO can function in the traditional sense or it can just be a wrapper. This should allow the flexibility to utilize any data source without requiring the rest of the system to know the details of where or how that data is retrieved.
  8. sample implementation[ Go to top ]

    Can we have have its sample implementation
  9. sample implementation[ Go to top ]

    public CMPUserDAO extends AbstractDAO
    {
      public MutableVO findByPrimaryKey(MutableVO obj) throws Exception
      {
        Object primaryKey = obj.getPrimaryKey();
        // Get Initial Context and Call the correct CMP EJB's Find By Primary Key Method.
        // Take the EJB and Populte the UserVO Value Object/Data Transfer Object.
        // Return the Value Object/DTO.
      }

      public MutableVO insert(MutableVO obj) throws Exception
      {
        // Get the Initial Context and Call corresponding CMP EJB create/insert method.
        // Populate value object with the data from the new EJB.
        // Return the Value Object/DTO so the system can verify it has been inserted successfully and cache it or do whatever.
      }
    }

    This is pretty crude, but should give you a basic idea of how it works. If you ever change from CMP EJB's to say a web service, jdbc, corba, etc., all you do is change this access code inside the DAO, and it works throughout the system. The system can be using multiple ways to access data, and it shouldn't affect the rest of the system.

    Hope this helps a bit.

    Later

    Nic
  10. How can this be extended to make the VOs coarse grained to make them capable of utilizing multiple tables?
  11. That is all controlled within the DAO. The VO is just the transport mechanism. The DAO is the controller class for the VO. You can pull from 5 tables if you want and assign the data to the VO when you build it. I have used queries with joins in several DAO's. Granted, it is easier to have these particular types of objects be read-only, you can still allow them to be updated and such. You just need to handle that logic in the DAO.
  12. This is not meant to be exact code.... Just an example.
    UserDAO
    {
      private static final String FIND_BY_LAST_NAME_QUERY = "Select userid from users where lastname = ?"

      private static final String FIND_BY_USERID_QUERY = "Select a.userid, a.name, a.address, a.phone, b.accountNum from users a, accounts b where a.userid = b.userid and a.userid = ?";

      findByPrimaryKey(String userId)
      {
        //Connect to Database
        //Initiate the query with the FIND_BY_USERID_QUERY string.
        //Pass the resultset to createVO to populate the Value Object
        //Return the VO to the client.
      }

      findUserByLastName(String lastName)
      {
        //Connect to the Database
        //Initiate the query with the FIND_BY_LAST_NAME_QUERY string.
        //Call voDispatcher.findByPrimaryKey(new UserVO(rs.getString(1));
        //This way it checks to see if the data is already cached(If you're caching the data) so we don't have to build it, and it simplifies the query process. I used to build them all, but this actually sped things up as much as 20x by not populating vo's every time even though I still ran a simple query.
      }

      createVO(ResultSet rs)
      {
        //Populate the VO and return it.
      }
    }

    Hopefully this will give you a general idea. I'm not always the greatest at putting my thoughts to paper, so I hope I made sense.
  13. Coarse grained VOs[ Go to top ]

    I am going to implement framework based on this design pattern.
    It is very simple idea: POJO + dynamic code generation/reflection + metadata in javadoc.
    It looks like this:

    class MyBean{
     //fields ...
     String getName(){
       return name;
     }
     int getValue(){
        return value;
      }

    }

    interface MyDAOFactory{

       /**
       * @query SELECT FLD_NAME AS name , FLD_VALUE AS value
       * FROM MY_TABLE WHERE id=$1
       * @handler bean
       */

       MyBean findByPrimaryKey(int key);

      /**
      *@query SELECT myStoredProcedure($1.name,$2.value)
      */
       int myStoredProcedure( MyBean bean );

    }

    We have all info in this interface to generate implementation:

    MyDTOFactory factory = (MyDTOFactory)Db.getProcedures(MyDTOFactory);

    Execute queries or stored procedures:

    MyBean bean = factory.findByPrimaryKey(777);
    int result = factory.myStoredProcedure( bean );

    End transaction and release resources:

    Db.close();

    Cache or Authorization can be implemented using custom callbacks(Interceptors).

    I have used property files to map SQL statements to methods before to start this implementation,
    but I found javadoc much easy to read/write.

    Visit http://voruta.sourceforge.net,
    this experiment is not for production at this time, but I believe it will be interesting.
  14. why would the CoreBusinessMethods and hence the CoreFacade session bean extend/implement the VOController? Isn't the facade supposed to use DAO? or was it meant to be Entity? I think I am missing something?

    Are you looking to add additional functionalty into Cacheable and CacheableMutableVo? or is it meant to be just data type interfaces?
  15. They all implement the interface so that you are sure to be able to utilize the dao methods from wherever you are. From proxy, you should just need to say proxy.insert(valueObject) and it should be able to pass through the architecture and be inserted properly. I mainly implement it so I don't forget to put the MutableVOController methods in the facade bean and proxy.

    As for the caching and cacheable value objects, I have re-engineered it to use keys/cacheablekeys that are tied to the value objects and hold just primary key information. Now when I call findByPrimaryKey, I just give it a key. It also helped to slim down the object relationships. Now I don't pass all the data from a value object inside another vo. I just set the key and if I need that object, I just call for it over the wire. I had way too much data to have direct references to value objects within value objects.
  16. Here is an example of how my value objects / keys relate and have been redone as well as a new MutableVOController interface, MutableVO, Key, and CacheableKey. As everybody knows, new architectures are always a work in progress. I have also modified plugins for IntelliJ Idea for generating the equals(), hashCode(), and verify() methods inside the keys. Hopefully, I am improving on my original designs. I welcome any feedback/questions.

    Thanks

    Nic


    public class Ach extends MutableVO implements Serializable
    {
        private String acctHolder;
        private Character acctType;
        private String achAcct;
        private Character achType;
        private BankPK bank;
        private BatchPK batch;
        private Calendar createDate;
        private ReasonPK reason;
        private Boolean repetitive;
        private String status;
        private AccountPK account;
        private UserPK user;
        private UserPK verifiedBy;
        private String verifyCode;
        private RepetitivePK repetitiveInfo;

        protected Ach()
        {
            super();
        }

        public Ach(AchPK primaryKey)
        {
            this();
            if (primaryKey != null)
                setKey(primaryKey);
            else
                setKey(new AchPK());
        }

        public Integer getAchId()
        {
            return ((AchPK)getKey()).getAchId();
        }

        public AccountPK getAccount()
        {
            return this.account;
        }

        public void setAccount(AccountPK account)
        {
            this.account = account;
            setDirty(true);
        }

        public BatchPK getBatch()
        {
            return batch;
        }

        public void setBatch(BatchPK batch)
        {
            this.batch = batch;
            setDirty(true);
        }

        public Calendar getCreateDate()
        {
            return this.createDate;
        }

        public void setRepetitiveInfo(RepetitivePK repetitiveInfo)
        {
            this.repetitiveInfo = repetitiveInfo;
        }

        public void setCreateDate(Calendar date)
        {
            this.createDate = date;
            setDirty(true);
        }

        public String getAchAcct()
        {
            return this.achAcct;
        }

        public void setAchAcct(String acct)
        {
            this.achAcct = StringUtil.blankIfNull(acct);
            setDirty(true);
        }

        public BankPK getBank()
        {
            return bank;
        }

        public void setBank(BankPK bank)
        {
            this.bank = bank;
            setDirty(true);
        }

        public String getAcctHolder()
        {
            return this.acctHolder;
        }

        public void setAcctHolder(String holder)
        {
            this.acctHolder = StringUtil.blankIfNull(holder);
            setDirty(true);
        }

        public Character getAcctType()
        {
            return this.acctType;
        }

        public void setAcctType(Character type)
        {
            this.acctType = type;
            setDirty(true);
        }

        public Character getAchType()
        {
            return this.achType;
        }

        public void setAchType(Character type)
        {
            this.achType = type;
            setDirty(true);
        }

        public Boolean isRepetitive()
        {
            return repetitive;
        }

        public void setRepetitive(Boolean repetitive)
        {
            this.repetitive = repetitive;
            setDirty(true);
        }

        public RepetitivePK getRepetitiveInfo()
        {
            return repetitiveInfo;
        }

        public UserPK getUser()
        {
            return user;
        }

        public void setUser(UserPK user)
        {
            this.user = user;
            setDirty(true);
        }

        public UserPK getVerifiedBy()
        {
            return verifiedBy;
        }

        public void setVerifiedBy(UserPK verifiedBy)
        {
            this.verifiedBy = verifiedBy;
            setDirty(true);
        }

        public String getVerifyCode()
        {
            return this.verifyCode;
        }

        public void setVerifyCode(String code)
        {
            this.verifyCode = StringUtil.blankIfNull(code);
            setDirty(true);
        }

        public String getStatus()
        {
            return this.status;
        }

        public void setStatus(String status)
        {
            this.status = StringUtil.blankIfNull(status);
            setDirty(true);
        }

        public ReasonPK getReason()
        {
            return reason;
        }

        public void setReason(ReasonPK reason)
        {
            this.reason = reason;
            setDirty(true);
        }
    }


    public class AchPK extends CacheableKey
    {
        private Integer achId;

        public AchPK()
        {
            setControllerClass("com.tab.core.server.dao.AchDAO");
        }

        public AchPK(final Integer achId)
        {
            this();
            this.achId = achId;
        }

        public Integer getAchId()
        {
            return achId;
        }

        public void setAchId(Integer achId)
        {
            this.achId = achId;
        }

        public final String toString()
        {
            StringBuffer buffy = new StringBuffer("com.tab.core.shared.vo.AchPK {");

            buffy.append("achId=");
            buffy.append(achId);
            buffy.append("}");

            return buffy.toString();
        }

        public boolean equals(Object obj)
        {
            AchPK cast = null;

            if (obj == null)
                return false;
            if (obj == this)
                return true;

            if (!(obj instanceof AchPK))
            {
                if (obj instanceof MutableVO)
                    cast = (AchPK) ((MutableVO) obj).getKey();
                else
                    return false;
            }
            else
                cast = (AchPK) obj;

            if (achId == null && cast.achId == null)
                return true;
            if (achId == null || cast.achId == null)
                return false;
            if (!(achId.equals(cast.achId)))
                return false;
            return true;
        }

        public final int hashCode()
        {
            int _hashCode = 0;
            if (validate())
            {
                _hashCode = 29 * _hashCode + achId.hashCode();
            }
            return _hashCode;
        }

        public final boolean validate()
        {
            if (achId == null)
                return false;
            else
                return true;
        }
    }





    public interface MutableVOController
    {
        public Key insert(MutableVO obj) throws Exception;
        public int delete(Key obj) throws Exception;
        public int deleteAll(Key obj) throws Exception;
        public int update(MutableVO obj) throws Exception;
        public List findAll(Key obj) throws Exception;
        public MutableVO findByPrimaryKey(Key obj) throws Exception;
    }


    public abstract class MutableVO implements Serializable
    {
        private Key primaryKey = null;
        private Boolean dirty = null;

        protected MutableVO()
        {
            setDirty(false);
        }


        public MutableVO(final Key primaryKey)
        {
            this();
            setKey(primaryKey);
        }

        public final Key getKey()
        {
            return primaryKey;
        }

        public final void setKey(final Key primaryKey)
        {
            this.primaryKey = primaryKey;
        }

        public final void setDirty(final boolean dirty)
        {
            this.dirty = new Boolean(dirty);
        }

        public final boolean isDirty()
        {
            return dirty.booleanValue();
        }

        public final boolean equals(Object obj)
        {
            return getKey().equals(obj);
        }
    }


    public abstract class Key implements Serializable
    {
        private String controllerClass = null;

        public final String getControllerClass()
        {
            return controllerClass;
        }

        protected final void setControllerClass(final String controllerClass)
        {
            this.controllerClass = controllerClass;
        }

        public abstract boolean validate();
        public abstract int hashCode();
        public abstract boolean equals(Object obj);
    }


    public abstract class CacheableKey extends Key implements Cacheable
    {
        //Set the default cache time to 2 days.
        private static final int OBJECT_CACHE_MINUTES = 2880;
        private Date dateofExpiration = null;
        private Long sequenceNumber = null;

        protected CacheableKey()
        {
            super();
            if (dateofExpiration == null)
                setMinutesToLive();
        }

        public CacheableKey(final int minutes)
        {
            this();
            setMinutesToLive(minutes);
        }

        public final void setMinutesToLive()
        {
            setMinutesToLive(OBJECT_CACHE_MINUTES);
        }

        public void setMinutesToLive(final int minutes)
        {
            if (minutes > 0)
            {
                dateofExpiration = new java.util.Date();
                Calendar cal = java.util.Calendar.getInstance();
                cal.setTime(dateofExpiration);
                cal.add(cal.MINUTE, minutes);
                dateofExpiration = cal.getTime();
            }
        }

        public final boolean isExpired()
        {
            // Remember if the minutes to live is zero then it lives forever !
            if (dateofExpiration != null)
            {
                // date of expiration is compared.
                if (dateofExpiration.before(new java.util.Date()))
                    return true;
                else
                    return false;
            }
            else
            {
                return false;
            }
        }

        /**
         * @param sequenceNumber The unique sequence number assigned to this value
         * object when it is placed into the cache.
         */
        public final void setSequenceNumber(final Long sequenceNumber)
        {
            this.sequenceNumber = sequenceNumber;
        }

        /**
         * @return The sequence number for this value object when it was placed in
         * the cache. This number must match the cached sequence number
         * for an update to take place. Otherwise, this value object has
         * been updated previously by another source, and should be re-loaded
         * before an update can occur.
         */
        public final Long getSequenceNumber()
        {
            return sequenceNumber;
        }
    }
  17. It looks nice! But I've some doubts!

    1 - I'm not sure if I understand why the setControllerClass() is in the PK!

    2 - Why do you use the fully qualified name of the controler class instead of its Class object?

    Thx

    EMCAO
  18. Tweaked[ Go to top ]

    I have since incorporated Spring into the design along with hibernate so I was able to remove the caching stuff and just use the name of the key class as the lookup for the controller / dao. It is much simpler now and works pretty slick. Sorry I didn't replay before. I hadn't looked at this thread for quite some time... lol