Spotlight on LOB : State Aware Objects
The Line of Business Solution Accelerator has been publically available for some time now, but it still surprises me how many LOB developers have missed out. Over the past couple of months I staffed a few MS events with a focus on the LOB Accelerator, so I had to chance to talk first hand with many developers about it. Two things became very clear to me:
1) We need to do a better job of clarifying what this solution is all about…our intentions, uses, etc.
2) We need to do a better job of breaking out the important building blocks for easier re-use geared at devs who don’t want to spend hours in the sample project
What it is and what it’s not
It’s intended for LOB developers as a starting point. A solid architecture to build on or extend from...
You can think of the LOB solution accelerator as a very thorough code sample—created to demonstrate how to solve many of the toughest issues a LOB developer would commonly build from scratch (mobile networks that come and go, offline databases that can sync with back-end servers, UIs that work on any type of device, integrated mapping, etc.). It’s full of good design decisions, re-usable objects, and effective use of Windows Mobile APIs. You can read about some of the individual highlights here, but let’s just say that if you don’t believe in re-inventing the wheel (and who does), this is a great resource. It is a fully functionally application, but it’s built around a hypothetical business scenario—A Supply Chain (Customer Service, Warehouse, and Delivery). If you are not in the supply chain business, then you would need to build some new forms and re-purpose the database for your specific needs. The core architecture is generic and easy to re-use. Business requirements are going to be different for every vertical, but the basic architecture of a LOB client is often very similar regardless of your market. No matter what your business is, a typical LOB solution is going to need most of the elements you will find in this accelerator.
Misconceptions: It’s not an off-the shelf CRM application. You are not going to download it, configure a few XML files and start doing business. It’s not a competitive forms engine or plug-and-play workflow application.
If you use the LOB Solution Accelerator as a starting point, be prepared to write some code… but not nearly as much as you would on your own!
Breaking out the Building Blocks
The strength of the LOB Solution Accelerator is that it’s very thorough and modular. The problem is that when you build a sample that has so much included in the source, it can be intimidating to understand where to start. How do I extract the basic building blocks? When I first read about it, I couldn’t wait to get into the code. Then I loaded it up in Visual Studio and uh,… choked a bit. The main solution contains seven sub projects, a plethora of source files/resources, and somewhere in the neighborhood of 5000 lines of code. Hmmmm….Where to start? The first thing you should do is review the documentation with the sample titled, “Elements”, which goes into detail about all the individual pieces of the solution. You should also frequent Rabi’s blog. He’s awesome and he blogs about LOB frequently. From there, it’s up to you..
I think there is always a trade of when you re-use large amount of code that you didn’t write. On one hand, you might save a lot of time by using code someone has already completed. On the other hand, if you have a problem along the way or need to change the way it works, you can sometimes spend more time trying to understand what the previous coder was doing than if you had written it from scratch. That’s just a developer preference and you can take either approach with the LOB Accelerator. You can just add some new forms and a new database, then build on the existing solution – or – you can start from scratch and pull out the building blocks as you need them.
I thought I would do a few posts taking the later approach…breaking out building blocks into a new solution based on some of the labs we’ve been doing. So, let’s work on building an Inspection Application.
State Aware Objects
At the foundation of everything else in the accelerator architecture is the use of state aware objects. If you decide to create your own application and then just pull building blocks out as you need them, then this is where you need to start. The LOB code makes very good use of UI, Application (Business), and Data layers. One of the fundamental goals of this architectures is to abstract the UI layer form the data layer and vice versa. A well designed application layer sits in between everything else and helps consistently broker data between the UI and data layers. Many developers struggle with the best way to do this. You need data from the database to populate the UI. The UI may modify data that needs to be written back to the database. Thus is the origin of some of the most hideous spaghetti code ever written. It doesn’t have to be that way…
Before I ask you to invest your time trying to understand how to solve this, let me highlight a few of the end results. State aware objects take the guesswork out of your UI and Data Layers. If you don’t use state aware objects, then anytime you move data between your UI and business layer, you are writing code to detect modifications in the UI and update your business objects. Similarly, anytime you refresh data from your database, you will need to make sure your UI gets updated. Traditional data bound controls are okay, but they leave a lot to be desired as your project becomes more and more complex. Wouldn’t it be great if you could just pass an object to your UI and it kept track of any changes itself? Wouldn’t it be great to just throw a bunch of objects at your database and have it only update the things that have changed? State aware objects track their own changes so you don’t have to worry about handling all this on your own. We’ll cover what this means in the data layer in a follow-up post, but state aware objects make it a breeze. It also greatly simplifies your UI code.
The LOB code takes a simple and elegant approach to building state aware objects. To borrow from the LOB sample, you need to pull in two classes: DataObject.cs and ObjectState.cs. You can typically find them in the “C:\Program Files\Windows Mobile Developer Samples\Windows Mobile Line of Business Solution Accelerator 2008\Code\HardwareDistributor\Business” directory.
ObjectState.cs simply contains a series of enums to designate different states your object might take on.
namespace InspectorPOC
{
/// <summary>
/// The state an object is in either new, unchanged, modified or marked for
/// deletion.
/// </summary>
public enum ObjectState
{
Delete,
Modify,
New,
Unchanged
}
}
ObjectState.cs is a base class wrapper you can derive from to add state support to your own classes. Note, you will need to change the namespace (e.g. – InspectorPOC) if you pull this into your own project.
using System;
namespace InspectorPOC
{
public class DataObject
{
#region Fields
protected ObjectState _objectState;
#endregion
public DataObject()
{
_objectState = ObjectState.New;
}
public ObjectState ObjectState
{
get
{
return _objectState;
}
set
{
_objectState = value;
}
}
protected T ChangeKey<T>(T currentValue, T newValue, string propertyName)
{
T result = currentValue;
CheckObjectState(propertyName); // just in case
// Property must be null currently.
if (currentValue == null)
{
if (newValue != null)
{
// Only modify the value if it is different
result = newValue;
}
}
return result;
}
protected T ChangeValue<T>(T currentValue, T newValue, string propertyName)
{
T result = currentValue;
CheckObjectState(propertyName);
// Since the type is not really known make sure currentValue is not
// null before testing for equality
if (currentValue != null)
{
// Only modify the value if it is different
if (!currentValue.Equals(newValue))
{
result = newValue;
ModifyState(ObjectState.Modify);
}
}
else
{
// if currentValue is null and newValue is not then they are
// not equal so change the value.
if (newValue != null)
{
result = newValue;
ModifyState(ObjectState.Modify);
}
}
return result;
}
protected void CheckObjectState(string propertyName)
{
if (_objectState== ObjectState.Delete)
{
throw new ApplicationException("Cannot change property, " + propertyName +
", because object is marked for deletion.");
}
}
public void Delete()
{
ModifyState(ObjectState.Delete);
}
protected void ModifyState(ObjectState newState)
{
switch (newState)
{
case ObjectState.Delete :
_objectState= ObjectState.Delete;
break;
case ObjectState.Modify :
if (_objectState== ObjectState.Unchanged)
{
_objectState= ObjectState.Modify;
}
break;
}
}
}
}
To do an inspection, we might need an Appointment and a Customer class. To make these state aware, all we need to do is derive them from our DataObject class and plug in the ChangeValue method. It looks like a lot of code, but there is nothing complex about it. The only extra thing you are doing is adding a base class and using the ChangeValue method. Our new classes would look something like this:
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
namespace LOBSample
{
public class Customer : DataObject
{
private string _address;
private string _city;
private Guid _customerId;
private string _name;
private string _phone;
private string _state;
private string _zipCode;
public Customer() : base()
{
}
public Customer(Guid customerId, string name, string address,
string city, string state, string zipCode,
string phone) : this()
{
_customerId = customerId;
_name = name;
_address = address;
_city = city;
_state = state;
_zipCode = zipCode;
_phone = phone;
ObjectState = ObjectState.Unchanged;
}
public string Address
{
get
{
return _address;
}
set
{
_address = ChangeValue<string>(_address, value, "Address");
}
}
public string City
{
get
{
return _city;
}
set
{
_city = ChangeValue<string>(_city, value, "City");
}
}
public Guid CustomerId
{
get
{
return _customerId;
}
}
public string Name
{
get
{
return _name;
}
set
{
_name = ChangeValue<string>(_name, value, "Name");
}
}
public string Phone
{
get
{
return _phone;
}
set
{
_phone = ChangeValue<string>(_phone, value, "Phone");
}
}
public string State
{
get
{
return _state;
}
set
{
_state = ChangeValue<string>(_state, value, "State");
}
}
public string ZipCode
{
get
{
return _zipCode;
}
set
{
_zipCode = ChangeValue<string>(_zipCode, value, "ZipCode");
}
}
}
}
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
namespace LOBSample
{
public class Appointment : DataObject
{
private string _address;
private Guid _appointmentId;
private string _city;
private string _contactName;
private string _contactPhone;
private Customer _customer;
private Guid _customerId;
private Guid _employeeId;
private DateTime _endWindow;
private Nullable<DateTime> _finishedInspection;
private string _notes;
private Nullable<DateTime> _startInspection;
private DateTime _startWindow;
private string _state;
private string _zipCode;
public Appointment() : base()
{
}
public Appointment(Guid apptId, Guid customerId, Guid employeeId,
string contactName, string contactPhone,
DateTime startWindow, DateTime endWindow,
string address, string city, string state,
string zipCode, Nullable<DateTime> startInspection,
Nullable<DateTime> finishedInspection, string notes)
: this()
{
_appointmentId = apptId;
_customerId = customerId;
_employeeId = employeeId;
_contactName = contactName;
_contactPhone = contactPhone;
_startWindow = startWindow;
_endWindow = endWindow;
_address = address;
_city = city;
_state = state;
_zipCode = zipCode;
_startInspection = startInspection;
_finishedInspection = finishedInspection;
_notes = notes;
ObjectState = ObjectState.Unchanged;
}
public string Address
{
get
{
return _address;
}
set
{
_address = ChangeValue<string>(_address, value, "Address");
}
}
public Guid AppointmentId
{
get
{
return _appointmentId;
}
set
{
_appointmentId = ChangeValue<Guid>(_appointmentId, value, "AppointmentId");
}
}
public string City
{
get
{
return _city;
}
set
{
_city = ChangeValue<string>(_city, value, "City");
}
}
public string ContactName
{
get
{
return _contactName;
}
set
{
_contactName = ChangeValue<string>(_contactName, value, "ContactName");
}
}
public string ContactPhone
{
get
{
return _contactPhone;
}
set
{
_contactPhone = ChangeValue<string>(_contactPhone, value, "ContactPhone");
}
}
public Customer Customer
{
get
{
if (_customer == null)
{
if (_customerId != null && _customerId != Guid.Empty)
{
//_customer = Services.GetCustomer(_customerId);
// TODO - Plug In Data Layer
}
}
return _customer;
}
set
{
if (value != null && _customerId != null &&
value.CustomerId != _customerId)
{
// this will validate that you can modify the object and
// set the object state correctly
CustomerId = value.CustomerId;
_customer = value;
}
}
}
public Guid CustomerId
{
get
{
return _customerId;
}
set
{
_customerId = ChangeValue<Guid>(_customerId, value, "CustomerId");
}
}
public Guid EmployeeId
{
get
{
return _employeeId;
}
set
{
_employeeId = ChangeValue<Guid>(_employeeId, value, "EmployeeId");
}
}
public DateTime EndWindow
{
get
{
return _endWindow;
}
set
{
_endWindow = ChangeValue<DateTime>(_endWindow, value, "EndWindow");
}
}
public Nullable<DateTime> FinishedInspection
{
get
{
return _finishedInspection;
}
}
public string Notes
{
get
{
return _notes;
}
set
{
_notes = ChangeValue<string>(_notes, value, "Notes");
}
}
public Nullable<DateTime> StartInspection
{
get
{
return _startInspection;
}
}
public DateTime StartWindow
{
get
{
return _startWindow;
}
set
{
_startWindow = ChangeValue<DateTime>(_startWindow, value, "StartWindow");
}
}
public string State
{
get
{
return _state;
}
set
{
_state = ChangeValue<string>(_state, value, "State");
}
}
public string ZipCode
{
get
{
return _zipCode;
}
set
{
_zipCode = ChangeValue<string>(_zipCode, value, "ZipCode");
}
}
public void Done()
{
if (_startInspection == null)
{
throw new ApplicationException("Inspection has not been started on this property.");
}
if (_finishedInspection != null)
{
throw new ApplicationException("Inspection has already been completed on this property.");
}
_finishedInspection = DateTime.Now;
}
public void Start()
{
if (_startInspection != null)
{
throw new ApplicationException("Inspection has already been started on this property.");
}
_startInspection = DateTime.Now;
}
}
}
So now we have simple state aware objects and that opens the door to do a lot of cool things. Next post, we’ll look at wiring them up to our UI using simple Lists and plugging in the data layer – again, borrowing some tricks from the LOB Solution Accelerator.
Cheers,
Reed
Comments
- Anonymous
September 22, 2008
PingBack from http://blog.a-foton.ru/2008/09/spotlight-on-lob-state-aware-objects/