Version: 1.00.00 - last update: Friday 7th of March 2014, 17:00:00
This is an overview of FoxQuill's basic building blocks. Note: I will add content here while I'm writing about the components in detail in the other sections.
I said it in my (previous) overview chapter: FoxQuill is a Managed Runtime Environment (MRE) for all kind of business related VFP class instances. This is a sounding statement. Let me fill it with more concrete explanations for you, now.
FoxQuill's Main Core Component is called The Kernel
The Kernel is a VFP Container that hosts all other Core Components. The Kernel does not implement any important functionality but publishing an interface to access its members. The Kernel Members (the Core Components) implement the Kernel Infrastructure Layer (KIL). Based on this infrastructure the Kernel then creates an Object Management Layer (OML). Classes belonging to the OML are typically named Managers, Controllers, and Handlers. We will discuss them later in detail. Finally, the OML loads the Application which is, generally said, the set of all class instances used to create the application specific appearance and behavior the user is interacting with. The following diagram shows this hierarchy.
Internally, the Kernel creates a public variable (normally "MY") holding a reference to the Construct Component which belongs to the Infrastructure Layer. The My-Reference can be compared with the well-known goApp-Reference used in many other implementations. Thus, in the FoxQuill universe all code that addresses an object in any layer will look like this:
lnAnswer = My.Bios.MessageBoxes.QueryAppQuit.Show()
As you will see later, the above Command follows a common FoxQuill Object Notation Scheme:
ConstructRef.ManagerRef.ControllerRef.HandlerRef.Method()
Another common FoxQuill Object Notation Scheme for querying a property looks like this:
llSingleton = My.Settings.System.AppIsSingleton.Value
Again, this Query follows the same common FoxQuill Addressing Scheme:
Result = ConstructRef.ManagerRef.ControllerRef.HandlerRef.Value
We will discuss that issue in more detail in the Kernel chapter.
FoxQuill's Object Pool is called The Construct
Basically, the Construct is-a VFP Collection. The Construct stems from a "new" FoxQuill BaseClass called ObjectCollection. As with any native VFP Collection ObjectCollections can be stacked inside each other, again and again. The most prominent extension of the native VFP Collection class is the ability of the ObjectCollection to address stacked ObjectCollections like shown in the examples above. Trying this with native VFP Collections we would have to write the first example like so:
lnAnswer = My("Bios").Item("MessageBoxes").Item("QueryAppQuit").Show()
What a mess :-) But now, we are able to store ObjectCollections in ObjectCollections, in ObjectCollections, in… without the need to use the collection's Item() method to drill down into the containment hierarchy. You can read all about the implementation details in the Construct chapter.
The ObjectCollection not only is a Core Component but really a Key Component that makes programming the FoxQuill Framework a hassle-free experience!
FoxQuill's Class Manager is called The Factory
Like the Construct FoxQuill's Factory is-a ObjectCollection. The Factory is able to instantiate any kind of object and 'Do' any kind of form, as long as the definition can be found in the Factory's driving-table.
Another feature of the Factory is the ability to return a factory string suitable for creating objects on the fly using VFP's macro substitution. In rare cases this might be helpful but is not recommended!
The Factory also supports the creation of Member Objects within other containers, and collections.
Another prominent feature of FoxQuill's Factory is the ability to create any object within its own distinct data session! Internally the Factory keeps track of all created objects and their data sessions.
The driving-table also stores information about Object Relations! Thus, the Factory knows which sibling, or child objects have to be instantiated in which 'parental' data session!
There's a lot more to say about additional features. You can read more about the details in the Factory chapter later on.
FoxQuill's Data Manager is called The GPStore
Like any other Core Component the General Purpose Store is-a ObjectCollection. To simplify VFP-table based system data handling to the max all system data is stored in one table only! Well, almost :-) In Fact there are three tables which together make up a GPStore. I will explain that in more detail in the GPStore chapter. But…
All data is stored in a highly redundant, de-normalized, serialized way!
What does this mean? Well, in short, a GPStore does not organize its data using the relational column/row addressing directly!
A short side-kick: The relational theory describes a set of columns as the representation of a data structure (similar to a class). The rows are then instances of that structure (similar to a set of objects stemming from the same class only differing in their appearance). In the relational world we now can address each data field (each atomic information) using column/row addressing.
A GPStore places each atomic field value in a distinct record! If we want to store a regular record (with, let's say, 10 columns) inside a GPStore System we will end up having 10 distinct records each holding one atomic field value. This is meant by serializing the data.
To keep track of which GPS-Record maps to which column/record of the native VFP source table we have to add these meta-information to our GPStore-Table adding extra columns. Amongst the Mapping Information columns there are further columns to describe the Data Type of the stored value, its Dimension(s) and other Meta-Data.
Normally, this meta-data is stored only once in the table header of a regular VFP table. Because the GPStore table has to store its "class definition" meta-data together with each data value in every GPS-Record again and again, this is a highly redundant way of storing data. In terms of Relational Normalization Theory we would say "the GPStore data table is de-normalized to rot in hell" :-)
Why would we pay such a high price acting against all odds?
The answer is: Freedom! We are free to change our data class schemas (the 'virtual table headers') from one record-set to the next without the need of exclusive file access to alter our table structure. But there is much more! We can add as much meta-data to each data record as we need. The GPStore for example supports data versioning, data translation, access security, encryption, relations, and more! Imagine: You now can ask your GPStore to retrieve information about the the string "Heinz" and perhaps you get the answer: "Heinz" was found 3 times denoted by 'First Name', 1 time denoted by 'Last Name', and 2 times denoted by 'City'. Then you can ask the GPStore to retrieve the whole record where 'Heinz' is used as a 'Last Name' and get back a VFP data object with all fields restored from the serialized records.
To restore serialized data back to a cursor that can be consumed by FoxPro FoxQuill's GPStore employs a special Serializer/DeSerializer object. The rest of this exiting story can be read in the GPStore chapter in the next future…
FoxQuill's Unified Messaging System is called The Matrix
Like any other Core Component the Matrix is-a ObjectCollection. "What the hack is a Unified Messaging System for?" you may have asked yourself, don't you? :-)
Basically, the Matrix translates human-readable object paths (like My.Logging.TextFile.ErrorLog) into machine-readable ones like 1.23.12.55.
Without diving too deep into details we can state that if both expressions above address the same class instance in FoxQuill's working memory, and My is the reference of the Construct (which equals 1 here), then the Logging object must be item 23 of the Construct ObjectCollection, the TextFile object must be item 12 of the Logging ObjectCollection, and the ErrorLog object must be item 55 of the TextFile ObjectCollection.
Adding up the Item-IDs along the object path (transforming them to a dot-separated string) will give us a new key expression we will use within the Matrix ObjectCollection. But, what will be stored in the Matrix ObjectCollection using the key "1.23.12.55." and why?
Well, FoxQuill introduces a set of 'virtual' classes called Smart Interfaces. A Smart Interface object does not wrap, or decorate its implementation object. A Smart Interface is a placeholder, a so called binding proxy based on VFP's EMPTY class that represents the Handler Object within the Matrix. Every Handler supports one or more Smart Interfaces.
When the Factory instantiates, let's say, a Customer Form for us that form instance will be stored somewhere in the Construct maybe under "My.Gui.Forms.CustomerMain". Notice, that the CustomerMain reference is not our form's reference but the reference of the Construct's Handler 'wrapping' our form reference!
Together with our form stored in its Handler inside the Construct at least one Smart Interface object is created and stored in the Matrix ObjectCollection using a machine-readable key that equivalently 'points to' our Form Handler.
All Public PEMs of our form will be represented within the Smart Interface by adding a simple property to it. These properties will be solely used as handles for an event binding that takes place in the next step of the process:
The Matrix binds the Handler of our new form (in our example the .CustomerMain object) to each of the Smart Interface Properties in a predefined (table-driven) way. After that, the Matrix broadcasts a message NewFormCreated so that all other Smart Interface Objects that are interested in that message will get the chance to signal that Application-global event to their own Handlers.
Messages are no longer sent from one object to another directly but solely through and to their Smart Interfaces. Handlers implementing a generic contracting scheme. They check incoming and outgoing parameter values against their contracts ensuring the correctness of the message signatures. There is much more to say about the collaboration between Smart Interfaces and the Handlers they are bound to… You will find the whole story in the Matrix chapter.
Bottom line is: With the help of the Matrix you can forget any hard-coded 'object reference wiring'. Just code against the generic messaging interface of your classes' Handler, that's all. The last thing you have to do is to set up some records in the Matrix driving table that define the bindings you need for your form to interact correctly. BTW, that job can also be done later at runtime!
FoxQuill's Task-Oriented App-Manager is called The Workflow
Like any other Core Component the Workflow is-a ObjectCollection. The main task of the Workflow Manager is to "drive" your application. Look at your own implementations for example. You will find some entry point where your application starts. From there you almost always can follow a narrow execution path that finally leads to some open tables, some running business objects with your main form bound to their properties, before you enter VFP's READ EVENT loop. This is a good example for an application workflow. Better said, this is good example for a Process defined in FoxQuill's Workflow Manager. In FoxQuill's Workflow Manager context Controllers are called Processes and Handlers are called Tasks. Each Task you define can manage an unlimited count of Steps.
Sometimes it is not advisable to split a simple workflow up in too fine-grained Steps. Often it is enough to hard-code a whole Process just into one single Step. The idea behind the scenes of FoxQuill's Workflow Management is, as always, to create reusability even if the code looks 'procedural' that much. You may think about setting up your development environment for a given project and a special scenario. Maybe you want to run some code every time you entering the debugging mode. If you encounter an invariant sequence of code lines that is only varied using some external values and you find yourself typing in those code lines again and again (or copy & paste them again and again) you obviously found a code fragment that should be better wrapped into a Step Class.
The biggest advantage using Processes, Tasks, and Steps is that they all can be scripted. In other words, they can be dynamically altered at runtime!
Another big plus of using FoxQuill's Workflow Management is that all Actions defined within a Step are recorded and can be replayed later. This also leads to an almost unique feature called Forward Recovery. The users of a FoxQuill-based application get a new option beside save-or-cancel editing called suspend editing. They may not only close their data entry forms but suspend them. The next day they can resume editing by the help of Forward Recovery.
Finally, using FoxQuill's Workflow enables Undo actions. This feature can be compared to VFP's Transactions that can be rolled back at any point before End Transaction took place. There is much more to talk about. Get the rest in the Workflow chapter.
Final thoughts
If you followed my writing you may have asked yourself, if the additional layers introduced by FoxQuill's Managed Runtime Environment would not have a negative impact on performance and clarity. Believe me, they don't have!
First, let's talk about Handlers. These guys handle our workhorse classes. What are they doing when they implement the contracting that shields our implementations from false input parameter values? Well, they take the parameter checking code out of our implementation methods, thereby making our methods easier to read (even running a little bit faster). Of course, our parameter checking code was only relocated which means it still gets executed (slowing down things again:-).
But this is irrelevant compared to the advantages we will gain! A Handler is capable of handling an unlimited count of class instances of the same type (the class type he was designed to handle). Let's open a second and third instance of our form. Now we do have a positive impact on performance and memory footprint because our parameter checking code was only loaded once inside the Handler! But that's not all by far!
Handling a form does not only mean checking parameters, but is much more, like resizing, refreshing, repositioning, and other things. All these issues should be taken out of the form class. The optimal form class in a FoxQuill MRE has no code in its methods at all! Okay, let's put two or three lines in the Form's QueryUnload() method but that's all! The one and only task of a FoxQuill form is being the host for the controls we dropped on it! Form resizing, and repositioning of form controls is left to the Form Handler. Even better, repositioning of form controls is delegated via a second Smart Interface to a specialized Layout Manager.
Now, let's talk about Controllers. While each Handler Class is designed to support only one specific Class Type, each Controller Class is designed to support only one specific Handler Base Type. This said, it should be clear that a Form Controller will only 'control' Form Handlers, just as a MessageBox Controller will only 'control' MessageBox Handlers.
What does 'control' mean in this context? Well, to put it bluntly, Controllers do not control very much, at least the vast majority. There is a chain of responsibility established between Handlers an Controllers instead. If some functionality is common to all Handlers it will be implemented in their Controller class, instead of moving it upwards the Handler class hierarchy. Thus, we can choose a totally different BaseClass type for a specific Handler without loosing the common functionality. Our new, let's say, Custom-based Handler calls its Parent Controller if it needs some common Handler functionality implemented there.
BTW: The Layout Controller is one of FoxQuill's Controllers that do 'control' its Layout Handlers to synchronize the activities of these sibling Handlers. But that's another story…
Finally, let's talk about Managers. FoxQuill's Managers are grouped by the Aspects they manage. There is a Manager for Security, Basic IO Operations, Logging, Error handling, and many many more. Some of these Aspects are no real AOP Aspects because they are restricted to only one Application Layer and therefor no Cross Cutting Concern. Most what was said about Controllers also is true for Managers. There is a chain of responsibility established between Controllers and Managers. If some functionality is common to all Controllers it will be implemented in their corresponding Manager class instead of moving it upwards the Controller class hierarchy.
How to implement a bullet-proof handling of Cross Cutting Concerns based on the FoxQuill Framework goes far beyond this document, even the entire Blog, and maybe subject of a separate essay to be written at some indefinite future date:-)
Cheers!
No comments:
Post a Comment