Wednesday, December 30, 2015

Integrator In-Memory Database - Makes Integration Even Easier

Overview

Recent RedPrairie Integrator versions include an embedded H2 database that opens up new possibilities for efficient and simple integration projects.  The basic components provided are:
  • create internal table
  • insert into internal table
  • select from internal table
    • This command asks for a table_name as a parameter - but we can pass a complete join clause as well.
  • release internal tables
In some respects the idea is similar to caching discussed in another post - but the added functionality that helps in integration is the ability to apply complete SQL operations.

As of the writing of this blog, this functionality is less known and primarily used internally by RedPrairie integrator itself to abstract the inbound IFD as a table and for "Expression on Child Segment" IFD Field class - but it can come in handy in other cases as well.

Complex Mapping Scenario

If the inbound structure closely matches the RedPrairie concepts - then nothing special is needed - but in some cases the structures can be quite different.  In my experience the various SAP IDOCS present the data in a large number of mutually connected segments, for example:

Here the left side represents an incomplete view of a delivery in SAP.  Right hand side represents an order in WMS.  As you can see the SAP view is a lot more verbose and it needs to transform basically down to a order and order lines.

How to handle this mapping in standard integrator?

This type of mapping is not a problem for Integrator.  It has been able to handle it since the earliest versions.  Some of the tools available are;
  • Ability to access any inbound IFD field as an EO Column
  • Create EO segments at the same level as inbound IFD
  • Using "Expression on Child Segment" concept on the final result IFD.
Some less experienced implementers end up creating intermediate tables to address this type of mapping - that is not needed.  We can map it in one shot, i.e. inbound IFD goes via EO creation to the result IFD which looks like the right hand side.

The result is that the integration works but can be a resource hog.  Basically every time an EO segment instance is created - it has to push a subset of the inbound IFD to the internal database (it used to be actual tables and now it is the embedded H2 database) and then system allows for executing SQL against it.  As described above the traditional solution would have required several EO segments parallel to the various inbound IFD segments, e.g.

Once these additional EO segments have been created - the final result IFD will use "Expression on Child Segment" to create the final fields.  Each of these IFD fields will require integrator to push the EO segment data to the internal database so that SQL can be applied.  Lets say you have 10 order line fields that have to be created in this fashion - it will require the EO data to be pushed to the internal database 10 times for the same order line.  As you can see this can quickly become a performance issue.

Internal Database to the rescue

At a very basic level - the above discussion highlights the key deficiency in integrator - that it does not allow for joining data from multiple inbound segments.  So creating the intermediate EO segments is a way to circumvent that.  Using the internal database we can overcome this and simply load the inbound IFD to internal databases.  Afterwards we can simply join the data so that the EO also matches the result IFD:
The basic difference is that we have introduced a high level segment that would load all of the relevant inbound IFDs to internal database as tables.  And at the end we would release those tables.  The retrieve method implementation of the UC_LOAD_HOST_ORD will utilize the "create internal table" and "insert into internal table" commands.  
    First we need to create a component that would return a resultset for each inbound IFD segment that we are interested in. For example lets call that component "get usr sap order segments as rs". It could be implemented as:
    /*
     * During integrator execution - ifd_data_ptr is on stack
     * so it does not need to parse the inbound again.  But
     * in case it is not on stack, e.g.
     * for testing then we get it via standard
     * integrator command.
     */
    if ( @ifd_data_ptr#onstack )
       hide stack variable where name = 'ifd_data_seq'
    else
       sl_get ifd_data_ptr 
    |
    {
       /*
        * If an inbound IFD is optional - we need to still
        * create an empty recordset for it
        */
        ... create empty recordsets
        |
        /*
         * Now fetch the inbound IFD Data into a
         * recorset, e.g. lets say inbound IFD
         * segment is called MYSAPHDR - we will say
         */
       {
          {
             sl_list ifd_data_fld <
             where ifd_seg_id = 'MYSAPHDR'
             >> uc_sap_mysaphdr
          }
       }
       |
       ... so on.  Get every relevant inbound IFD
       ... segment to a recordset.
       ... This step is extremely efficient as it
       ... is simply looking at parsed 
       ... view of inbound IFD from memory.  
       ... If an inbound IFD was optional, e.g.
       ... order notes - then you still need
       ... to create a valid recordset with 1 row.
    }
    
    Now we can create another command say "create usr sap order tables" as follows:
    get usr sap order segments as rs
    |
    {
       release internal tables
       ;
       create internal table
       where table_name = 'uc_sap_mysaphdr'
       and res = @uc_sap_mysaphdr
       ;
       ... so on for all tables
    }
    
The UC_RELEASE_HOST_ORD will simply call "release internal tables" command.

With that in place the ORDER_HDR, ORDER_DETAIL etc. will no longer utilize the generated retrieve method implementation that says "where sl_data_fk = :sl_data_pk" instead it will call a MOCA Command  that will utilize "select from internal table" to get data from multiple inbound IFD segments utilizing joins - which is extremely efficient.
    This will utilize the several tables created earlier and access the data from there. It can utilize complete SQL language including group and join clauses:
    {
       select from internal table
       where select_stmt =
         "select ... any columns from any table ...."
       and table_name = 
         " uc_sap_mysaphdr join .. left outer join .." 
    }
    

Some Gatchas

  • Note that the internal tables are created directly from the recordset.  If the recordset is empty, the table cannot be created as it does not have the column information.  That is why we need to have an empty table in that case.  The "sl_list ifd_data_fld" command returns ifd_data_seq and ifd_data_dtl_seq in the recordset.  So you can create the empty recordset with these values set as 0.  That way the recordset will not be empty but when you access it for the particular ifd_data_seq you will not get a row - which is what we wanted.
  • If this functionality is used, then all segments below UC_LOAD_HOST_ORD must use this and not the traditional "sl_data_fk = :sl_data_pk" retrieve methods.  This is because when creating the tables we called "release internal tables" and that also killed the table that integrator had created for this SQL to work.
  • Referring to first gotcha - note that in MOCA if you assign one recordset to another, it just sets the pointer and does not clone the data.  This implies that if you set multiple recordsets to the same "empty" recordset then even though you are thinking they are two different recordsets - they are really the same.  So if you move to the last row in one - you will also move to the last row in the other.  Where it would cause an issue is that when you will create the internal table from one - the next one will not work.  You will need to call ".reset" on the recordset again before using it.

Summary

The basic idea is that we should try to match event output to the RedPrairie order model via this capability.  That implies that we push the inbound data to in-memory tables once and then join them as needed to create the final set.  At a very high level the approach is similar to a two-step approach of first loading the data to temporary staging area and then logging another event from that - but this achieves that in a single step and will be more optimal - both in performance and also in management.


Right way to tackle RedPrairie/JDA Interfaces

Every WMS implementation needs to deal with interfaces.  This includes host interfaces, e.g. interfacing with SAP, Oracle, JDE, etc. and also to the automation equipment.  Many times the project plan calls for these tasks at the wrong time and that can lead to overall delays and rework.  Timing this activity correctly is critical to the overall success of the project.

Many times the customer's past experience with interfacing influences their decision to start interface discussion prematurely - but such proactive approach is not helpful.  So one thing we need to establish upfront is that RedPrairie/JDA will be able to handle any type of interface requirement.  If you can send it RP can process it! So do not lose your sleep over it.


Abstraction is the key

We need to understand that the actual project is WMS implementation and not the interfaces.  Interfaces need to be understood abstractly as just another mechanism to provide data.  Nothing more and nothing less.  So once it is established that RP can do any type of interface work then we need to focus on WMS solution summary and not interfaces.  

Proper place for Interfaces in the Project Plan

At a very high level, the project plan should be as follows:
  1. Key customer resources get trained on the solution set.
  2. Software is installed in development environment
  3. A Proof of Concept exercise is done with standard solution
    1. This highlights the interfaces
  4. A solution summary is developed
    1. Interfaces are called out here
  5. Interface Discussion
A completely wrong approach will put the interface discussion ahead in the plan.  I have seen instances where the interface discussion is done even before the software is installed.  Such meetings are not very productive because the customer's resources do not understand RP and the implementer does not understand customer requirements - so as a result a sub-par interface diagram is created.

Host Interface Matrix

Host interfaces are simply a mechanism to provide data to RedPrairie.  It is not different from manual data entry from that aspect.  Solution summary should be firmly established before this discussion starts.  One way to approach this is that the customer could simply take screenshots from RedPrairie client and highlight the data that they expect to come from a host system - that will be easier than going through spreadsheets with cryptic column names.  Any RP implementer should be able to take such a document and convert that to an actual interface.

Similarly for data going to host, the customer SME (Subject Matter Expert) can highlight the data that they would expect to populate the host system.

MHE Interfaces

MHE (Material Handling Equipment) interfaces are no exception.  The full solution summary should first describe the use cases in terms of RF flows.  This should be done even if majority of the picking is done by MHE.  This provides a baseline for testing.  Once this is done, MHE use cases should be defined and an equivalence diagram should be created.   This diagram will describe how MHE picking, for example, is equivalent to RF picking.  A diagram like this will form the basis for MHE interface discussions.  This will also help during development and unit testing phases.

MHE Emulator

Ability to emulate MHE messages is key to a successful implementation.  Unfortunately many MHE systems do not provide such capability.  This becomes a challenge if MHE system communicates over sockets.  A simple emulator can be created by RedPrairie integrator where you can create a system that represents the host system and listens for messages.  You can verify MHE protocol in this way.  You can use similar techniques to send completion messages back to WMS. 

When sending data to MHE, typical triggering point is the pick release process.  pckwrk table(s) should form the basis for data that is sent to MHE for picking.  Custom columns can be added here to incorporate any unique concepts but this should generally be the starting point.

The transactions back from MHE will typically call a flavor of "move inventory".  If we have an equivalence of these use cases to RF screens then this can be a relatively simple exercise.

Another approach may be to have a folder with raw messages and create a simple groovy command that sends data from this folder to the WMS.  You can also use "telnet" to send data.


Interesting WMS Concepts for MHE

RedPrairie WMS provides several concepts that can form the basis for providing data to MHE at proper level.  Often I have seen that new concepts are introduced in this regard which adds needless complexity and testing headaches:
  • Idea of picking cartonization
  • Idea of lists that can group cartonized cartons
  • Non cartonized picking directly to a tote
  • Pick Release process and associated standard triggering points like "register picks released"
  • Replenishment Configuration concepts
For most picking systems, e.g. Pick To Light, the MHE system will have a location table with similar locations as RedPrairie.  Inventory levels in RedPrairie will be assumed to be accurate as well.  In such systems orders are allocated within RedPrairie and at pick release time we send data to the MHE system.  RedPrairie concepts are a "shoe in".  By the time pick release is done - we know exactly which carton needs inventory from which MHE locations so appropriate messages can be constructed.

When MHE system completes a pick - some systems will tell exactly what was picked while some will simply say "this tote is picked".  In either case - processing of such a message should simply call "move inventory".  Sometimes implementer would make this interface needlessly complicated by a false assumption that "move inventory will be too expensive".  This unproven and untested notion will then translate to new concepts and make the whole implementation overly complicated.  It is always best to not over-engineer this step and simply pick the inventory in RedPrairie when it is picked in MHE.

MHE system from this point on may have multiple stops, e.g. audit station, weight station, etc.  Such locations may be handled as simple P&D locations or hops.  In either case - if MHE system can send a message in these cases - that message should translate into a "move inventory" call.  Refer to this article if you have a use case where certain percentage of totes need to be audited.

Every implementation may be slightly different but always have following goals:

  • Do not invent new concepts
  • Always equate to corresponding RF use cases

Summary

Interfacing capabilities are a strong suit for RedPrairie/JDA WMS systems.  Do not let some bad past experiences influence the project plan too much.  Considering interfaces as just another type of data entry method simplifies the project a great deal.  Based on the type of implementation - you may need significant hours, but those should not translate to significant complexity.  Proper abstraction that preserves the core RedPrairie concepts will go a long way.






Caching Data Using MOCA

Overview

We often need to cache some data during any implementation.  If you are creating a C component, that may take form of a "static" variable.  You could sometimes use "session variables" - if intended scope is a transaction.  You could also employ "Environment Variables". 

RedPrairie software itself utilizes caching in several algorithms.  Most of the implementations use C static variables.  This path is fine but requires a lot of housekeeping code and also prone to bugs - especially memory leaks.

As of 2011, MOCA has introduced external caching.  One such caching provider that MOCA supports is "infinispan".

Possibilities

This opens up some very interesting possibilities.  In this blog, I will describe a solution that we incorporate in almost all of our implementations, i.e. we cache results of an arbitrary MOCA command such that if the same command is called again - it simply returns data from the cache.  The solution is quite simple - thanks to the wonderful concepts implemented in MOCA.

MOCA Command "execute ossi moca and cache"

Whenever we need to call a command whose result can be cached, we execute it via this interface.  It is similar to the "execute server command" MOCA command - but the difference is that the results can be cached and if called again, it will return data from the cache.

It has following input parameters:
Argument Description Comments
uc_moca_cmd This is the exact command that needs to be executed Similar to what you will pass to execute server command
uc_inline Should the command have access to stack variables? Similar to "inline" parameter of execute server command
uc_cache_key The results of the execution will be saved in cache with this key This will include some key data for the execution so that it can be looked up again. You could default it to MOCA command itself but that will not work very well for inline contexts. Most of the time you can come up with primary key of this cache node. For example policy table primary key or area key etc.
uc_cache_grp The cache can be made up of several groups For example policy caches can be placed in one group, areas in another

While such caching is often very useful in production environments - it can be a pain in development environment where we are frequently changing the data that is cached.  That is why it is a good idea to support an environment variable to disable caching.  When the caching is disabled - this behaves exactly like "execute server command".  A sample implementation may be as follows:
publish data
where uc_inhibit_ossi_cache = @@UC_INHIBIT_OSSI_CACHE
|
if ( @uc_inhibit_ossi_cache = '1' )
   execute server command
   where cmd = @uc_moca_cmd
   and inline = @uc_inline
else
   execute ossi moca and cache core


MOCA Command "execute ossi moca and cache core"

This is the main command that utilizes the caching functionality in MOCA.  It works off the same input as the above command.  It first looks up the the cache for the passed in key.  If found it returns the complete result set from the cache.  If not found - it will execute the command, put the result set in cache and then return the data.  A sample implementation may be as follows:
publish data
where uc_my_infispan_cache = nvl(@uc_cache_grp, 'OSSI__MISC' )
and   uc_my_inline = string ( nvl(@uc_inline,0) )
|
{
   [[
   import com.redprairie.moca.cache.*;
   import com.redprairie.moca.cache.infinispan.*;
   //
   // This creates the cache.  Each cache group becomes a different cache.
   //
   MocaCache cache = CacheManager.getCache(uc_my_infispan_cache, 
                        new InfinispanCacheProvider(), null, null); 
   //
   // First get the cache_key from the cache.  We always put the full resultset in cache
   // so the get is expected to return that.
   //
   my_rs = cache.get ( uc_cache_key );

   //
   // If we do not find resultset in cache - then we execute the command (inline or not)
   // after executing, we put the whole result set in cache
   //
   if ( my_rs == null )
   {
      if ( uc_my_inline == "1" )
         my_rs = moca.executeInline ( uc_moca_cmd )
      else
         my_rs = moca.executeCommand ( uc_moca_cmd )
      //
      cache.put ( uc_cache_key, my_rs );
   }

   //
   // If we found in cache or executed - by this time we have the resultset
   //
   [ret:my_rs]
   ]]
   |
   //
   // we publish the resultset
   publish data combination
   where res = @ret
}


Use Cases

This comes in quite handy.  For example in complex integration projects we often have lookup tables - this improves the performance significantly in that case.  Similarly data in policies, area, storage polices, pick policies, etc. is quite static and can take advantage of this capability.  

In addition to this, this method makes the concept quite abstract and requires minimum housekeeping.  All we need to do is construct the command and execute it.  MOCA allows the whole recordset to be cached so we can thus cache complex information.

Additional Documentation

For additional features and detailed documentation, refer to "MOCA Developer Guide" chapter on "Caching"