Developing a Smart Application Architecture

Developing an Application Architecture

Overview

This series of articles will walk through the creation of a simple application architecture that can be utilized in programs of nearly any size. The code posted in these articles is for illustrative purposes of concepts and is not intended for direct production use.

Part 1: Giving Your Data Some Brains

Every application is made up of a set of objects, and most of these objects will expose a set of properties that will be manipulated. The use of properties rather than directly exposing data members provides two benefits. First, there is the ability to implement implicit operations within the getter and setters. Second, it provides a common basis for tools such are reflection even when there are no implicit operations. Start with a simple set of classes that represent an invoice.

public class Invoice
   {
      Invoice()
      {
         Items = new List<InvoiceLineItem>();
      }
      public Guid InvoiceID       { get; set; }
      public string InvoiceNumber { get; set; }
      public string InvoiceDate   { get; set; }

      public ICollection<InvoiceLineItem> Items { get; private set; }
      public decimal ItemsTotalPrice  { get; set; }
      public decimal ItemsTotalTax    { <get; set; }
      public decimal ItemsTotalWeight { <get; set; }
      public decimal Shipping         { get; set; }
      public decimal ShippingRate     { get; set; }
      public decimal TaxRate          { get; set; }
      public decimal InvoiceTotal     { get; set; }
   }

   public class InvoiceLineItem
   {
      public Guid ItemID           { get; set; }
      public Guid InvoiceID        { get; set; }
      public int LineNumber        { get; set; }
      public decimal Quantity      { get; set; }
      public bool Taxable          { get; set; }
      public decimal UnitPrice     { get; set; }
      public decimal Weight        { get; set; }
      public decimal ExtendedPrice { get; set; }
   }

By using the .NET 2.0 capability of automatic properties, you have eliminated the need to manually implement the mechanics of the get and set operations. However, this savings comes at the cost of not being able to place any logic in them. As soon as you want to put in an operation on either the get or the set, you have to revert back to:

private decimal m_ExtendedPrice;
public decimal ExtendedPrice
{
   get { return m_ExtendedPrice; }
   set { m_ExtendedPrice = value; }
}

You are now up to 6 lines (even with the compressed formatting) for each property. This bloats out source code up to well over 110 lines for just the properties, instead of the current 19. To reduce this, you can write a simple class that encapsulates the syntax of a property.

public class Field<DATA_TYPE>
   {
      private DATA_TYPE m_Value;
      public  DATA_TYPE Value
      {
         get { return m_Value; }
         set { m_Value = value; }
      }
   }

For (temporary) simplicity, you will re-implement your invoicing classes using this helper with [read only ] fields rather than properties.

public class Invoice
{
   Invoice()
   {
      Items = new List<InvoiceLineItem>();
   }
   public readonly Field<Guid> InvoiceID = new Field<Guid>();
   public readonly Field<string> InvoiceNumber = new Field<string>();
   public readonly Field<string> InvoiceDate = new Field<string>();

   public ICollection<InvoiceLineItem> Items { get; private set; }
   public readonly Field<decimal> ItemsTotalPrice = new Field<decimal>();

   public readonly Field<decimal> ItemsTotalTax = new Field<decimal>();
   public readonly Field<decimal> ItemsTotalWeight = new Field<decimal>();
   public readonly Field<decimal> Shipping = new Field<decimal>();
   public readonly Field<decimal> ShippingRate = new Field<decimal>();
   public readonly Field<decimal> TaxRate = new Field<decimal>();
   public readonly Field<decimal> InvoiceTotal = new Field<decimal>();
}

public class InvoiceLineItem
{
   public readonly Field<Guid> ItemID = new Field<Guid>();
   public readonly Field<Guid> InvoiceID = new Field<Guid>();
   public readonly Field<int> LineNumber = new Field<int>();
   public readonly Field<decimal> Quantity = new Field<decimal>();
   public readonly Field<bool> Taxable = new Field<bool>();
   public readonly Field<decimal> UnitPrice = new Field<decimal>();
   public readonly Field<decimal> Weight = new Field<decimal>();
   public readonly Field<decimal> ExtendedPrice = new Field<decimal>();
}

This will require that you specifically use the Value property of a field when setting or retrieving information. At first glance, it looks like you have made the class more complicated to declare, and also more complicated to use. But, as you start to expand the capabilities of your Field class, you will begin to see significant benefits. First, add the ability to validate and assignment to a Field instance. For this example, you will create the base class, and then a specific validator to ensure that a decimal quantity is within a range.

public class Validator<DATA_TYPE>
   {
      public virtual void Validate(DATA_TYPE value) { return; }
   }

   public class RangeValidator : Validator<decimal>
   {
      public RangeValidator(decimal min, decimal max)
      {
         m_Min = min;
         m_Max = max;
      }
      public override void Validate(decimal value)
      {
         if ((value < m_Min) || (value > m_Max))
         throw new ArgumentException();
      }

      private readonly decimal m_Min;
      private readonly decimal m_Max;
}

Developing a Smart Application Architecture

Now, you enhance your Field class to know about Validator:

public class Field<DATA_TYPE>
{
   public Field()
   {
      m_Validator = new Validator<DATA_TYPE>();
   }
   public Field(Validator<DATA_TYPE> validator)
   {
      m_Validator = validator;
   }

   private DATA_TYPE m_Value;
   public DATA_TYPE Value
   {
      get { return m_Value; }
      set
      {
         m_Validator.Validate(value);
         m_Value = value;
      }
   }

   private readonly Validator<DATA_TYPE> m_Validator;
}

This enables you to add validation to any of your fields:

public class InvoiceLineItem
{
   public readonly Field<Guid> ItemID = new Field<Guid>();
   public readonly Field<Guid> InvoiceID = new Field<Guid>();
   public readonly Field<int> LineNumber = new Field<int>();
   public readonly Field<decimal> Quantity =
      new Field<decimal>(new RangeValidator(1, 100));
   public readonly Field<bool> Taxable = new Field<bool>();
   public readonly Field<decimal> UnitPrice =
      new Field<decimal>(new RangeValidator(0.01M, 9999M));
   public readonly Field<decimal> Weight =
      new Field<decimal>(new RangeValidator(0.1M, 99M));
   public readonly Field<decimal> ExtendedPrice =
      new Field<decimal>();
}

Any attempt to assign a value outside of the specified range will now automatically throw an Argument Exception. The next step is to make the class objects automatically react to modifications to the Value. Once again, this functionality is implemented by enhancing the Field class.

public class Field<DATA_TYPE>
{
   public class ValueChangedEventArgs : EventArgs
   {
      internal ValueChangedEventArgs(DATA_TYPE oldValue,
                                     DATA_TYPE newValue)
      {
         OldValue = oldValue;
         NewValue = newValue;
      }

      public readonly DATA_TYPE OldValue;
      public readonly DATA_TYPE NewValue;
   }

   public event EventHandler<ValueChangedEventArgs> ValueChanged;
   private void Fire_ValueChanged(DATA_TYPE oldValue,
                                  DATA_TYPE newValue)
   {
      if (ValueChanged != null)
          ValueChanged(this,
             new ValueChangedEventArgs(oldValue, newValue));
   }
   public DATA_TYPE Value
   {
      get { return m_Value; }
      set
      {
         if (!Equals(m_Value, value))
        {
            DATA_TYPE oldValue = m_Value;
            m_Validator.Validate(value);
            m_Value = value;
            Fire_ValueChanged(oldValue, m_Value);
        }

      }
   }

This can be used to start to include "Business Logic" into your classes.

public class Field<DATA_TYPE>
{
   public class ValueChangedEventArgs : EventArgs
   {
      internal ValueChangedEventArgs(DATA_TYPE oldValue,
                                     DATA_TYPE newValue)
      {
         OldValue = oldValue;
         NewValue = newValue;
      }

      public readonly DATA_TYPE OldValue;
      public readonly DATA_TYPE NewValue;
   }

   public event EventHandler<ValueChangedEventArgs> ValueChanged;
   private void Fire_ValueChanged(DATA_TYPE oldValue,
                                  DATA_TYPE newValue)
   {
      if (ValueChanged != null)
                 ValueChanged(this,
                 new ValueChangedEventArgs(oldValue, newValue));
   }
   public DATA_TYPE Value
   {
      get { return m_Value; }
      set
      {
         if (!Equals(m_Value, value))
        {
           DATA_TYPE oldValue = m_Value;
           m_Validator.Validate(value);
           m_Value = value;
           Fire_ValueChanged(oldValue, m_Value);
        }

      }
   }

Conclusion

At this point, you have a simple architecture that provides for both validation and event driven logic implementation. This is only the very beginning. In the next article, you will apply the same principles at the class level to create intelligent entities and collections. Future articles will include binding the data to user interfaces and intelligent persistence.

More Information

The information in these articles is based on the "Smart Architectureb" guidance published by Dynamic Concepts Development Corp. This is an open architecture and recommended set of interfaces that can be freely utilized by the developer community. Commercial Vendors are encouraged to produce implementations and tool sets that utilize this architecture.



About the Author

David Corbin

Sr Software Architect with 30 years experience developing high-performance / high-reliability software systems.

Comments

  • There are no comments yet. Be the first to comment!

Leave a Comment
  • Your email address will not be published. All fields are required.

Top White Papers and Webcasts

  • The explosion in mobile devices and applications has generated a great deal of interest in APIs. Today's businesses are under increased pressure to make it easy to build apps, supply tools to help developers work more quickly, and deploy operational analytics so they can track users, developers, application performance, and more. Apigee Edge provides comprehensive API delivery tools and both operational and business-level analytics in an integrated platform. It is available as on-premise software or through …

  • Packaged application development teams frequently operate with limited testing environments due to time and labor constraints. By virtualizing the entire application stack, packaged application development teams can deliver business results faster, at higher quality, and with lower risk.

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds