Saturday, July 3, 2010

eScript Framework - GetRecords

Matt has launched YetAnotherSiebelFramework, a blog about... you get the idea. This is an important step forward in this community's attempt to create a true open source Siebel eScript framework. He adds flesh to the skeleton I have assembled here. He will shortly be adding his own posts to explain his functions in more detail but I thought I would get a head start with starting a discussion about one of his most important pieces, the GetRecords function. I say one of the most important pieces, as the real driver behind this solution is to replace the many plumbing steps, as Matt calls them, that sit in so much of our script. So for instance to query an Account by Id (sId) to get the Location for instance, you would write something like this:
var boAccount = TheApplication().GetBusObject("Account");
var bcAccount = boAccount.GetBusComp("Account");
with (bcAccount) {
ActivateField("Location");
ClearToQuery();
SetViewMode(AllView);
SetSearchSpec("Id", sId);
ExecuteQuery(ForwardOnly);

if (FirstRecord()) {
var sLoc = GetFieldValue("Location");
}
}
You get the idea. His function essentially replaces this with:
var sLoc = oFramework.BusComp.GetRecord("Account.Account", sId, ["Location"]).Location;
So that is pretty cool. What follows is mostly quibbling but I think attracting criticism from our peers is the best way to make this framework the most usable it can be. On a technical note, I am using 7.8 and the T engine for my personal sandbox so have not yet been able to get Matt's entire framework up and running. Nevertheless, I have gotten his individual functions running so I will limit my discussion to that scope. Here are my thoughts:

(1) My biggest point is to think about whether it makes more sense to return a handle of the BC rather than filling an array. I am thinking about this in terms of performance. There are times when having the array would be useful, like say when I want to perform array operations on the data, like doing a join. But often, I may just need to test a field value(s) and perform operations on other values conditionally. In this case, I would only be using a small percentage of the data I would have filled an array with. It may also be useful to have a handle in order to use other Siebel BC functions like GetAssocBusComp or GetMVGBusComp. I do not claim to be a java guru, but I am curious about the performance implications. What I have done with my own framework is to build three functions:
  • Bc_GetArray (this is basically the same as Matt's)
  • Bc_GetObject (stops before filling the array and just returns the handle to the BC)
  • Bc_GetInvertedArray (Same as Matt's but makes the fields the rows and the record the column)
(2)I took out the following two lines:
aRow[aFields[i][0]] = vValue;
if (aFields[i][0].hasSpace()) aRow[aFields[i][0].spaceToUnderscore()]= vValue;
that checks if the field name has a space and if so changes it to an underscore and replaced them with a single line:
aRow[aFields[i][0].spaceToUnderscore()]= vValue;
I think this should be more efficient since a regular expression search is being done regardless, I think just doing the replace in one step saves an operation.

(3) I like the first argument, "Account.Account" syntax for most situations. I think we can make this even more robust though by allowing us to pass in an already instantiated BC. This is probably infrequently necessary moving forward with the pool concept Matt has introduced, but there is a low cost way to handle either. What I have done is to add a test of the data type:
if (typeof(arguments[0])=="string") {
before starting the pool logic. I then added an else to allow us to pass a BC object in and add it to the pool:
else {
oBc = arguments[0];
this.aBc[oBc.Name()] = oBc;
}
(4) I think I understand where Matt is going with the pool as a mechanism to instantiate BCs less frequently. His bResetContext argument, the flag indicating that the pool be flushed, is I think unnecessarily too drastic. If I understand it correctly, setting this flag to true would flush the entire pool. While this may sometimes be desired, it seems more useful to just flush the BO/BC in play. This would allow you to write code for instance in nested loops that jumps between BCs without clearing context when it is not necessary too. I may not be thinking of a situation where this would be necessary though so if anyone can think of one I am all ears. My recommendation would be to make the flush just clear the passed BO/BC but if the "Full flush" is necessary, then perhaps a code indicating one or the other can be used. This could be accomplished by just removing the reference to the FlushObjects function, as the following if/else condition effectively resets the BO/BC variables in the array after evaluating the bResetContext argument.

4 comments:

  1. Mik,

    I think instead of creating separate version of this function better option will be to create a method chain. Which will be similar to what we can do in siebel

    GetBO("Contact") .GetBC("Contact").GetFieldValue("dd");

    Which will allow flexibility to get only instance or fieldvalue as required.

    As far as your idea of passing a BC instance in a function.
    I think it might be an option in T engine but ST engine is very fragile and it might lead to memory leaks.

    And I agree with you option of just flushing the BO and BC passed instead of flushing the complete cache.

    ReplyDelete
  2. I had briefly set up a Method Chaining working example when I saw Jason's post on it

    http://www.impossiblesiebel.com/2009/03/siebel-escript-framework.html

    I had it working when it was all declared in the declarations section of the Application. I am not sure how to set it up in the context of a series of methods within a Business Service though. That is actually one of the problems I am having now getting Matt's framework to work in my T engine environment and I am not sure if it is a limitation of T. For instance he has a function called Class_BusComp in which he declares references to other BC specific functions such that he can call a BC function like this:

    oFramework.BusComp.GetRecords...

    When I do this, it does not work at all. So I just call everything directly,

    Frame.GetRecords...

    and I moved on since I have been working on other things. In fact I cannot even call a function by another name (He declares a variable as a handle to a function of another name: Class_BusComp.prototype.getRecords = Function_Bc_GetRecords but I cannot even get that to work when called directly). I am curious to know whether anyone else knows of such a limitation or of what may be going wrong.

    ReplyDelete
  3. I'ts hard to say where your problem is. It should work. I've even tried to use that example on the link you provided and it works. I can call the functions from other BS without any problem.

    ReplyDelete
  4. Hopefully I can help add some useful thoughts to this...

    1) Objects.
    I took the approach of passing JS "objects" around as they can be very powerful and describe simple or complex things (e.g. single values, records, sets of records) - and the engine seems to handle the memory work well - I've stress tested this a bit - e.g. running getRecords() 500,000 times - and couldn't detect any leaks - I think with passing BC around the object handling code would need to be very robust. I also wanted to create a simple to use interface - so if the caller had to look after destroying objects this adds a line or two - and I wanted everything to work on a single line.

    Also using JS objects I can pass the objects from the server side to the browser side with relative ease.

    There's another feature of the getRecord() or getRecords() is typing (not Siebel strong typing, but making use of JS types). An example of this is "Date". If typing is used (e.g. add ":Date" to the fieldname) then a JS Date object is returned - so methods can be invoked on it (including the "new" date methods added by the Framework) so:

    javascript:alert(top.oFramework.BusComp.getRecord("Employee.Employee", "0-1", ["Full Name"]).Created.toDb());

    should give you a DD/MM/YYYY of when the Sadmin user record was created.

    (Some of the system fields are always returned, so don't need to be explicitly listed in the inputs. Other types are :String (default) :Number and :Boolean

    2) Fieldnames with Space and Underscores.
    the reason I had the two lines comes from the use of JS objects to represent records. You can access a property (so in the case of a "record" a field) using either

    myObj.FieldName or myObj["FieldName"]

    I prefer the former dotted notation (ex C++, Java) - however if the property has a space in it this won't work. Siebel often has fieldnames with space or spaces in them, so I added a line to convert the space to an underscore so that it could be accessed using the dotted notation e.g.

    so myObj.Field_Name (and still get at it with myObj["Field Name"]

    If you only use either the dotted notation or the bracket notation then yes. this code can be shortened.

    3) I like it, this makes sense.

    4) Object Pooling/Context Reset
    Ok, an explanation of the pooling. It did start as a way to keep common objects in memory to save on the creation of them however testing this (well, in 7.7 ST anyway) that the savings aren't as dramatic as I hoped, but there is another reason for this...

    I'll try and talk in terms of vanilla relationships, however I'm so used to the objects we use I'm not sure that this is vanilla, but the idea will hopefully be clear.

    Say you have a "parent" BC - so Asset. When querying a child BC of this (say Contact) where there is a one to many relationship (so one Asset can have many Contacts) you can query the Asset first, then without querying the Contact BC you should have context - so if you scroll through the records on the Contact BC you should have only those related to your Asset. The pooling in the Framework enables this - and the reset context argument is basically clear the parent - so if you then searched on Contact you'd get all records. As noted the current mechanism is drastic in that the whole lot is cleared out - this came about from a necessity to fix a production issue very quickly - I'll correct this shortly!

    I have posted the SIFs at http://yetanothersiebelframework.blogspot.com/2010/07/framework-sifs.html
    with some start-up information at http://yetanothersiebelframework.blogspot.com/2010/07/getting-started-with-sifs.html

    Hopefully these will prove useful.

    On the ST versus T engine - I've only tested this with 7.7 + ST - I'm not sure what limitations T engine might have - I thought that following typical ECMA script behaviours it should be ok... maybe not - so any info would be useful.

    ReplyDelete