Dr. Dobb's is part of the Informa Tech Division of Informa PLC

This site is operated by a business or businesses owned by Informa PLC and all copyright resides with them. Informa PLC's registered office is 5 Howick Place, London SW1P 1WG. Registered in England and Wales. Number 8860726.


Channels ▼
RSS

Database

The Strix Object Persistence Engine


Aug01: The Strix Object Persistence Engine

César is founder and leader of Neco, a company specializing in software development support and OO methodologies in Santiago de Compostela, Spain. You can reach him at [email protected].


Despite modern APIs and interfaces, using relational databases with object-oriented languages continues to be problematic. Strix, a software layer that works between an application and the relational database backend that stores its data, was designed to address this problem. Instead of interfacing directly with the database engine, the application talks to the Strix run time (SRT) which, in turn, interacts with the database through "store managers" (SM). Store managers are necessary because different database products speak different languages (at least, different dialects of SQL) and offer different capacities (such as transactioning, cursor types, or syntactic rules for identifiers). Store managers are dynamically loaded and unloaded by the SRT as needed. The interaction between the SRT and the store managers takes place though an intermediate layer originally named "Strix Run-Time/Store Manager Interface" (SRTSMI), which provides extra services such as tracing the flow of information between the application and the database backend, or providing an API for you to write new store managers. Figure 1 illustrates the Strix architecture.

This database structure can be accessed through the SRT and a suitable backend-specific store manager, and requests to create, modify, retrieve, and delete objects can be issued from client programs. The Owl query language (which closely resembles SQL) can perform arbitrary object-oriented queries. Finally, the Scops software layer, which operates on top of Strix, can recompile the specified class model with some more information to generate language-specific wrappers for the corresponding classes, attributes, and relationships. Using Scops on top of Strix lets you forget about the existence of a persistence engine and live in a "total persistence" world, where every object created is persistent unless specified otherwise.

Strix evolved from research into representational systems and metainformation at the Laboratory of Archaeology and Cultural Forms (http://www-gtarpa.usc.es/) at the University of Santiago de Compostela, and Verdewek (http://www.verdewek .com/), the research division of Neco (http://www.neco-ti.com/). The BlueMoon project attempts to integrate Strix, the Metis methodology, hierarchical enumerators, emerging user interfaces, and substructured storage into usable products.

Strix Concepts

Two components play special roles in Strix: the metainformation manager (MiMan) and the generic information manager (GiMan). MiMan implements object stereotypes (such as class, attribute, and relationship) and builds an in-memory network of objects representing the structure of the system described by the developer. GiMan, in turn, implements domain-level concepts such as object (an instance of a class), value (an instance of an attribute), and tuple (an instance of a relationship), and lets clients instance the metainformation provided by MiMan, manage instances and even serialize and deserialize them from/to different formats such as a binary stream, XML, or plain text. Figure 2 is the example class model I use throughout this article; Figures 3 and 4 show sample portions of in-memory structures managed by MiMan and GiMan and are available to Strix.

A central Strix concept is that of system — a set of classes, relationships, and other constructs that comprise a fully specified application domain. Figure 2 serves as an example. For the most relationally inclined, a system is for Strix as a database is for any conventional relational product. For Strix to operate with a system, it must be described by you using the Middle language, which offers the classic object-oriented stereotypes (class, relationship, generalization, attribute, and role), some not-so-classical ones (hierarchical enumerators, multivalued attributes, and relationships with multiple properties such as boundness or navigability), and a rich set of data types (Booleans, integers, floating-point decimal, fixed-point decimal, strings, timestamps, timespans, and binary data).

Listing One shows the Middle specification of the system in Figure 2. The system is named "Test," and given the system ID Verdewek/Test. An enum declaration specifies the possible values for attributes of type EProjectType, using the built-in hierarchical enumerator notation. Then, a list of class declarations follow. Attribute clauses inside each class include the attribute name, its cardinality (0..1 if omitted; that is, a classical single-valued attribute that is nullable), its type (built-in or a declared enum), and in some cases a size (just for string and binary data types). Specializations and generalizations are introduced by "isa" clauses. The "reference" clauses, in turn, include the reference name, its cardinality (0..n if omitted), its target class optionally preceded by a role name in square brackets, an inverse reference specification that links each reference to its inverse one so both conform a whole relationship, and some modifiers such as bound or main. The bound modifier gives the reference bound semantics. (Bound semantics are often used to implement aggregations as opposed to associations. However, Middle does not make this distinction, and bound semantics can be freely applied to references and combined with other properties.) Consequently, the lifetime of the referenced object becomes dependent on the referrer. The main modifier marks the reference as more important than its inverse toward relationship identification. Finally, "invariant" clauses specify Boolean conditions that must always stay True for the object's lifetime. Invariants are not supported in the current version of Strix.

Design Considerations

Our main goals in constructing Strix was to make it simple to use, and suitable not only for large databases and systems (which in turn, usually imply bigger human teams and budgets), but also for small- and medium-sized projects. Also, we wanted to obtain an extensible and adaptable framework so it could be changed with little effort and customized by its final users.

We made several decisions to meet these aims. First, we decided not to write a database system but to use existing ones, borrowing from their I/O, caching, and concurrency features. At the same time, we wanted to make Strix independent of the specific backend used to store data, so we took a modular approach. Common functionality regarding system installation and management, object-relational mapping, and GiObject construction and management is implemented by the SRT. On the other hand, product-specific functionality such as SQL generation, identifier management, and direct interaction with whatever library is used to access the database engine are implemented by store managers. A Microsoft Jet 4 store manager has been developed, and others will follow. The Strix run-time and the store managers communicate through a programmatic interface implemented by a separate component, the SRTSMI, which is needed to isolate SRT's "downwards" interface to store managers from its "upwards" interface to applications; see Figure 5. The SRTSMI, in fact, implements the Asio language, an abstraction of SQL that allows declarative communications between SRT and store managers.

Another important decision was that of using a ubiquitous infrastructure to support nearly all operations performed on persistent objects — the GiObject. Of course, using GiObjects adds another layer to the already thick pile of memory structures, COM, ActiveX, and Visual Basic (or whatever language you use), but it also brings in some advantages. The main one is that metainformation can be made dynamic and changed at run time. C++ or Visual Basic objects, for instance, have to be based on classes defined at design time; GiObjects don't. In coherence with this, classes, attributes, and relationships (objects on their own) could be freely created, modified, and deleted at run time, although the current version of Strix does not support it. In fact, we found this functionality quite challenging but of little convenience in usual business scenarios.

Finally, some extensibility mechanisms had to be built in Strix so that you could hook in its internal workings. The SRTSMI component defines an abstract interface that user-defined libraries can implement to gain access to some Strix internals. Using this feature, Strix triggers events as selected operations are performed, providing a simple way to monitor, trace, or intercept them.

Implementation

Strix was implemented on Windows using Microsoft's Visual Basic 6 and Visual C++ 6. The SRT, SRTSMI, and store managers are implemented as ActiveX DLLs. An application wanting to use Strix would import the SRT type library through Project References in Visual Basic or create a class wrapper in Visual C++ and directly call its methods; in this sense, using Strix is comparable to using Microsoft ActiveX Data Objects (ADO) or any similar database library.

Different store managers may coexist in the same computer, and each of them must be registered in the Windows Registry so the SRT can find them as needed. Store manager registration must be performed by setup programs following the guidelines specified in the Strix Store Manager Development Kit (available soon at http://www.verdewek.com/).

At the database level, Strix stores objects in tables following some hard-wired conventions that are completed with a small amount of heuristics done at system installation time. The mapping between the object-oriented and the relational worlds is done as follows:

  • A table is created for each class in the system. Each row in the table corresponds to an object. An OId column is added to hold object identifiers.
  • Another column is created for each single-valued owned attribute (those with a maximum cardinality of 1), mapping Middle data types into backend-specific ones.

  • Each multivalued attribute (those with maximum cardinality greater than 1) is implemented in one of three possible ways, depending on its maximum cardinality and some other parameters: 1. For small maximum cardinalities, separate columns in the same table may be used to store different values of the attribute. 2. For larger maximum cardinalities or other scenarios, a single column in the same table can be used to store all the values for the attribute, encoded in a single string. 3. Finally, an additional table may be created and related one-to-many with the main table; each row will contain a single value for the attribute and a link to the related row in the main table.

  • Tables corresponding to classes include columns for owned attributes; that is, attributes defined by themselves and not inherited from superclasses. This way, specializations are implemented distributing object data across several tables and allowing abstract views of objects by looking at each table in isolation. Figure 6 shows an example of this.

  • References between classes may be implemented in one of two possible ways: 1. For one-to-one or many-to-one references, an additional column may be added to the class table and linked to the related class table. Navigational queries can be resolved easily, but inverse ones must be performed by lookup. 2. For one-to-many or many-to-many references, an additional table is created with two columns, which are linked to the related class tables.

  • Hierarchical enumerators are stored in a specific table and enumerator items are stored in another table. Items are assigned encoded string identifiers so that abstract comparison can be performed without hierarchical navigation.

Unique indexes are created for OId columns and nonunique indexes may be created for columns involved in relationships. In any case, these conventions may vary in future versions of Strix as the installation engine becomes more sophisticated. In particular, hints given by the developer will be considered in a future revision.

Strix Dynamics and Use

To use Strix, you must write a text file containing the Middle specification of the system to implement, as well as decide on a database backend to use. The system can then be installed into Strix by calling the StrixRunTime.InstallSystem operation, and passing as arguments the Middle filename, an identifier of the chosen backend, and any backend-specific parameters. If a suitable store manager is available, Strix compiles the Middle specification and, if no errors are found, stores system metainformation for future use. Also, it loads the appropriate store manager and directs it to create the database structure that serves as data store. Once this is done, system installation is considered complete and the system can be used normally. Figure 7 is a sample system as shown by the MiMan Explorer tool.

To use an installed system, you must open it by calling StrixRunTime.OpenSystem and pass the desired system's name as an argument. This operation returns a pointer to a System object, which offers the following services:

  • Metainformation
  • CreateObject

  • RetrieveObject

  • Query

  • SaveObject

  • DeleteObject

  • CloseSystem

The Metainformation property returns a pointer to a MIPackage object, implemented by MiMan, which lets you navigate the system's metainformation and examine its classes, attributes, relationships, and enums.

The CreateObject lets you create a new persistent object. Only the class name must be passed as argument. The SRT relies on GiMan to create a GiObject object, and then attaches it to the specified class, gives it a unique object ID (OId), calls the store manager to insert it into the database, and returns a pointer to the GiObject to the caller. The GiObject, in turn, offers collections of values and tuples corresponding to its class's attributes and references, as well as the option to jump into the metainformation realm from any instance-level entity (for example, from a value to the linked attribute, or from an object to the correspondent class). Also, a GiObject can be serialized into a byte stream in any of several formats. Currently supported formats include the proprietary Serial Object Format (SOF), XML (a simple DTD is used), and human-readable text.

RetrieveObject takes a single OId as argument and returns a pointer to the retrieved GiObject. Once again, it relies on GiMan to create a GiObject, then invokes the store manager to query the underlying database and populate the GiObject with the proper values and tuples.

The Query operation also retrieves objects from the database but lets you submit a declarative query written in the Owl query language. On return, the operation will give the caller a pointer to a GiObjectCol object, which holds a collection of zero or more GIObjects. The Owl language closely resembles SQL but navigational expressions give it much more power. For example, the query:

SELECT Employee e WHERE e.WorksIn. Requester.Name LIKE 'a*'

retrieves all employees working in projects that have been requested by customers with a name starting with the letter "a." Relationships can be navigated by relationship name or role name. Owl does not support joins in the SQL sense, but it does support navigational expressions, object comparison, abstract comparison (antisymmetric version of conventional comparison that yields Car = Vehicle but Vehicle <> Car, useful with hierarchical enumerators), and collection membership test.

Once an object is created or retrieved, the programmer can modify it in a number of ways: adding values for its attributes, editing or deleting existent values, adding new tuples for its references, or removing existent tuples. These changes are accomplished through GiMan's classes and operations. To make changes persistent, the SaveObject operation must be called, passing the edited GiObject as an argument.

Deleting an object is as simple as calling DeleteObject and passing an OId as argument. Finally, CloseSystem closes the system and releases resources.

Extending Strix

Strix can be extended at three major levels:

  • Developing new store managers so it can use alternative backends.
  • Monitoring information exchange at the SRTSMI.

  • Using higher level layers on top of Strix.

To develop a new store manager, you must familiarize yourself with the two parties involved: the SRT and the specific back-end to use. Interfacing with the SRT is done through the SRTSMI, so we have prepared a Strix Store Manager Development Kit (SSMDK) that includes reference materials, practical recommendations, and a proposed architecture for new store managers. Interfacing with the specific backend is usually done by issuing SQL sentences to the database server through any available library. For example, the Microsoft Jet 4 store manager uses the ActiveX Data Objects (ADO) 2.5 library. The main purpose of a store manager, actually, is to translate from Asio commands sent by the SRT into SQL sentences or other database manipulation commands. An Asio interpreter is therefore a central component of every store manager. The SRTSMI provides a number of services to help you in dealing with Asio sentences.

Monitoring information flow inside Strix is another interesting extension. The SRTSMI defines the IStrixExtension interface, which can be implemented by any COM object wanting to receive events from SRTSMI. This COM object should register itself with SRTSMI, specifying which kinds of events, store managers, and systems are of interest, so it receives an event each time an Asio command is sent to a store manager or some other operations are performed. The StrixTrace tool uses this feature to show a trace of all information passing through Strix.

Finally, using wrapper components to access Strix instead of directly interfacing to it can be useful in many cases. The Scops component (see Figure 1) offers language-specific class wrappers for the classes defined in the system, so a more convenient programmatic interface can be used. Listings Two and Three are Visual Basic code for retrieving a list of all projects requested by a specific customer using raw Strix and using Scops. With Scops, code is shorter and more readable, and strong typing at the application-domain level avoids many run-time type checks.

In addition, the Ulula component (Figure 1) implements an emergent-user interface engine, so user-interface elements (such as forms and reports) can be dynamically built at run time or installation-time instead of having to be hardcoded by hand. Ulula uses metainformation to set up forms and fields, pick the most appropriate user-interface control (text boxes, drop-down lists, check boxes, and so on) to edit each attribute value, and offer relating, grouping, and sorting options when building a report definition. Adding an attribute to a class or changing the class model itself would not require manually editing the application front end because Ulula will change the user interface accordingly the next time it is invoked. Of course, a performance penalty exists, as working versions of Ulula demonstrate, but today's powerful computers are much cheaper than human workforce.

Acknowledgments

Many people have contributed to the BlueMoon project. I am grateful to Roberto Gomez Mendez and Javier Cancela Vicente, and especially to Pablo Criado Boado, whose chaotic sense to delve into complexity has helped me so many times. And of course, I owe a wealth of gratitude to Isabel, my significant one.

DDJ

Listing One

system Test {Verdewek/Test}
enum EProjectType
 InformationSystem
 Component:
  COMServer
  ActiveXControl
  .
 WebSite
end
class Person abstract
 attribute Name: 1 String 40
 attribute Address: String 200
end
class Customer
 isa Person
 attribute Code: 1 String 8
 reference Requests: 1..n Project inverse RequestedBy bound
end
class Employee
 isa Person
 attribute Wage: Fixed
 attribute Job: String 40
 reference WorksIn: Project inverse HasTeam main
end
class Project
  attribute Name: 1 String 40
 attribute Type: 1..n EProjectType
 attribute DateDue: TimeStamp
 attribute Finished: Boolean
 reference RequestedBy: 1 [Requester] Customer inverse
Requests main
 reference HasTeam: [TeamMembers] Employee inverse WorksIn
 invariant FinishedInPast: Not Finished Or (Finished And DateDue< Now())
end

Back to Article

Listing Two

'retrieve target customer.
Dim obCustomer As GiObject
[retrieve it in any way you choose]

'prepare collection to hold projects for the customer.
Dim obsProjects As GiObjectCol
Set obsProjects =
obCustomer.Package.CreateObjectCol("Project")
Dim obProject As GiObject

'iterate tuples of target customer.
Dim tu As GiTuple
For Each tu In obCustomer.Tuples
 'if tuple belongs to the Requests reference...
 If tu.Reference.Name = "Requests" Then
  'retrieve related project.
  Set obProject = sy.RetrieveObject(tu.TargetOId)
  'and add it to the collection.
  obsProjects.Add obProject
 End If
Next tu

Back to Article

Listing Three

'retrieve target customer.
Dim cus As Customer
[retrieve it in any way you choose]

'prepare collection to hold projects for the customer.
Dim pjs As Projects

'retrieve project list.
Set pjs = cus.Requests

Back to Article


Related Reading


More Insights






Currently we allow the following HTML tags in comments:

Single tags

These tags can be used alone and don't need an ending tag.

<br> Defines a single line break

<hr> Defines a horizontal line

Matching tags

These require an ending tag - e.g. <i>italic text</i>

<a> Defines an anchor

<b> Defines bold text

<big> Defines big text

<blockquote> Defines a long quotation

<caption> Defines a table caption

<cite> Defines a citation

<code> Defines computer code text

<em> Defines emphasized text

<fieldset> Defines a border around elements in a form

<h1> This is heading 1

<h2> This is heading 2

<h3> This is heading 3

<h4> This is heading 4

<h5> This is heading 5

<h6> This is heading 6

<i> Defines italic text

<p> Defines a paragraph

<pre> Defines preformatted text

<q> Defines a short quotation

<samp> Defines sample computer code text

<small> Defines small text

<span> Defines a section in a document

<s> Defines strikethrough text

<strike> Defines strikethrough text

<strong> Defines strong text

<sub> Defines subscripted text

<sup> Defines superscripted text

<u> Defines underlined text

Dr. Dobb's encourages readers to engage in spirited, healthy debate, including taking us to task. However, Dr. Dobb's moderates all comments posted to our site, and reserves the right to modify or remove any content that it determines to be derogatory, offensive, inflammatory, vulgar, irrelevant/off-topic, racist or obvious marketing or spam. Dr. Dobb's further reserves the right to disable the profile of any commenter participating in said activities.

 
Disqus Tips To upload an avatar photo, first complete your Disqus profile. | View the list of supported HTML tags you can use to style comments. | Please read our commenting policy.