What is ECO Anyway?
I remember when I first heard about Bold for Delphi, I didn't really know what it was so I
emailed BoldSoft to ask. Either Jonas or Jesper sent me a document they had just written
explaining it. The document went on to describe blind men encountering an elephant, each
one feeling a different part of the animal and assuming it was something different (a tree,
snake, etc). Well, that made it as clear as mud, so I promise not to mention blind men or
elephants from this point onwards.
This article will give an overview of the abilities of ECO but will not describe how to use
them, it is meant only to explain what ECO is so that you may decide for yourself if you
would like to investigate further. This article will not explain UML. If you are not
familiar with some very basic concepts such as Classes and Associations then I recommend
you do a small amount of preparatory reading on that subject first.
Index
ECO is an application framework
A very brief way to summarise ECO is to say that it is an application framework, by this I
mean that it helps you to build applications quickly. Delphi itself is allows you to
create applications quickly, so what is so different about ECO?
Delphi provides lots of tools for creating nice looking applications, especially when
additionally using third party extensions, but when it comes to the business layer there is
still a lot of work to do. Probably everyone knows that it is bad practise to mix your GUI
code in with your business logic, yet many people still do it. People who do not mix their
presentation and business logic either have to do a lot of manual work creating database
tables, stored procedures, etc; or have previously spent a lot of time writing their own
library to map objects to a database.
Forgetting for a minute all of the nice additional services of ECO, you could say that this
essentially is what ECO does for you. ECO allows you to define your business layers as a
collection of associated classes. These classes remain completely separate from any GUI
code, and may even be compiled into their own re-usable .net assembly, making it easy to
reuse the business logic for multiple user interfaces (WinForms, WebServices, Web
Applications, Import / Export routines).
Working with objects
Instead of selecting data from tables, and inserting new rows into tables, you now work
purely with objects. For example
[SQL]
insert into
Customer (Name, Telephone)
values
('Air Software Ltd','+44 (0)121 243 6689');
[Pascal]
NewCustomer := Customer.Create(EcoSpace);
NewCustomer.Name := 'Air Software Ltd';
NewCustomer.Telephone := '+44 (0)121 243 6689';
EcoSpace.UpdateDatabase;
[C#]
Customer newCustomer = new Customer(EcoSpace);
newCustomer.Name = "Air Software Ltd";
newCustomer.Telephone = "+44 (0)121 243 6689";
EcoSpace.UpdateDatabase();
To create a related object is just as simple.
[Pascal]
AlternateTelephoneNumber := TelephoneNumber.Create(EcoSpace);
AlternateTelephoneNumber.Name := 'Home number';
AlternateTelephoneNumber.Number := '+44 (0)121 999 9999';
NewCustomer.ContactDetails.Add(AlternateTelephoneNumber);
EcoSpace.UpdateDatabase;
[C#]
TelephoneNumber alternateTelephoneNumber = new TelephoneNumber(EcoSpace);
alternateTelephoneNumber.Name := "Home number";
alternateTelephoneNumber.Number = "+44 (0)121 999 9999";
newCustomer.ContactDetails.Add(alternateTelephoneNumber);
EcoSpace.UpdateDatabase();
The benefits of working with objects
The above examples show how to manipulate your data as "Business objects" rather than as
data, but why would you want to do this? Probably the main reason for taking the OOP
approach is the ability to use Polymorphism.
In the above example I used "ContactDetails.Add()" to add the alternate telephone number.
You may assume that this is merely a list of telephone numbers, but why should it be? Take
a look at the following UML diagram.
In this diagram you can see that a Customer has zero or many ContactDetails.
ContactDetails has only a single attribute, "Name", it is also an abstract class so
instances of ContactDetails may not be created. From ContactDetails I have descended an
EmailAddress class that has an "Address" attribute, and a TelephoneNumber class that has a
"Number" attribute, I have also modelled a FaxNumber class which is a type of
TelephoneNumber. This allows me to record various types of contact information for a
customer, and group them all together under the generic name of "ContactDetails".
Of course objects may also have virtual methods, which is probably one of the most powerful
features of OOP. By using object inheritance it is possible to modify the default
behaviour of our objects.
The above example is not meant to be a real-life one, there are reasons why I wouldn't take
this exact approach that aren't really relevant to this article. The purpose of the above
model is to demonstrate polymorphic behaviour.
In the model you will see that we can enter a list of computers on our network.
Periodically these computers check what tasks they have to perform. To do this they do the
following
- Locate a computer object with their own unique name
- Call ExecuteScheduledEvents
ExecuteScheduledEvents will do the following
- Get a list of all related "ScheduledEvents" that have a NextDueDate <= Now
- Call ExecuteActions on each
A ScheduledEvent has one or more tasks, these tasks may belong to zero or many
ScheduledActions so are easily reusable. The Scheduled event will simply call
Action.Execute() on each action in its list. This list of actions is ordered, so the order
of the actions is predictable.
What is not predictable from the context of the ScheduledEvent is exactly what
Action.Execute() will do. All ScheduledEvent has is a list of object instances which it
knows have a certain abstract class as their base type, concrete descendant classes
determine the action action performed. A ScheduledEvent may do something like this
- ScanDisk
- DefragDisk
- BackupDatabase
OOP summary
Having code execution within your business model really does introduce a lot of power to
your application. The addition of introducing OOP and polymorphism allows you to
additionally model your business classes in a more abstract way, which is always a good way
of planning for future enhancements.
What is an EcoSpace?
The EcoSpace is an in-memory object cache of your object instances. The EcoSpace does not
start by loading all of your database into memory, instead it employs lazy-fetch techniques
that I will describe later.
The EcoSpace is your application's way of managing instances of your business classes.
EcoSpaces do not belong in a .net assembly along with your business classes, instead you
should have an EcoSpace within your application. At design-time you are able to tell the
EcoSpace which package(s) it will be working with, ECO determines the list of available
packages by searching the current project + used assemblies for UML packages containing
your modelled classes.
Object persistence
ECO does not use the term "Database" for a very good reason. You don't have to save your
objects to a database. For example, there is an XML persistence component that allows you
to persist your object instances to disk, another "persistence mapper" will update your
data to a remote ECO powered application using remoting, or you could allow your
application to connect directly to the database and use a BDP or SQL persistence mapper.
NOTE: XML persistence should not be used in released software, it is
for testing only
Once you have specified which classes the EcoSpace will be managing, and also configured
your persistence settings, it is now possible to generate your database schema. Using the
default settings it is possible to create your database with the single click of a button;
all of the tables and columns required to store data about your class instances are
generated instantly.
Alternatively you have two more persistence options
- You may enter custom mapping information to map your instances to an existing
database
- You may reverse engineer an existing database in order to create your business
model
My favourite feature of ECO persistence mapping is without doubt the database evolution
support. When you later revise your model structure + update your application you will
also need to modify the structure of your database. It is rarely acceptable to wipe your
database clean and regenerate because there may be data help within it. This is where
database evolution comes in. Database evolution modifies your database schema
without losing your data, database evolution can handle the following changes to
your model.
- Renamed classes
- Renamed attributes
- Renamed associations
- Moving an attribute/association up or down a class hierarchy
An example of renaming a class "Preson" to "Person", renaming an attribute "FristName" to
"FirstName" within the same table, and then moving an attribute "DateOfBirth" from a
descendant class into Person would produce steps something like this
- Create table Person
- Move data from Preson to Person
- Drop table Preson
- Create column Person.FirstName
- Move data from Person.FristName to Person.FirstName
- Drop column Person.FristName
- Create column Person.DateOfBirth
- Move data from Employee.DateOfBirth to Person.DateOfBirth
- Drop column Employee.DateOfBirth
Of course moving an attribute up the class tree has no code implications in your source
code, because Employee objects still have a DateOfBirth attribute due to inheritance.
ECO can create your database structure, it can map its objects to your existing database
structure, it can evolve your database structure (even if it was not created by ECO), and
can even reverse engineer your current database structure into business classes.
Additionally it is possible to persist your objects to non-database storages by
implementing your own PersistenceMapper component.
Object caching and lazy-fetching
Once an object instance has been fetched from the persistence storage its values are cached
within the EcoSpace. Whenever you modify the attribute values (properties) the cache is
updated. Even if your object instance in source-code goes out of scope and is subsequently
garbage collected, ECO will hold a cache of its attribute values in memory. The next time
you retrieve your object instance using this EcoSpace, the EcoSpace will return an object
based on this cache rather than requiring a persistence request.
As I mentioned earlier, ECO does not start its life by fetching all of your objects from
the database, although by looking at the following source code you would be forgiven for
believing it did.
Assume that only "MyCustomer" is currently loaded
[Pascal]
for ContactDetails in MyCustomer.ContactDetails do
ContactDetails.Name := ContactDetails.Name + '!';
[C#]
foreach(ContactDetails contactDetails in MyCustomer.ContactDetails)
contactDetails.Name += "!";
So, if only MyCustomer is loaded, how is it that you don't have to explicitly tell ECO to
load all of the objects in the ContactDetails association?
ECO holds a cache of object instances plus their properties. Whenever you read or write a
property of one of these object instances the read/write is actually passed through to the
EcoSpace cache. This approach has a number of benefits, a few of them are
- Objects are not re-fetched from the persistence storage, they are recreated from the
cache, this saves time
- Requesting an object instance from the EcoSpace multiple times results in the same
instance being returned from the cache, so updating a property in one reflects in them
all
- The EcoSpace is able to automatically fetch required data from the persistence
storage if it doesn't currently have a cache entry for it
In the example code above an attempt is made to read MyCustomer.ContactDetails.
Multi-links are not fetched with objects when they are retrieved from the persistence
storage, so the EcoSpace requests a list of object identifiers from the persistence storage
service. As you then access each ContactDetails instance within MyCustomer.ContactDetails
again the EcoSpace will issue a request to the persistence storage service to retrieve that
object.
NOTE: If you are going to loop through a known list of objects it is
possible to instruct ECO to pre-load those objects in advance as economically as
possible
Delay-fetched attributes
Some of your modelled classes may have attributes that are expensive to retrieve. A binary
attribute holding a passport photo could be considered expensive, both to transfer over the
network and to hold in memory. It would be unwise to retrieve such an attribute every time
you loaded a Person object for example, because in most situations your software will
probably be working with a Person in ways that do not require anybody to look at their
photograph.
ECO allows you to mark attributes in your classes as Delay-fetch. When an object instance
is retrieved from the persistence storage any attributes identified in this way are not
retrieved. Only when you try to access the value of the attribute will ECO issue a request
to the persistence storage service to retrieve it. This approach allows you to keep memory
and network usage down.
NOTE: It is currently not possible to unload an individual attribute,
however, it is possible to discard an object instance's cache so that its values are
reloaded when next accessed
The object constraint language
Some people refer to the OCL as "SQL for objects", which isn't a bad comparison. OCL is
used throughout ECO applications, both in your business model, and also on your forms /
WebForms, and in your source code too.
This overview article will not describe OCL in any great detail, the language is too large,
but I will describe some common uses for OCL and also explain what the OCL expression I
have used means. For more information on OCL you should consider reading through Anthony
Richardson's excellent article (originally written for Bold for Delphi) here
w
ww.ViewPointSA.com
OCL in your business model
Within the model itself there are a number of places in which OCL may be used.
Object constraints
It is possible to add a list of name/expression elements to any class within your model.
ECO does nothing with this list internally, but it is possible to use this constraint list
to ensure object validity. For example, a class implementing a hire period may have the
following two constraints
- Start date required : not startDate.isNull
- End date must be after start date : endDate > startDate
I always use such constraints in my model. I am able to enable or disable the Save button
on my WinForms depending on whether or not all of the constraints of the current object
have been met or not, or I can use the ASP .net ValidationSummary to display messages about
broken constraints on the current object and prohibit saving.
Whenever new rules are required, I update the constraints in my model and redistribute it.
I don't need to recompile my WinForm app or my website project, and the exact same rules
are applied to both interfaces.
Derived attributes / associations
Dataset programmers are familiar with calculated fields. Derived attributes/associations
are similar except for a few important points
- The member is a valid part of the model, so is accessible via code or any
WinForm/WebForm. Unlike a Dataset it does not have to be implemented each time you want to
use it.
- The member is lazy-calculated. Some calculations may be expensive to perform, so ECO
will only calculate the value if you attempt to access its value in some way
- The result of the calculation is stored in the EcoSpace's cache. This saves it from
being recalculated each time. Every element used in the calculation is "subscribed" to, so
if any of the variable values involved alter the cached value is marked as invalid.
NOTE: It is not only possible to calculate derived attributes /
associations' values using OCL, you may also calculate them by implementing certain methods
in your class
Take a look at the following UML diagram.
This is a real-life example of an ECO model actually in use. I used this structure on www.HowToDoThings.com. I have indicated all
derived attributes / associations by colouring them red (associations) or a nice pink
colour (attributes).
This UML structure is used to categorise articles on my website. According to the diagram,
articles on my website can belong to a single SubCategory. A subcategory must have exactly
one parent, which is a BaseCategory, this enables a Subcategory to be parented either by
another SubCategory or a MainCategory (which has no parent because it is a root category).
SubCategory has an attribute named "ArticleCount", which is an Integer. The OCL for this
attribute is
articles->size
"articles" is a member of SubCategory, it is a one-to-many association linking a
SubCategory to multiple articles. The expression "articles->size" tells ECO that we want
to know how many articles there are, note that this does not load the articles into the ECO
space. Adding a new article, deleting an article, or moving an article to a new
SubCategory will automatically mark the cached result of this OCL invalid.
A huge benefit of having these "calculated fields" actually within the model comes from now
being able to use these attributes in OCL as well. Looking further down the SubCategory
class you will see an attribute named "TotalArticleCount". Where ArticleCount shows the
number of articles in this category, TotalArticleCount will show the number of articles
belonging to this SubCategory and the total number of articles of any SubCategory
beneath this point. This is achieved with the following, very simple, OCL expression
articleCount + subCategories.totalArticleCount->sum
This expression is so simple because it uses a clever recursion trick.
- Count how many articles I own directly
- Add to this the "TotalArticleCount" of each of my child SubCategories
The "TotalArticleCount" of each of the subcategories does exactly the same, it returns the
number of articles it owns directly + the sum of all "TotalArticleCount" attributes of its
child SubCategories. This recursion continues until eventually you reach the leaf nodes in
the structure and there are no more SubCategories, at this point the expression
"subCategories" returns nil, and therefore the result is calculated as
articleCount + (zero)
Derivation expressions
Derivation expressions are an easy way of overriding an expression of an inherited
OCL-derived attribute or association. Against each class it is possible to enter a list of
name/expression pair values identifying which member you want to override, and its new OCL.
In an application I wrote recently I had a model in which different users manage many
"things". There were many different types of "things" within the model, the
"InterestedParties" for each depended on what the "thing" was and what state it was
currently in (live, pending, cancelled). Any user of the system could send a
message/request to a relevant user by seeing who its InterestedParties were. An example
would have been the User class itself.
[Thing]
InterestedParties=User.emptyList
[User]
InterestedParties=Administrator.allInstances
NOTE: "Administrator" is the class name, "allInstances" is an OCL
operation on a class to retrieve all instances.
This is a very powerful approach because I have the full power of OCL to determine would
should be available to deal with queries/requests relating to an object.
OCL in your source code
It is also possible to execute OCL statements within your source code using the OCL
service. This technique is useful for
obtaining references to objects, or for evaluating the result of a complex expression
that would otherwise require
many lines of code.
ECO handles
This section will be very brief. I merely want to reassure people that normally use
Dataset and grids etc that they do not suddenly need to start depending completely on
writing source-code.
ECO handles are components that may be dropped onto your WinForm/WebForm at design time.
These components provide access to the "ECO world". For example, the ExpressionHandle
allows you to enter an expression such as "Customer.allInstances", this expression
retrieves all Customer object instances.
These handles support standard .net databinding, so it is now possible to databind visual
controls to the handle to allow the user to edit the attributes of the objects. From a
user's point of view there is no difference between editing ECO objects and rows from a
Dataset.
ECO Services
The EcoSpace provides multiple services that provide you with some additional features.
Some of these services are very simple, some of them are very powerful. The EcoSpace will
return an instance of a particular type when requested.
[Pascal]
PS := EcoSpace.GetEcoService( typeof(IPersistenceService) ) as IPersistenceService;
[C#]
ps = (IPersistenceService) EcoSpace.GetEcoService( typeof(IPersistenceService) );
Some of these services are summarised below.
Persistence service
The persistence service allows you do perform the following actions
- Apply all changes to the persistence storage
- Apply all changes for a specified list of objects
- Retrieve objects using a criteria
- Discard ECO cache values for an instance
- Retrieve changes made by other users
Dirty list service
The dirty list service tracks all modified (dirty) objects. You can obtain a list of
all dirty objects in the EcoSpace using this service.
Extent service
This service provides the developer with the ability to work with all instances of a
particular class, it performs the
following actions
- Retrieve all instances of a specific class
- Receive notifications whenever an object is added/deleted
External ID service
This service allows you to obtain the unique ID of a particular object instance, or to
object an object instance from the EcoSpace based on its unique ID.
OCL service
This service allows you to execute OCL statements from within your source code and retrieve
the result. It also allows you to optionally subscribe to the OCL statement so that you
receive a notification when any of the elements in the expression change. This service can
also be useful for retrieving objects from the persistence service. See the article Two functions to evaluate
OCL on www.HowToDoThings.com.
State service
The state service merely provides the ability to determine whether a particular object
instance is modified or not.
Undo service
The undo service allows you to programmatically start a named "undo block". Whenever an
object instance is modified, the EcoSpace will automatically store its old value in the
active UndoBlock (if there is one). It is then possible at a later point to undo your
changes by reversing the changes in the undo block, it is also possible to then redo those
changes.
The undo service also provides in-memory object change transactions. This is basically
identical to the undo service, except you do only use StartTransaction(),
CommitTransaction(), or RollbackTransaction() rather than having to work with named undo
blocks.
Version service
Have you ever seen the WayBack machine? This is a
website that spiders other sites, whenever the site changes the new pages are cached,
enabling you to look at a website at any given date.
This is similar to the version service in ECO. In ECO you can identify a class as being
versioned. Each time modifications are persisted they are saved as a new version of the
object. Whenever you request that object ECO will return the latest version. Using OCL it
is possible to navigate through objects and associations at any given date and time, so it
is possible to see historical data.
The version service provides information about versioned classes such as
- Determine the current version number of an object instance
- Retrieve a historical instance of an object
- Determine the date/time of a specific instance
Summary
As you can see, ECO is not UML, and is so much more than an object persistence framework.
There is quite a lot of information in this article about ECO, yet I suspect that there is
probably a lot I have missed out, for example, I haven't explained the following
- Optimistic locking
- Object regions
- Multi-user synchronisation
The latter is covered in an excellent article by Roland Kossow here. I may write follow
up articles on the first two at a later date.
Hopefully this article has given you some idea of "What" ECO is, as opposed to "how" to use
it. I also hope that it has been successful in arousing your interest in this excellent
development tool, and you will consider looking into the "how" part a little more.
There are various people in the borland.public.delphi.modeldrivenarchitecutre.eco newsgroup
willing to help you with any questions you may have, and also a dedicated ECO
section on www.HowToDoThings.com.
About the author
Peter Morris has been developing model-driven applications since his introduction to Bold
for Delphi (ECO's predecessor) back in 2001. He is the managing director of Air Software Ltd, a company that provides bespoke
software solutions and consultancy services to various industries.
Elephant, blind man.
Connect with Us