Showing posts with label ECO. Show all posts
Showing posts with label ECO. Show all posts

2008-02-02

ECO docs progress

I'm currently in the process of migrating the QuickStart series from BDS over to VS and recreating the accompanying source code.

There's quite a lot of information in those articles, I hadn't realised how much I had written! Well, the transcription is going quite well. So far I have made it as far as article number 5. This one is going a bit slower because it is also a translation from VCL .NET to WinForms.

2008-01-30

MonoRail

I'm working on a new website for work. I've decided to use ECO for the business model due to how much time it saves me. I took a look at the new MVC ASP approach provided by Microsoft recently and was a bit disappointed. There were bugs in some pretty basic errors that would have been an annoyance to code around, and it just didn't feel "ready".

So, I've decided to take another look at MonoRail. I'd already written an ECO implementation for MR in the past but I decided to start the implementation from scratch. This was mainly inspired by the new EcoSpaceManager in ECOIV for ASP .NET. Using an EcoSpaceManager you can easily utilise many instances of different types of EcoSpace in the same page. I decided I would do the same.

Unlike the EcoSpaceManager I haven't gone for unique string values for identifying the EcoSpace instance I want. That approach is good in ASP .NET where you want to bind different components together to generate your HTML but it doesn't really make as much sense when everything you produce is written in code. If you want two instances of the same EcoSpace you can just use EcoSpaceStrategyHandler.GetEcoSpace().

Anyway, on to the detail:

The first thing I have done is to specify EcoSpace and EcoSpaceProvider settings as reflection attributes on the class (controller) and method (action). Like so

[UseEcoSpacePool(true)]
public class AccountController: EcoSmartDispatcherController
{
  public void Index()
  {
  }

  [UseEcoSpacePool(false)]
  public void SomethingElse()
  {
  }
}
In this example the EcoSpace pool will be used for all actions (methods) except SomethingElse which explicitly says not to use it.

If you want to specify EcoSpace type specific settings in your web app then this is possible too:

[UseEcoSpacePool(true)]
[UseEcoSpacePool(typeof(MyEcoSpace), false)]
public class AccountController: EcoSmartDispatcherController
{
  public void Index()
  {
  }

  [UseEcoSpacePool(typeof(MyEcoSpace), true)]
  public void SomethingElse()
  {
  }
}
In this example the default is to use the EcoSpace pool unless you are retrieving an instance of MyEcoSpace in which case it wont be used. However, if the action being invoked is SomethingElse() then retrieving an instance of MyEcoSpace will use the pool. The order of priority is as follows:
  1. Apply settings on the class that are not specific to an EcoSpace type.
  2. Apply settings on the method that are not specific to an EcoSpace type.
  3. Apply settings on the class that are specific to an EcoSpace type.
  4. Apply settings on the method that are specific to an EcoSpace type.
So the order of priority is that Method settings override Class settings, and then EcoSpace type specific settings override non specific settings.

Pretty versatile eh? But how do you get an instance of an EcoSpace? Well, that's pretty simple too!
public void Index()
{
  MyEcoSpace myEcoSpace = GetEcoSpace<MyEcoSpace>();
  //This will return the same instance
  MyEcoSpace myEcoSpace2 = GetEcoSpace<MyEcoSpace>();
}
Any EcoSpaces requested in this manner will automatically be "released" immediately after the method finishes executing so you don't need to worry about it at all. I say "released" because it will either be Disposed, stuffed into the session, or returned to the pool based on your EcoSpaceProvider settings for this class/method/EcoSpace type.

That's not the end of the story though. You can bind instances of your objects directly to HTML and back. To do this you need to identify your business class with a reflection attribute "EcoDataBind", like so
[AllowDeactivateDirty(true)]
public void Create(
  [EcoDataBind(typeof(MyEcoSpace), "Product", CreateIfNoObjectId=true)]Product product)
{
  PropertyBag["Product"] = product;
}
Here I have stated that it is okay to Dispose the EcoSpace instance if it contains dirty objects at the end of this method. The method itself consists of a single parameter named "product" of type "Product". The EcoDataBind might look a bit overwhelming but it says this
  1. The EcoSpace type you need to home the Customer object is MyEcoSpace.
  2. The prefix in the form to look for is "Product", so when you see <input name="Product.Name">> MonoRail knows that the value should go into the Name property of this product.
  3. If there is no ExternalId in the form identifying the object to fetch from the data storage before updating then a new instance should be created.
As a result when you run your app and go to localhost/account/create you will see that the product method has a new instance in there. When the user posts the form back you will again see an instance but containing the updated values. What does the HTML look like? I have used the Brail view engine and HTML helpers to output the HTMl I need. This allows you to use whatever HTML you like but then easily add the <input> etc based on your current object.

${HtmlHelper.Form('create.rails')}

  ${EcoFormHelper.PersistedExternalId('Product.ExternalId', Product)}

  Name : ${HtmlHelper.InputText('Product.Name', Product.Name)}

  Current version number : ${HtmlHelper.InputText('Product.CurrentVersionNumber', Product.CurrentVersionNumber)}

  ${HtmlHelper.SubmitButton('Save')}

${HtmlHelper.EndForm()}
I have split the lines up a bit to make them easier to visually separate.

First an EcoFormHelper is told to output a hidden input named Product.ExternalId for the product we set in the C# method (see PropertyBag["Product"] = product). EcoFormHelper.ExternalId will output the ExternalId for the object, PersistedExternalId will only output if the object is not new, this is useful in situations like this when the object was disposed of with the EcoSpace it belong too and we can just create a new instance.

Next the HtmlHelper gives us an <input> named "Product.Name" and its value is set to whatever is in the Product's Name property. The same is done for CurrentVersion.

A Submit button is then generated so that the user may post their changes.

Summary
Well, this little example shows that I can implement a nice clean MVC style approach to writing web apps with ECO and not have to worry about constructing EcoSpace instances in code, fetching objects from the data storage and so on manually; everything is done for me.

2008-01-20

Why I don't use ExternalID for URLs

An External ID consists of two parts, !

ClassID isn't really required for ECO controlled DB structures because there is always a root table containing the ObjectID + ClassID, but when you use ECO to map to an existing DB structure there needs to be a way to know that object 1234 needs to be fetched from the PERSON table.

So, now that we know why the ExternalID consists of two parts on to why I don't use it (much). If I am writing a website with ECO and I use the ExternalID for my URL like so

www.mysite.com/showarticle.aspx?id=23!1234

The number 23 is determined by looking for the class's index in the list of all classes in the model. This is the problem! If you change your model by adding a new class then your index may change from 23 to 24. Not a big problem for your application, but persistent links to that URL from other sites such as Google will no longer work.

An ExternalID is just like an object's pointer address. When you restart your application that address is no longer valid. ExternalID lives longer than an application restart, but doesn't always live past an application remodeling as explained above.

My tips are as follows:
  1. For passing object instances around in code, just past the object instance itself.
  2. For passing a reference to an object to another EcoSpace instance in the same application use ExternalID.
  3. For storing away a reference for an unknown period of time (config file settings, URLs) add a unique attribute to your class such as an AutoInc and retrieve it using that value in an OCL evaluation.
URLs are really the only example I can think of where an ExternalID is persisted externally and expected to live longer than the life of the current model. In this case I think the following URL is much more pleasing to the eye anyway

www.mysite.com/showarticle.aspx?id=1234

2007-09-29

Derived properties that you don't want cached

I'm rewriting a Win32 application using ECO. One of the things this application allows the user to do is to specify their preferred unit of measurement for height, distance, depth, weight, and speed.

To achieve this I have an ECO class ApplicationSettings which is a singleton which identifies the units to use. I then have 2 properties for each value....

InternalLandingDistance
DisplayLandingDistance

The idea is that I should never expose Internal* properties in the GUI but display their corresponding Display* property instead. The Display* property will read/write the Internal* property and perform some kind of conversion on it.

If I always store my values using the unit with the highest accuracy (Distance = feet, Depth = millimetres, Weight = Pounds, Speed = KPH) then I just need to convert them by the correct factor when reading/writing from the Display* properties.

This isn't rocket science, obviously I can just implement these as reverse derived attributes right? The problem with this is that I am using a new EcoSpace per form, so if someone changes the ApplicationSettings.DistanceMeasurement in one EcoSpace my derived member has no way of knowing unless I want to put in synchronisation.

Well, my app can work stand-alone or on a network. I don't want to over complicate things, so what I decided I needed was a reverse derived property with no subscriptions + caching, but that just isn't possible.

Well, actually, it is. It's just so obvious! Instead of marking my members Derived I marked them as Transient and HasUserCode. I implement the code like so.....


public float DisplayLandingDistance
{
get
{
return ConvertDistanceToDisplay(InternalLandingDistance);
#if NeverDoThis
{EcoModeler generated code here}
#endif
}
set
{
InternalLandingDistance = ConvertDistanceToInternal(value);
#if NeverDoThis
{EcoModeler generated code here}
#endif
}
}



Now my values will be calculated every time they are read instead of being cached. The "#if NeverDoThis" bit is there so that EcoModeler will always put its auto-generated accessor code within a region that never executes. I could just put a "return" above it, but I don't like those "Unreachable code detected" warnings!

2007-08-31

RetrieveChanges(out ignoredChanges)

Here's another quick blog about a new ECO IV feature. I asked for this during development because I had a particular problem I had to solve.

My new application has a class named "PlannedCall". When the user logs in they see a list of planned calls that are
A: Active
B: Not already actioned
C: Assigned to the current user, or not assigned to anyone
D: EarliestCallTime <= Today + 1

As time goes on there will be a lot of instances of this class in my DB, so obviously I want to use the OclPSHandle to select my objects otherwise I will end up with a whole load of objects in memory that I do not need. However, when another user actions the planned call the Actioned property becomes true. If I use an ExpressionHandle then this planned call would disappear from the presented list, but with OclPSHandle it will not. This is where the new feature comes in!

When you call PersistenceService.RetrieveChanges a collection of DBChange will be sent to the client from the server. The EcoSpace will inspect this list and create an IChange each time the DBChange refers to an object instance loaded into the EcoSpace cache.

In the past the DBChanges that do not apply were discarded, but now you can get hold of them. Take a look at this:


DBChangeCollection ignoredChanges;
EcoSpace.Persistence.RetrieveChanges(ignoredChanges);

//Do what you normally do with the IChange list here!

//Now check if there are relevant changes to objects we have not loaded
int plannedCallClassId =
EcoSpace.TypeSystem.GetClassByType(typeof(PlannedCall)).InternalIndex;

foreach(DBChange currentChange in ignoredChanges)
if (currentChange.ObjectId.ClassId = plannedCallClassId)
{
SelectPlannedCalls();
break;
}


SelectPlanendCalls will just execute an OclPsHandle...

sqlPlannedCalls.Expression = "PlannedCall.allInstances->select(...whatever...)";
sqlPlannedCalls.Execute();


This will only load objects that meet the correct criteria.

Finally, my expression handle can work on PlannedCall.allLoadedObjects instead of PlannedCall.allInstances - something like this


PlannedCall.allLoadedObjects
->select(active)
->select(not actioned)
->select( (assignedTo->isEmpty) or (assignedTo = var_CurrentUser) )
->select(earliestCallTime <= DateTime.Now.AddDays(1))


So now I have the benefit of being able to limit which objects I use by utilising the OclPsHandle, but also the benefit of the list being subscribed to so that changes to my local cache will add/remove rows in the grid displaying the planned calls + the list is updated if someone else updates a planned call.

I've just tested it in my application. I'm so pleased that I felt compelled to tell the world :-)

2007-08-23

ECO IV

CodeGear are letting some people blog about features in the next release of Delphi (Highlander) before it has even been released. How cool is that? A lot less secrecy, what a great move!

Sooooo. How about I spill some beans about ECO IV? Maybe I can find a thing or two to mention :-) I'll just throw together something unplanned, so expect a weird mixture of stuff in no logical order whatsoever!


Well, first of all, you're going to get a whole load of source code. I mean *lots* of it!


There's a new service called the ICacheContentService. Let's say you know for a fact that there is an object in the DB with the ECO_ID 1234 and it is a Customer. Instead of loading that object into the cache with an OclPS evaluation you can simply use this service to register it in the cache. I've found this really useful in an app I am writing where I use SQL to find customers matching a certain sales criteria. I then create an IObjectList of these customers by injecting their ID's into the cache.


There are some generics in there. The generated code is smaller as a result because you don't need a seconary "List" class for each class in your model. PersonListAdapter, CustomerListAdapeter etc. In addition to this it is easier to move from the Element world to the Business Object world. You can do stuff like this

MyElement.GetAsObject<Customer>.Name := 'Hello';
for currentCustomer in SomeElement.GetAsCollection<Customer> do
currentCustomer.DoSomething;


You can hibernate EcoSpaces. Can you deactivate an EcoSpace to a stream, a kind of snapshot. You can then later activate an EcoSpace from a stream, restoring the original state of the hibernated EcoSpace. This is useful when you want to shut down an application and then restart it where you left off. Maybe you could use it to "keep changes" made on the client without having to save them, and then later update the database. One certain use for this feature I see would be for storing EcoSpaces in a session where ASP.NET is using a DB as the session state so that multiple machines can serve the pages (a farm). I've done this using MonoRail rather than ASP.NET.


You can write ECO VCL.NET apps. Okay, it's not a big secret or anything, but I can tell you how good it is! I haven't played with TDataSet components in years now, but it all came rushing back to me as I played with components such as TExpressionHandle and TReferenceHandle, both descendants of TDataSet. Hooking up a TDBGrid to an ECO expression handle was so strange at first. It seemed like I was hooking up to a TQuery, that is until I started executing methods on the current row, and invoking state machine transitions. Then I felt at home again :-)


ASP.NET DataSource. ASP.NET stuff is so much more simple now. You have an EcoSpaceManager component on the web page that provides an instance of an EcoSpace. Then you have an EcoDataSource component which you can bind GUI controls to that are capable not only of showing the data of the selected ECO objects but also update it too without a single line of code. This approach is so much more simple than using all of those ECO handles on the form and having to manually update the form data into the objects.


ASP.NET Providers. You know 'em, the user, roles, settings etc that come with ASP.NET 2. Well, ECO supports a whole load of those too!


More frequent updates. You wont see this one in the box, this is one that ships afterwards. In the past ECO updates could only be shipped as part of a Delphi update. Well, things have changed. The ECO team (www.capableobjects.com) can now determine their own release schedules.


OCL enhancements. The OCL implementation used has now been extended so that it mirrors the .NET framework more. So you can do stuff like this (in OCL action language)

DatePosted := DateTime.Now.Date.AddDays(1)


BlackFish support. What else do I need to say?


Mapping enhancements. This one might not be a big feature that you'd write on a box, but it saves so much time! Remember in ECO III when you had to write a persistence mapper for every ENUM type in your model? No more! The mapper classes are now provided with type information when converting between DB/Model values so it is easy to write a single generic mapper class to work with any type (enums for example). In fact, you can now just set the PMapper of any ENUM to GenericEnumAsInteger and your enum property will be mapped to an INT column in your DB. What a time saver this one has proven to be!


Nullable types. You can now model your class attributes as Nullable<Integer> etc. So the following code...

Self.AsIObject.Properties.GetByLoopbackIndex(Eco_LoopbackIndices.DatePosted).AsObject := nil;

you can now do this

DatePosted := default(Nullable<datetime>);


Well, that's it for now. If I think of anything else I'll let you know. It's hard to remember what's new when you've been playing with it for so long!

Disclaimer: The code in this blog might not compile after shipping. Not because it is subject to change and other such legal stuff, but because I am typing from memory :-)

2007-08-21

More secure passwords

Storing a plain-text password in a DB leaves your system open to abuse from anybody with access to the tables (sys admin for example). Here is the technique I recently used. Instead of storing the password itself I store a hash of the password, and a random "salt" that was used to create the hash to make it less predictable.


[UmlTaggedValue("Eco.Length", "255")]
private string PasswordHash
{
get
{
...
}
set
{
...
}
}

[UmlTaggedValue("Eco.AllowNULL", "True")]
[UmlTaggedValue("Eco.Length", "40")]
private string PasswordSalt
{
get
{
...
}
set
{
...
}
}



As you can see I do not store the password, just a hash + salt, both of
which are private. Here are the methods used to set the values of these properties.

public void SetPassword(string newPassword)
{
if (newPassword == null)
throw new ArgumentNullException("password");
if (newPassword.Length < 6)
throw new ArgumentException("Password must be at least 6 characters");

PasswordSalt = Guid.NewGuid().ToString();
PasswordHash = EncryptPassword(newPassword);
}

private string EncryptPassword(string password)
{
TripleDESCryptoServiceProvider des;
MD5CryptoServiceProvider hashmd5;
byte[] pwdhash, buff;
hashmd5 = new MD5CryptoServiceProvider();
pwdhash = hashmd5.ComputeHash(ASCIIEncoding.ASCII.GetBytes(PasswordSalt));
hashmd5 = null;
des = new TripleDESCryptoServiceProvider();
des.Key = pwdhash;
des.Mode = CipherMode.ECB; //CBC, CFB
buff = ASCIIEncoding.ASCII.GetBytes(password);
string result =
Convert.ToBase64String(
des.CreateEncryptor().TransformFinalBlock(
buff, 0, buff.Length)
);
return result;
}


Now the code required to get a user by ID/Password

public static User FindUserByUserIDAndPassword(IEcoServiceProvider serviceProvider, 
string userID, string password)
{
string query =
string.Format("->select(userID.sqlLikeCaseInsensitive('{0}'))",
BusinessClassesHelper.EscapeOcl(userID));

User user =
BusinessClassesHelper.SelectFirstObject(serviceProvider, "User", query);

if (user != null && user.EncryptPassword(password) == user.PasswordHash)
return user;
return null;
}


The BusinessClassesHelper is a class with static methods to help with OCL operations, such as escaping user input values to avoid OCL injection.

using System;
using System.Collections.Generic;
using System.Text;
using Eco.ObjectRepresentation;
using Eco.Services;
using Eco.Handles;

namespace xxxxxxxxxxxxxxx
{
public static class BusinessClassesHelper
{
public static string EscapeOcl(string ocl)
{
if (string.IsNullOrEmpty(ocl))
throw new ArgumentNullException("ocl");

ocl = ocl.Replace("\\", "\\\\");
ocl = ocl.Replace("'", "\\'");
return ocl;
}

public static T SelectFirstObject(IEcoServiceProvider serviceProvider,
string className, string criteria)
{
if (serviceProvider == null)
throw new ArgumentNullException("serviceProvider");
if (string.IsNullOrEmpty(className))
throw new ArgumentNullException("className");
if (criteria == null)
throw new ArgumentNullException("criteria");

IOclPsService psService =
EcoServiceHelper.GetOclPsService(serviceProvider);
IOclService oclService =
EcoServiceHelper.GetOclService(serviceProvider);

IObjectList result =
psService.Execute(className + ".allInstances" + criteria);
result =
oclService.Evaluate(className + ".allLoadedObjects" + criteria) as IObjectList;
if (result.Count == 0)
return default(T);
else
return (T)result[0].AsObject;
}
}
}

2007-07-13

UndoBlocks, SyncServer, and multiple EcoSpaces

In an ECO app I am writing it is very important that updates by other users are identified as soon as possible. This is because there will be multiple users all selecting jobs to do from a predefined list, and it is not only possible but also very likely that more than one user will select the same job at the same time (the job at the top of the list).

To implement this I chose to use the remote persistence feature of ECO and in particular its Sync Server feature. Every 5 seconds my client apps will contact the server and retrieve a list of updates made by other users, it will then apply those updates to the cache of its own EcoSpace. So when a user selects a job to do at the top of the list their app will stamp it as InProgress and update the DB, the job will then automatically disappear from the screens of all other users.

As I like to use undo blocks in my app to allow the user to cancel changes (using my DirtyObjectCatcher component) it is highly likely that during these synchronisation operations there will be an active undo block. As a result of this any changes applied to the cache via the sync' process will be caught by the undo block. If the user decides to cancel their changes then all values in the undo block will be undone, including the synchronised changes from other users. This isn't a great problem for me because I am using the TimeStamp feature in ECO to ensure that when I update the DB I am actually updating the same values I originally fetched, but it does mean that the user is looking at old data. For example

01: User A is looking at a list of jobs, they choose to action Job 1
02: User B sees Job 1 disappear from their screen and choose to action Job 2
03: User A sees Job 2 disappear from their screen.

So far, so good. Now remember, the change of state to Job 2 was caught by the undo block that was activated when User A started to edit Job 1.

04: User A decides to cancel their changes.
05: The active undo block reverses its changes, this includes the changes applied by the sync server.

As a result user A sees Job 2 reappear on their screen.

This didn't cause me an operational problem, because when actioning a job I do this...

01: Assign to current user
02: Update DB
03: If no exception then continue to edit
04: If OptimisticLockException occurs then unload the job and tell the user another user is working on the same job and ask them to select another + unload the job from the cache (will causes it to be reloaded with the new info and disappear from the screen).

This is essential anyway because the Sync server only makes it less likely to experience a conflict, it does not make it impossible (always use OptimisticLocking unless overwrites are acceptable!).

Still, I wanted to prevent the user from seeing this message as much as I possibly could. The answer is very simple, you just use a new instance of the EcoSpace for editing the job. The UndoBlock isn't actually needed now, but I still have it because the DirtyObjectCatcher provides a great way of providing validation feedback to the user. When the UndoBlock is reversed though it will revert to old values in the new EcoSpace instance, which is irrelevant because it will be disposed once the owning form is closed. Any updates to the data will appear in the main app within 5 seconds due to the fact that the second EcoSpace instance will also use remote persistence to save the changes which will in turn ensure that all other EcoSpaces (in the same app or on different machines) will be notified of the changes.

I think I might play with this technique some more. It looks like quite a nice way of working with ECO and multiple users!

2007-07-10

CapableObjects announced

There are exciting times ahead! The team that brought you ECO for Delphi are now a separate company named Capable Objects (http://www.capableobjects.com).

ECO will still be part of the next release of Delphi, but according to the official statement (http://dn.codegear.com/article/3673>) and comments in the newsgroups it is quite obvious there will also be a version of ECO for Visual Studio!

In the past the ECO guys released patches for ECO bugs, unfortunately it was not always possible to provide a patch. With the guys now being a separate company they are free to decide their own release schedules. Instead of having to wait for a Delphi update they can now release updates as often as they wish. Just like the good old BoldSoft days when we used to get an official update really quickly if ever a critical bug was found. Not only that, but it would also seem that they will be shipping source code too!

But that's not all John!

The guys at Capable Objects have asked me to write documentation. I've been adding XMLDoc to the source code so the API help will be more up to date. As soon as possible I will be writing a quick start document for Delphi (and any other IDE's) too.

Can't wait! :-)

2007-02-16

ECO JumpStart

I've been up quite late working out what I want to cover in the "ECO jump start" document. The trick is to start at a level where the user knows absolutely nothing, and end up where they know enough to decide whether or not they wish to spend some time learning how to use ECO or not.

I think I should start off explaining why multi-tiered app development is a good idea; then I think I should go on to creating a package in a DLL; then onto creating a non-persistent WinForm app using that package; then make it persistent; and so on. My problem is that ECO just does so much!

I'll paste what I have so far at the bottom of this blog entry to get any feedback. As the list progresses the subjects become more advanced and I am worried that the jump start might actually frighten people off by making them feel overwhelmed. Maybe I should just take it so far and then leave the more advanced items out? Maybe I should just include a section at the end of the document explaining what advanced features are available without demonstrating how to use them? Then maybe do an "Advanced ECO" document in which I follow on where I left off and demonstrate the more advanced stuff.

At the moment I am undecided, what do you think? Remember, this document is really intended to server two purposes; it should be there to help new users to understand ECO, and also to act as a promotional ("marketing" if you like) document to boast how good ECO is without scaring people away.

And now the table of contents:


Overview
Why create separate layers (GUI / Business / Data access)
An overview of how ECO uses UML as the design tool to sepcify the business layer
Explain that ECO can persist those business objects in a number of ways, the DB being switched in a matter of seconds


Creating a business classes package
Create project
Add references
Configure package settings
Add 2 classes with n--m association
Add properties
Add association
Generate code

Creating a non-persistent WinForm app
Create ECO WinForm project
Add reference
Select packages

Adding some simple GUI
Delete all but rhRoot
(Design support)
Set EcoSpace type
Add DataGrid
Set EcoSpaceType
Add ExpressionHandle (allInstances)
Bind the datagrid to the ExpressionHandle

Adding new objects
Add EcoListActions
Set RootHandle
Add button
Set the ListAction to Add

Drag and drop
Add another ExpressionHandle
Add another DataGrid
Add another "Add" button
Add CurrencyManagerHandle, set parent = DataGrid1
Add ExpressionHandle, expression = "self.AssociationToOtherClass"
Add a DataGrid
Add DragDrop extender
DataGrid2 = DragSource
DataGrid3 = DropTarget
Run + create + drag/drop

Making the objects persistent
Add XML persistence to EcoSpace
Add EcoGlobalActions to form
Connect to RootHandle
Add button
Set GlobalAction to UpdateDatabase
Run, create, save, close, run

Creating a database
Remove the XmlPersistence
Add DB persistence + set configuration
Add connection
Generate DB
Run, create data, save

Evolving a database
Add new properties
Rename a property + set FormerName
Add new super class + move property up
Evolve DB
Run, show same data

Quick prototyping the model
Set AutoForm = True on grids
Run app, show auto forms
Add button
Set GlobalAction = debugger
Run app, show debugger, show access to auto forms

State machines
A simple state machine with triggers
Triggers with paramters
Transitions with guards
Multiple transitions out of a single state with guards (explain how only 1 must be true)
Multiple transitions out of a single state with the same trigger
Nested regions
Concurrent regions
Final state

Multi user
Unloading the cache to see changes by other users
Turning on optimistic locking
Getting conflict information
ChangeActionKind
Locking regions

Remote persistence
Creating the remote persistence server
Registering as a remotable server
Registering as a HTTP remotable server available via IIS
Switching the WinForm to RemotePersistenceClient

Multi user synchronization server
What it is, how it works
Adding it to the remote persistence server
Requesting changes from the WinForm client
Run two apps, show the propagation of changes between them

ECO and ASP .NET
Create ECO web application
Reference the same model
Explain why we have a PersistenceMapperProvider component (only about caching the mapping information for now)
Show how to generate DB / evolve from PersistenceMapperProvider instead of the EcoSpace

ECO ASP .NET autoforms for protoyping in ASP .NET

Show the ECO WebForm
Recreate the same WinForm gui in a WebForm

ASP .NET performance
Show the pool size in Web.Config
Explain what happens when more people request pages than there are EcoSpaces in the pool
Explain the SessionStrategy
Explain the maximum lifetime setting in Web.Config

ASP .NET multi-user
Explain how EcoSpaces are kept in sync with each other via the PersistenceMapperProvider
Demonstrate how WebForm data is in sync
Demonstrate how the WinForm / WebForm apps do not stay in sync with each other
Migrate the ASP .NET app to use the persistence server
Demonstrate WinForm app reflecting changes to data made in a WebForm app

Overview of each of the ECO components

ECO in code
Switching between Object and IObject
Overview of some of the ECO services
UndoService, In-memory transactions + undo blocks
DirtyListService
PersistenceService
OCLPS Service
OCL service
OCL action language service


Advanced modeling techniques
HasUserCode = true
OCL derived attributes
Overriding OCL derived attributes in subclasses
Code derived attributes
Code reverse derived attributes
Derived associations
Transient properties
Transient classes
Transient associations
Association classes
Platform independant property types
AutoInc
Blob
Memo
Delayed fetch properties
Referencing other packages
Extending classes
Adding associations
Adding class constraints
Enforcing in a WinForm application
Enforcing the same constraints in a webform application
Disabling the auto-generated multiplicity constraints
Object versioning
Navigating historical data
Retrieving all historical versions of an object

Advanced database techniques
Reverse engineering a database
Cusomizing the mapping information to fit an existing database
Evolving the structure of an existing database
Using a single model to pull data from multiple databases (including mixed servers) and updating the data too

2007-02-07

ECO book at last!

Hi all

I have finally reached a point where I can dedicate a few hours each today to "something new" and have decided that the ECO book I have always wanted to write would be great fun. My first idea is to create 2 or 3 separate items:

Title: ECO jump start
This would be in paper / PDF format and would contain a set of exercises for a new user to follow in order to get started with ECO as quickly as possible. It would basically cover creating a simple model, prototyping using auto-forms, using the different handles, databinding, parent-child grids, creating/evolving the DB, OCL / code derived / reverse derived attributes, and stuff like that.

Title: ECO API
This would be an electronic format which would basically be a "Press F1" reference.

Title: ECO book
This would either be a paper/pdf format or, if possible, part of the ECO API document. It would be a technical overview of the framework + its services and abilities. I imagine it might be similar to my ECO Services chapter, except I'd probably include a set of "How to" items at the end of each chapter.


The plan here is that we get

1: An easy way for people to assess exactly what ECO does, and to quickly start creating simple "test" applications.
2: A complete reference for information on specific items where you would normally find yourself hitting F1.
3: A conceptual overview of the whole thing, explaining how it all works together and how to achieve certain goals.


This is what I have in mind at the moment, but it could all change based on the feedback I get :-) The above is basically the order I learned in when I learned Bold and then later moved over to ECO, it goes from simple through to in-depth. What I want to know is

A: Do you think I should go for 3 separate items or try to combine the last 2?
B: Is there anything I have missed?
C: What information would you like to see?

As a closing note I will paste in a table of contents I wrote 729 days ago when I first decided this would be a good idea if only I had the spare time :-) It's not necessarily what I still have in mind, but it might get some brain cells bubbling in your heads to get us all started off. If there are any topics you guys mention that I have missed I will add them to my list. Due to its age (1 day short of 2 years old) it is based on ECO II, so there are lots of nice new ECO III items I can put in such as creating your own services, state machines etc.


An overview of ECO
What is an ECOSpace
Modeling an application
Attributes, Methods, and Relationships
OCL Derived attributes
Code derived attributes
Reverse derived attributes
Inheritance Vs Composition
Packages
Creating a simple ECO WinForms app
Creating a simple ECO ASP.net app
Creating a simple ECO Webservices app
Borland.ECO namespaces
Handles
ObjectRepresentation
Persistence
Services
Subscription
UmlRt
WinForm
Persistence
Changing persistence
Evolution
Custom persistence mapping - AutoInc etc
Reverse engineering an existing database
OCL



Also need to include multi-user support, optimistic locking, change propagation, reconciliation, object locking regions, writing your own services + OCL extensions.



Thanks for your feedback!


Pete

2007-02-01

ECO extensions 2.1 released

I have just released a minor update to ECO extensions. It contains a fix for a bug that would prevent the DirtyObjectCatcher from catching modified objects for MDI child forms.

2006-12-26

Onion, more on signals

I've been trying to think through every type of feature I can that people might want in an application, and then seeing if I can think up a way of implementing it using signals. A couple of days ago I thought up something I couldn't implement using the exact approach I had.

I once wrote an app that had a treeview on the left hand side, the user could drill down to a customer of their choice and then when they clicked on that customer the display would update and show their details. This made me realise that a simple "SelectCustomer" signal would not be sufficient, what I actually needed to do was to have multiple SelectCustomer signals and indicate which customer to select.

The changes I have decided to make are as follows:
  1. I will introduce a struct called SignalCreateParameters. This will hold a string property "Parameters" which may be used to automatically populate some of the properties of the created signal, ie the customer identity.
  2. The signal factory will first be asked to populate a list of SignalCreateParameters, and then ultimately the target will be provided the opportunity of modifing that list, so that it may add additional items, remove them etc.
  3. Neither the signal factory or target will have GetSignalPermission. Instead the SignalCreateParameters will have a SignalPermission property so that individual customers may be disabled, removed etc within the list.
I've also decided that I really don't like having an ISignalFactory. I really don't like having to write two classes each time I want to add a single new "thing". Having to write two classes for each new feature is prone to error in my opinion, so I've been looking at using reflection again.

I originally used reflection but decided against it because it required the developer to
  1. Identify that the class is a signal (interface or attribute)
  2. Implement certain methods as static methods with a specific name + parameter list.
I went for the SignalFactory class approach so that I could create a base class and allow the developer to inherit from it, this would make it very clear which methods needed to be implemented. However I think that the penalty of developing two classes each time outweighs the time it takes to learn the method signatures required (1 constructor + 1 optional method).

So I think I will now remove the ISignalFactory part of the framework. The only thing I really need to consider is performance. On my 2.Ghz laptop executing a static method directly 1 million times takes 31 milliseconds, whereas via reflection it takes 2,109 milliseconds. This would mean that if there were 1,000 signals and 1,000 simultaneous requests the process would take 2.1 seconds per user. I don't suppose that is too bad really, if anyone has anything further they think I should consider then I'd like to hear from you. Just write to my droopyeyes.com email address (support@)

2006-12-20

Onion, part 3

Women can multitask

No matter how many times you might be told "women can multi-task!" it's just not true, humans can only do one thing at a time. I don’t doubt for a second that my wife's brain can keep track of multiple subjects much better than my single task brain, but at any one point her brain is only concentrating on a single task!

It's exactly the same for software. People may wish to deviate from their current task in order to fulfil some adhoc requirement, but that task is an interruption, it does not occur in parallel to what they were doing before. Once that interruption is over the user of your software wants to pick up where they left off. This is what a process driven (or "task oriented") approach to writing software is about.

Process driven work flow

A process in this context is a single task performed within an application in order to achieve a specific goal. The goal may be just about anything such as "Delete customer", "Print invoice" etc. The process may involve only a single step
  1. Are you sure you wish to delete this customer?
or multiple steps
  1. Select invoice
  2. Select action
  3. Are you sure you want re-print this invoice?
and at various steps,but not necessarily every step, the user may be required to provide some kind of input in order for the process to continue.

During its operation the process may call open the functionality of one or more other processes and act accoring to whether the process completed successfully or was cancelled, after the spawned process has terminated control should be handed back to the original process.

To achieve this we will require some kind of first-on-last-off (FILO) stack of processes. When a process is executed it is added to the stack and becomes the "active" process, when a process is completed or cancelled it is removed from the stack; at this point the re-activated process is informed why it has been re-activated, just in case it needs to act according to how the child process it executed came to be removed from the stack, whether it completed or was cancelled.


Reusable UI

My new (cheap) DVD recorder with hard-disk is pretty good, but you might be surprised with the bit that really impressed me! If I click the Browse button I get a list of contents on the hard-disk and pressing Enter will play the file. If I click the Copy (to DVD) button I see exactly the same UI, however, when I press the Enter button it will copy the file to a DVD instead of playing it.

You might not think this is very impressive, and maybe I'm just weird, but it is the first real-life example of reusable UI I have seen outside of computer software, in fact it is the first example I can recall seeing at all!

We've all heard of patterns in source code. The thing is, there are also patterns in GUI too. Using the example above the pattern is "Select a recording". This GUI asks the user for a specific type of information (which recording they wish to work with), and this input is used by two separate processes, let's say the "PlayRecordingProcess" and the "CopyRecordingProcess". What happens to the recording selected depends completely on what the process is doing.


Interfaces

I decided that instead of writing an ECO framework I would instead write the framework as a set of interfaces and then implement the classes however I want, my first (and only?) implementation will be an ECO one because the state diagrams save me from having to write a lot of code. I also decided that I would avoid properties in these interfaces, using methods instead. This is so that frameworks such as ECO which persist properties to a database wont attempt to persist the values returned by these interfaces.

IProcessStack

The requirements of this interface are

  • GetActiveProcess : IProcess - Gets the IProcess at the top of the stack, the active process

  • GetProcessCount : Integer - Returns the number of processes on the stack

  • GetProcess(Index : Integer) : IProcess - Returns the IProcess at the given index

  • GetProcessParameters : IProcessParameters - Gets a reference to an object that has properties which should be set by the user (explained below)

  • ActiveProcessChanged (event) - Triggered whenever the currently active process changes

  • ProcessParametersChanged (event) - Triggered whenever the ProcessParameters property changes, so that the GUI may react by displaying the currect controls to edit its properties


Initially I struggled with this interface. The class that implements it has an ExecuteProcess method. Originally this method accepted an IProcess parameter, but I didn't like that because an ECO implementation would require the Process to be an ECO object in order to have an association to it, and a remoting implementation would require this Process to be something else. If I asserted this restriction at runtime I would not get strong compile time checking.

Then I had a bit of a eureka moment! I was trying to make the interface a generic representation of the class, and this is not what an interface is for! An interface defines "how do I talk to this class" rather than "this is what this class is". We will never need to execute a process in a generic way, the interface is only there so that we can develop a generic GUI or multiple GUIs. Only the application layer itself will actually need to execute processes, this will be done by signals, processes, and ultimately once at the very start of the application when the main process is executed. So, dilemma over, on I go...

IProcess

This interface is very simple

  • GetProcessStack : IProcessStack - Used to access the process stack

  • GetProcessParameters : IProcessParameters - The ProcessStack will use GetActiveProcess.GetProcessParameters to determine what to present to the user

  • GetInstanceId : String - Used as a unique identifier for this instance. This can be used in a GUI to identify which target a signal should be sent to

  • Activate(ProcessActivationReason) - This method will be called by the ProcessStack whenever the process becomes the active process. The reasons for activation are ProcessExecuted, ActiveProcessCancelled, ActiveProcessCompleted




IProcessParameters

As promised earlier I will explain IProcessParameters. A process may pass through various internal states before reaching its final state and finishing. In some cases the process may be able to obtain all of the information it requires from automated sources (a database, config file, etc) and therefore require nothing from the user. It is more likely however that the user will be required to provide instructions or data at various points.

Whenever this situation occurs the Process will update its ProcessParameters reference and execute the ProcessStack.ProcessParametersChanged event. For example

public class AuthenticateUserParameters : IProcessParameters
{
private string userName;
public string UserName
{
get { return userName; }
set { userName = value; }
}

private string password;
public string Password
{
get { return password; }
set { password = value; }
}

Guid IProcessParameters.GetTypeUniqueIdentifier()
{
return new Guid("DEADBEEF-4F89-11D3-9A0C-0305E82C3301");
}
}

When a process needs to authenticate a user it will need the user to enter a username and password. The process would update its GetProcessParameters reference and then trigger the ProcessParametersChanged event.

At this point the UI layer, which has subscribed to this event, will get the GUID that identifies the type of the parameters, this is just a unique ID that says "I need the user to fill out AuthenticateUserParameters". The UI can then find a suitable control to present to the user and databind it to this object.

Once the user has typed in their username and password they would click a UI element that has been created to represent a signal "OK" or "Login" or something like that. This signal will then be sent to process and this will trigger either an internal state change within the process, or an exception explaining that the username or password is incorrect.

Just like with my clever DVD HDD recorder the UI has absolutely no idea why it is being used, all it knows is that it has been asked for certain information and should present this request in a format acceptible to the user. This UI may therefore be reused throughout the application. The application may ask the user to log in initially, and then at a later time the user may exceed some authority level at which point the same ProcessParameters would be used to capture the username and password of the user’s manager before being allowed to continue.

UI patterns appear all over the place. Here are some examples:

  • Prompt for a username and password

  • Ask the user to confirm or cancel an action. "Are you sure?" Yes/No

  • Select an item from a list

  • Enter a list of numbers in a grid, such as stock quantities during a stock check, or a purchase order




Summary

This one has been pretty conceptual really. What we have now is a clear separation between database, business objects, application layer, and user interface. This separation not only encourages clearly focused code, but also encourages reuse of both processes and composite UI controls.

Using this separation approach also allows us to have multiple UI's without having to implement any logic in the UI (except for code to enable, disable controls etc). In addition this clear separation would allow us to place the layers on different computers. We might have a single database, a farm of application servers, and each client connecting via either the Internet or directly through smart(ish) UI applications, or even a combination of both!

2006-12-12

Onion, part 3 (teaser)

Seeing as I haven't had enough free time to write part 3 recently I thought I'd post this little teaser. Would it surprise you to know that this very diagram generated into code and I was able to run it?

The signals on the ShowWelcomeMessage had to be hand coded, but hopefully I will be able to get some kind of custom code generation plugin to get the signals auto generated in future.

2006-12-08

Onion, part 2

Going beyond wizard interfaces

If someone had said to me "Process oriented application", in the past I would have thought to myself "Wizard-like interface". Whereas I find "Wizards" very useful in certain situations I also find that they are too time intensive for a user who knows what they want to do, how to do it, and just want to get on and do it!

The thing is, I think an application layer should be process driven. The business objects layer is there in order to represent the logical business entities of the application, and the application layer should be there to drive the logical flow of the users' interaction with those objects. So, the question is "How do we implement a process oriented framework without enforcing a wizard-like interface?" The answer I think is to use Signals.

Signals

The process may imply that there is always a specific path through an application. Although the user may influence that path by selection options along the way, it is also necessary to allow the user to perform a selection of adhoc actions at any given time, these tasks should in some way relate to the action that the user is currently performing. For example, whilst selecting a customer from a list it might be reasonable for the user to decide to view the details of one of the customers within the list before deciding they have chosen the correct one, or to edit the customer's details if they should spot a mistake.

So what exactly is a Signal and how would it work? Think of a signal as a method. Instead of this method being assigned directly to a class and therefore available to each instance of that class, a signal is a more of a "command" object which may be sent to a target object instance. The properties of this command object are akin to the parameters of a method.

You may ask yourself "If a Signal is so similar to a method, why wouldn't I just use a method instead?". Well, there are a number of benefits:

  • Either the SignalTarget is aware of the Signal type and reacts accordingly upon receiving it, or the Signal is aware of the SignalTarget and performs an operation upon it.
    • It is possible to add methods to specific instances rather than only at class level.
    • It is possible to extend the functionality of classes without having to alter their source code, which is excellent for allowing customers to have customisations written without having to resort to checking conditional compiler defines.
    • It allows you to keep the source of the target class "clean" rather than polluting it with methods for every possible feature.
  • A signal may be received by more than one type of target. Using this approach helps to introduce a kind of "business dictionary" for the application. If the same term is used throughout the application (eg "Stream to file") the user will find it much easier to understand the application.
  • Using a signal approach makes it very easy to ascertain which operations are available for a given target. This also allows us effectively to hide or disable a method given the current state of the object. A method based approach would require the use of reflection, and also a way of determining which methods of the class are their for implementation purposes and which are there to serve the user.
  • Signals may be registered from dynamically loaded assemblies, providing the ability to customise object behaviour at runtime.
  • Signal permissions may be used to introduce role based behaviour. The available actions depend entirely on the roles of the person using the application.
So, sermon over, how do they work?

Firstly I wanted to avoid instances. Signals are a way of behaving, a "thing you do" rather than a "thing you are", therefore a Signal should be an interface.

Signal

Not surprisingly I have chosen the name ISignal for this interface, and it has only a single member:
  • Execute(ISignalTarget)
This method should be invoked by the ISignalTarget to which the signal has been sent. This way the ISignalTarget always has "first refusal" and may decide whether or not to allow the signal to continue.

Signal target

The ISignalTarget interface is used to identify an object as a target for signals. This interface has two methods:
  • AcceptSignal(ISignal)
  • GetSignalPermission(ISignalFactory) : SignalPermission
AcceptSignal is executed when a signal is sent to an instance of ISignalTarget. This is where the class itself is able to react to a received signal if it is explicitly aware of that signal type. By default this method should execute signal.Execute(this) so that the signal (which may be unknown to the target) may perform any actions.

GetSignalPermission is a way of determining compatibility between the ISignal and the ISignalTarget. It returns one of the following values:
  • Supported: The ISignalTarget is aware of the signal type and will perform an action in response to receiving it, therefore the target and signal are compatible.
  • Unknown: The ISignalTarget is unaware of the signal type. Therefore the target and signal are only compatible if the signal itself indicates that this is the case.
  • Prohibited: The ISignalTarget is aware of the signal type, and regardless of what the signal indicates it will not accept the signal, therefore the signal and target and incompatible.
  • Disabled: The ISignalTarget is aware of the signal type. Although this signal is normally accepted it will not be accepted at this point in time, this could be due to the current state of the ISignalTarget (for example, "Archived"), therefore the signal and target are compatible but the signal may not be sent.

ISignalFactory

As you probably know, in .NET there is no (easy) way to implement virtual class methods. Class methods would have been very useful in this scenario because it is possible for a signal to indicate support for a target, yet it makes no sense to create an instance of every ISignal for the purpose of performing this query. In fact memory consumption is not the only reason to not take this approach:
  1. We have no way of knowing how to create an instance of the ISignal type. We could assume that we should always look for a parameterless constructor and invoke it using reflection, but this would not work for classes which require a parameter in their constructor. For example, if instances of the class belong to some kind of "object space" or a transaction which must be passed as a parameter.
  2. If we did create an instance of every type they would be used only for obtaining permissions. In a multi-user environment such as remoting or ASP .NET applications those same instances would be used by every thread, so setting their properties (aka method parameters) would be nonsense.
This is the reason I opted for an ISignalFactory interface. If .NET had the ability to handle virtual class methods then this would not have been necessary, but it's better to work around a limitation than to give up and complain about it...

The ISignalFactory interface has the following methods:
  • GetSignalPermission(ISignalTarget): SignalPermission - Identical to the behaviour of the ISignalTarget method.
  • CreateSignal(object applicationData): ISignal - Creates an instance of the ISignal to send to the target. The "applicationData" can be an object, struct, or whatever the developer decides is necessary to pass to the factory in order to construct an instance (Such as an "object space" or a transaction object of some kind).
  • Category : string - This can take any format the developer wishes and may be used to categorise signals within the UI. For example "\MainMenu\File\Edit" would indicate to the UI layer that the signal should appear in the File->Edit menu of the application's main menu.
  • DisplayName : string - This is the text to display in the UI, for example "Copy". This could either be the literal text to display or the identity of a string resource within the UI to retrieve and display.
  • UniqueId : string - This is a way of uniquely identifying the signal, it could be the namespace + classname, or better still a Guid as this will never change.

Signal catalogue

There is one final piece to the Signal jigsaw, the ISignalCatalogue. I have implemented a class for this item as it has very specific behaviour and I can't see any way in which it would be useful to modify it. Despite this I have created an ISignalCatalogue interface for the purpose of interacting with the class just in case somebody wants to write an alternative implementation.

The signal catalogue is a single point from which it is possible to get a list of compatible signal factories and signal targets. My implementation scans all loaded assemblies during its construction and looks for classes which implement ISignalFactory and creates an instance, this instance is then held in a list owned by the catalogue. It is also possible to manually register an instance or an assembly in case you need to dynamically load signals/factories from a DLL in order to provide customisation.

Once the signal factory is created it provides the following functionality:
  1. Given an ISignalTarget it will return a list of compatible ISignalFactory instances.
  2. Given an ISignalTarget and an ISignalFactory it will return a "SignalPermission".
  3. Given the UniqueId of an ISignalFactory it will return the ISignalFactory so that an ISignal may be created and sent to the target.
The signal catalogue is used as the single point of reference for obtaining compatibility because it performs the task of querying both target and factory before determining the result.

A SignalPermission is obtained from both the ISignalFatory and the ISignalTarget. The combinations listed below will provide the specified result:
  • Factory = Prohibited : Prohibited
  • Factory = Unknown : Returns the permission of the ISignalTarget.
  • Factory = Supported:
    • Target = Disabled : Disabled
    • Target = Prohibited : Prohibited
    • Target = Supported : Supported
    • Target = Unknown : Supported
  • Factory = Disabled:
    • Target = Supported : Disabled
    • Target = Unknown : Disabled
    • Target = Disabled : Disabled
    • Target = Prohibited : Prohibited

Conclusion

It is now easy to find a list of "commands" that are compatible with any given ISignalTarget by retrieving a collection of ISignalFactory. The information from this list of factories may be presented in the UI for the user to choose from, and may be logically grouped by the UI through use of the factories' "Category". In fact it would also be possible to differentiate signals available to the UI from internal signals via this category, maybe anything starting with "\UI" should be displayed.

If a "Process" within the application layer implements ISignalTarget then we now have the ability to specify within our application what exactly the user can do with that process. Rather than having "Next" and "Back" methods on our process we can implement "Next", "Back", "Okay", "Finish", "Confirm" or whatever type signals are most appropriate for the given task. In addition it is possible to display to the user a menu of possible actions, a group of actions (similar to the XP control panel) to the side of the form showing related tasks they could perform.

Most importantly though these signals provide a way of telling a physically separate layer what commands are available without that layer having to have any knowledge of the signal or the target. This makes it possible not only to have separate layers on a single machine, but also very easy to stream the information as XML to a physically separate layer on a client machine.

Summary

In this entry I have discussed a technique that can be used for many purposes, but in this case it will be used to aid user interaction with the application. In the next in my mini series I will describe the process layer in more detail, how it operates, and how to allow the user to interact with the information presented to them. Keep an eye out of Onion part #3 :-)

2006-12-02

Onion

An onion has layers...
...Shrek

In the beginning

When I first started writing applications they would typically be a single program editing a single datasource. As time went on this changed because people wanted to share data, so client/server applications appeared.

It didn't stop there though, N-Tier applications became much more common. Applications were typically split up like this:

RDBMS--DAL--Business classes--UI

Due to the fact that I use ECO for my business layer , and that ECO has the DAL built in, the illustration for me would look something like:

RDBMS--Business classes (ECO)--UI

Thinking of business problems as classes instead of tables really helps to simplify your design, so I have been very happy writing applications this way for some time now.


The application layer

In December 2005 I was tasked with the job of writing quite a complicated Compact Framework application. Although this application was going to be complicated it needed to be very simple to use, as the customer's employees were not all computer literate.

I decided to take a wizard-like approach to the application. This had the benefit of guiding the users through their daily tasks in a way that offered them options which were relevant to what they are currently doing instead of overloading them with options in a menu. However, this approach was not only beneficial to my customer's users, it taught me something I think is very valuable. Applications should have an extra layer!

The problem is that many programmers will design their UI to reflect their database layout or their business classes. For example, a developer might create a "Vehicle" form, but when presented with this form what would a user do?
  1. View its service history?
  2. View a planned service schedule?
  3. View purchase + depreciation information?
  4. Hire it out to a customer?
If the developer does not know what the user wants to do with the vehicle in question then the only sensible thing to do is to show all of this information (maybe in tabs) and allow the user to do anything they want isn't it? Can you imagine a person who is non computer literate being shown such a complicated form and being expected to to just "get on with it"? I hope your training and support departments are well staffed! :-)

The solution is to present the user with a task instead of an instance of a business class. So instead of editing information within a complicated Vehicle form the user will enter a few basic details in order to complete a "TransferVehicleOwnershipActivity". The word "Activity" could easily be replaced with something like "Task", "Action", or "Process", but the general idea is that you present the user with something to do rather than presenting them with the object on which their actions will be performed.

Code in the UI

There are some things that simply don't belong in your business classes and this sort of code often gets written into the UI layer. Code in the UI layer should only concern itself with UI related issues, such as enabling/disabling controls. An example of this would be some kind of an email client.

When you open a message in your inbox it is automatically marked as read, but where should this code be written? I'd certainly agree that you should have a method on your IncomingMessage class
void MarkAsRead()
but this method still needs to be invoked! You may find yourself reaching for the Form.Load event, and I really wouldn't blame you if you did.
void IncomingMessageForm_Load(object sender, EventArgs e)
{
this.IncomingMessage.MarkAsRead();
}
but what if in the future you needed to create a web based version of your application? Obviously you'd expect to have to write some code into your WebForms but again this should be code to handle things like enabling/disabling UI controls. To get the same functionality you'd have to copy the code from your WinForms application.

Once you have finished copying all of the logic from your WinForms application and you have a functionally identical WebForms application what next? Someone changes one of the UI applications and forgets to update the other! We should have known better, having code like this causes a bad smell...
void IncomingMessageForm_Load(object sender, EventArgs e)
{
//If you update this code make sure you
//update IncomingMessage.aspx!
this.IncomingMessage.MarkAsRead();
}
This is exactly the sort of scenario that is easily solved by having an application layer!


Onions have layers

Onions have layers. This is why I have decided to name my application framework "Onion". I'll be playing around with Onion in my spare time and blogging about my experiences and decisions, so keep an eye out for part #2!

OCL aliases

This is just a copy/paste of a reply I made in a newsgroup, but I think it is quite informative so here it is....

KEY
Square brackets denote a class [Person]
Rounded brackets denote a role (Pets)

Let's say you have a Car class and a Garage class, an individual car is regularly serviced at a specific garage so you have the following association

[Garage] (Garage) 1----0..* (ServicableCars) [Car]

Car.allInstances->select(Garage.Code = '1234')
The above OCL will fail because "Garage" is a class so the parser is expecting stuff like "allInstances". So you might think this should work

Car.allInstances->select(self.Garage.Code = '1234')
but it doesn't because "self" refers to the root object and not the object at the parsed node where it is specified. This is what aliases are for:

{aliasname} + {pipe}

Car.allInstances->select(currentCar | currentCar.Garage.Code = '1234')
This was possible in Bold too (native windows predecessor to ECO), but in Bold you could differentiate between members and classes through case sensitivity. PascalCase always meant a class whereas camelCase always meant a member, so this would work in Bold but doesn't in ECO as it is not case sensitive

Car.allInstances->select(garage.code = '1234')
Although ECO is not case sensitive you will notice that I still use the
sensitive format for clarity :-)

Roles

What is a Customer? If you are some kind of business service provider then it will be a Company, if you are a window cleaner then it will be a Person, but if you are a travel agent it could be either. What about a Supplier? You could argue the same as for a Customer, but you could also argue that a Company/Person could be both a Supplier and a Customer.

This is where Roles come in. A Supplier isn't something you are, it is something you do, you supply something; just as a Customer consumes something. I find that it is much better to enable "Things" to perform more than one action, it is rare that life is simple enough for everyone to perform only a single role in life. The typical solution to this is the Party/Role pattern.

Party role pattern

A good point to make here is that this is a pattern, not a set of classes intend for inheriting from! This means that when you have a Company (alias "Party") you should create a CompanyRole class and associate them, descending CustomerRole and SupplierRole from CompanyRole. The problem with approaching this simply as a pattern is that we still cannot make both a Person and Company share roles. Another typical solution to this is to descend both Person and Company from a Party class. I personally only like to inherit classes for behaviour purposes, not in order to inherit properties/associations - inheritence is I am rather than I have.

So, how do I implement this kind of behaviour in my own applications? I still use a Party/Role pattern, but I use a bit more aggregation:
Aggregated party role pattern

This model allows for a collection of roles which are collected by being associated with a RoleHolder. Some roles may apply to any kind of object that performs roles, whereas some such as EmployeeRole may only be applied to a Person. To implement this functionality the RoleHolder should not be used directly, instead a concrete descendant should be used for each type of class; Person owns a PersonRoleHolder and Company owns a CompanyRoleHolder. A role may permit or deny itself being applied to a certain type of RoleHolder via its virtual MayApplyTo(roleHolder) method, likewise a concrete RoleHolder may reject a certain type of Role via its virtual MayAcceptRole(role) method.

This gives us a way to ensure that certain types of Roles cannot be applied to certain types of RoleHolders, there must be mutual agreement before an association may be made. Now we need a way of retrieving the true owner of the Role, this is achieved by overriding the abstract object GetOwner() of RoleHolder. employeeRole.RoleHolder.GetOwner() would return a Person, whereas customerRole.RoleHolder.GetOwner() could return either a Company or a Person.

Finally we need a uniformed way to get to the RoleHolder of an object, seeing as Person and Company do not have nor need a common ancestor class the most appropriate way to check if it holds Roles and to retrieve its RoleHolder is to use an interface, IRoleHolder.GetRoleHolder is implemented on the business entities in order to provide this ability. A could of nice methods to add to the RoleHolder class + IRoleHolder interface are:

bool HasRole(Type roleType);
Role GetRole(Type roleType);

This way we can write code like this:

IRoleHolder roleHolder = (Person as IRoleHolder);
if (roleHolder != null && roleHolder.HasRole(typeof(EmployeeRole)))
{
EmployeeRole employeeRole := RoleHolder.GetRole(typeof(EmployeeRole)) as EmployeeRole;
employeeRole.TerminateEmployment(DateTime.Now);
}

Conclusion:

This approach allows a business entity to perform multiple roles at the same time. It also allows the same role to be applied to business entities that do not share a common ancestor class.

ECO is so fast!

I spent the day (March 2005) training someone how ECO works.

Between 9:30 and 12:30 we went through the basics, how to create a model in a package (and why). We then covered the different component types, whilst making a simple client/server app.

Between 13:45 and 17:00 we went on to
  1. Create a server application
  2. Convert the client to connect to the server instead of directly to the DB
  3. Make the client synchronise with the changes from the server
  4. Create a web service which connected to the server for persistence
  5. Create a website which connected to the server for persistence

That's a lot of work to get done in 3.5 hours at the best of times, but when you are also explaining why everything is done the way it is then this is surely a reflection of just how quick it is to develop applications using ECO.

Everything we did was adhoc. I had no idea what the trainee wanted to cover before he arrived so everything was written from scratch. I'm quite frankly surprised that we managed to cover as much as we did!