| Intro | Part I | Part II | Part III | Part IV | Part V (4.1) | Part VI (6.0) | Summary |
The project would involve a lot of C++, a lot of Oracle and SQL... and a product called Broadvision One-to-One which had been 'recommended' by IS Solutions' client and, after a brief evaluation, approved by IS Solutions. Since this was the unknown technology in the project, the first thing that happened was a training course. Broadvision were in the process of setting up their UK offices so their UK support person - a French girl, Marine - was also on the training course, which was run by a chap from their Dutch office, Niek. Unfortunately (for us or for Niek), we were to learn about version 2.5 of the product and Niek was only familiar with the prior version (2.1 if memory serves). We were all in for an interesting and informative week!
So what is Broadvision? First, let me describe its aims and then I'll explain what t he product actually is. Broadvision is intended to let you sell 'product' over the web using targeted marketing. In other words, it is intended to provide personalised advertising, editorials and product offers by recording how you browse (within a Broadvision-powered web site) and some personal details about you (a user profile that you fill in). The course was a bit of a marketing pitch too, as is so often the case with product-based training courses, and to be honest it sounded just the ticket for our project. I made notes during the course that suggested Doug and I would probably only need to make minor customisations here and there whilst the majority of our work would be integration. If this had proved to be the truth, I wouldn't be writing these articles of course.
Anyway, so what is Broadvision? Hang on, we still need some background on web applications! When you visit a dynamic web site, the first page you visit has to start a 'session' on the server side against which all your actions and choices can be logged, the session lasting until you explicitly log off the site or else it times out when it hasn't heard from you for a while (because you've gone browsing elsewhere). One of the most common dynamic web experiences that follows this pattern is an ASP (Active Server Page) site such as Microsoft's own. The ASP pages are a mixture of HTML and either VBScript or JScript. Some of the session management is explicit in the script programming and some is implicit in the ASP engine on the server. As those who have visited Microsoft's site will know, ASP relies heavily on 'cookies' - small files stored on your hard drive by the browser which contain 'lookup' information which can be passed back to the web site on request. That's how many dynamic web sites recognise you and cookies, whilst considered by some surfers to be an invasion of privacy, are generally a good way of providing a more personalised path through the mire that is the world wide web. In addition, the scripts in the pages can produce dynamically generated HTML based on information from databases, typically accessed via ODBC.
Not everyone likes cookies though and ISS's client had serious objections to them. Broadvision solves this problem by maintaining all the persistent information on the server and using complex URLs and hidden data fields in forms to help the Broadvision engine keep track of which web user is doing what at any one time. Broadvision has a number of Unix processes that manage web user sessions and provide access to databases and other resources. From the programmer's point of view, Broadvision is a set of classes that generate HTML and process GET and POST data submission operations, and also a fairly low-level API which gives access to the underlying Oracle or Sybase databases.
The range of objects seemed broad and, at first, well-suited to the client's requirements for tracking customers, providing targeted offers and selling foreign currency, travel books, flights and holidays over the web.
This is when things began to get interesting. Broadvision, in common with many application frameworks, makes a lot of assumptions about what you, the programmer, are going to be doing with it. It assumes, for example, that the 'product' you're going to be selling has a fairly simple structure, the sort that can be modelled by a single product table in a database. It assumes that when you pick product up off the 'shelf' and put it in your 'shopping basket' that if you pick up an identical product as well, it can just remember the quantity '2' in the basket. It also assumes that a user either visits the site as a 'guest' and doesn't buy anything or that they 'log in' right at the beginning. All these assumptions were to cause us interesting problems over the next six months.
Broadvision has a session manager process - the CGI program - that handles all POSTs and GETs from the user. Each operation sends a series of hidden fields that tell Broadvision what 'object' to invoke to handle the request as well as the parameters to that object. For example, a link which would look like:
<A HREF=hol/hlb03f.t>Destination</A>in plain HTML becomes something like:
<BVBlockObject Dyn_SmartLink receive_class=Dyn_SmartLinkReceive destination=hol/hlb03f.t>Destination</BVBlockObject>in Broadvision tags. Broadvision interprets the extended HTML file, loads the 'Dyn_SmartLink' object (from a shared library), invokes various methods on it to handle the attributes ('receive_class' and 'destination') and generate the actual HTML sent back from the web browser. The processed HTML looks something like:
<A HREF=/cgi-bin/bv.cgi?BV_EngineID=0.766.1.2.3.4 BV_Operation=Dyn_SmartLinkReceive& BV_SessionID=AFDHFRGB& BV_ServiceName=Mall& form%25destination=hol%2fhlb03f%2et>Destination</A>In reality, the engine ID and session ID would be much more complex. When the user clicks on the link, Broadvision loads the 'Dyn_SmartLinkReceive' object and invokes various methods on it to handle the attributes (as before, except they are now prefixed with 'form%25') and the Broadvision session manager selects the next extended HTML file (from the 'destination' attribute) and the process begins again.
Each object ultimately extends a 'Dyn_Object' base class and overrides its methods. The sequence of methods called is very rigid: to generate HTML, Broadvision calls 'prepare()' which calls 'prepare_attribute_list()' which calls 'prepare_attribute()' on each attribute given. Then it calls 'handle_attribute()' on each attribute and finally 'handle_body()'. When handling an incoming request, it calls 'receive_attribute()' on each attribute and then 'receive_body()'. You override the methods within your own objects to record and process each attribute, generate the HTML and process incoming attributes respectively.
Broadvision expects each object to process attributes and store them in named private data members. Think about it: each object performs certain identical functions in terms of dealing with name/value pairs, and yet you have to duplicate the code in every single derived object!
Needless to say, my first revision was to create generic 'receive' and 'submit' objects that inherited from 'Dyn_Object' and used a 'map<>' to store any attributes found. My own objects then inherited from these, resulting in much less duplication.
This was not a good sign! Broadvision's approach to code reuse amounted to brute force: cut'n'paste. A typical object written using this approach amounted to some 200 lines of identical code to the base class... and that was before you added your own functionality. By abstracting the common attribute handling into a base class and clearly separating 'generators' from 'receivers' my own objects amounted to about 40 lines of framework into which I could drop my own functionality.
My next problem was that I still had to override 'handle_body()' and 'receive_body()' in toto because those routines also perform certain operations that are standard and must be duplicated in every derived class. The solution to this was to change the base class function to call a private virtual which, in the base class, did nothing but in your derived class performed the necessary specialised task.
// base class:
void My_Receive::receive_body() {
//... standard stuff
specialised_receive_body();
//... more standard stuff
}
// virtual
void My_Receive::specialised_receive_body() {
// do nothing
}
// derived class:
void My_DerivedReceive::specialised_recieve_body() {
// do derived stuff
}
I hope I've given some flavour of what happens when you use a framework that isn't ideally suited
to your application. Next time, I'll look at database access and
flyweight classes to encapsulate some of Broadvision's raw API as well as looking deeper into
subjects such as session management.