Developing a Smart Architecture, Part 2

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.

Review

In Part 1 of this series, you did a detailed walkthrough of creating intelligent data fields that reduced the reprtitive coding effort while at the same time providing both (simple) validation and change notification.

Part 2: Building Intelligent Entities

Now that you have the ability to create intelligent fields, you need to expand this to creating useful intelligent classes. To begin this process, you will need to re-work a few of your existing classes. This is primarily being done so that you can treat specialized instances of the generic Field class in a unified way. You will create an interface and then update the class to implement this interface.

public interface IField
{
   event EventHandler<ValueChangedEventArgs<Object>> Changed;
}


public class Field<DATA_TYPE> : IField
{
   // Basic implementation remains the same with the following
   // additions/changes...
   private void Fire_ValueChanged(DATA_TYPE oldValue,
                                  DATA_TYPE newValue)
   {
      if (ValueChanged != null)
         ValueChanged(this, new ValueChangedEventArgs<DATA_TYPE>
                      (oldValue, newValue));
      if (Changed != null)
         Changed(this, new ValueChangedEventArgs<Object>
                 (oldValue, newValue));
   }
   public event EventHandler<ValueChangedEventArgs<Object>> Changed;
}

Because every instance of a Field<T> can now be treaded as an IField, you can create your Entity class. The exposed functionallity is as follows:

public class Entity : IField
{
   public void RegisterField(string name, IField field) {...}
   public Field<DATA_TYPE>GetField<DATA_TYPE>(string name) {}

   public event EventHandler<ValueChangedEventArgs<object>> Changed;
   public event EventHandler<FieldValueChangedEventArgs> FieldChanged;
}

You now have a basic set of abilities, including:

  • Registering Fields with the Entity and assigning them a Name
  • Retrieving a given Field by Name
  • Propogating events from the Fields to the Entity itself

It is important to note that an Entity is itself an IField. This allows for the creations of entities, whereas the fields themselves are entities.

You will also need the ability to create collections of these entities. For right now, you will only implement a few critical methods and properties.

public class EntityCollection<KEY_TYPE, ENTITY_TYPE> : Entity
   where ENTITY_TYPE : Entity
{
   public void Add(KEY_TYPE key, ENTITY_TYPE item) {}
   public bool Remove(KEY_TYPE key) {}
   public event EventHandler<CollectionChangedEventArgs>
      CollectionChanged;
   public void Clear()
   public int Count { get { } }
   public ENTITY_TYPE this[KEY_TYPE key] {}

}

It is important to note that an EntityCollection is itself an Entity (and therefor a IField). This allows any EntityCollection to be a Field member of an Entity.

Both the Entity and EntityCollection classes at this point are still very simplistic, but are already ready to create some powerful examples.

Enhancing the Invoice Sample Code

First, you must update your Invoice and InvoiceLineItem classes, and enhance them to make use of these entity base classes. This will require the Registration of the various Field objects in the constructor.

public class Invoice : Entity
{
   public Invoice()
   {
      RegisterField("InvoiceDate",      InvoiceDate);
      RegisterField("InvoiceID",        InvoiceID);
      RegisterField("InvoiceNumber",    InvoiceNumber);
      RegisterField("Items",            Items);
      RegisterField("ItemsTotalPrice",  ItemsTotalPrice);
      RegisterField("ItemsTotalTax",    ItemsTotalTax);
      RegisterField("ItemsTotalWeight", ItemsTotalWeight);
      RegisterField("Shipping",         Shipping);
      RegisterField("ShippingRate",     ShippingRate);
      RegisterField("TaxRate",          TaxRate);
      RegisterField("InvoiceTotal",     InvoiceTotal);
   }

   // Remainder of the Class is identical to previous version.....
}

public class InvoiceLineItem : Entity
{
   public Invoice()
   {
      RegisterField("InvoiceID",     InvoiceID);
      RegisterField("ItemID",        ItemID);
      RegisterField("LineNumber",    LineNumber);
      RegisterField("Quantity",      Quantity);
      RegisterField("Taxable",       Taxable);
      RegisterField("UnitPrice",     UnitPrice);
      RegisterField("Weight",        Weight);
      RegisterField("ExtendedPrice", ExtendedPrice);
   }

   // Remainder of the Class is identical to previous version.....
}

Now, you can add a series of events that will ensure that all fields are properly updated when their dependancy fields change. Because these "rules" are inherent in the nature of your Invoice, you will implement them internally to the class and install them during object construction.

public class Invoice : Entity
{
   // Called from Constructor after registration
   private void InstallRules()
   {
      Items.CollectionChanged       += Items_CollectionChanged;
      ItemsTotalPrice.ValueChanged  += Recalculate_Tax;
      ItemsTotalTax.ValueChanged    += Recalculate_Total;
      ItemsTotalWeight.ValueChanged += Recalculate_Shipping;
      ItemsTotalTax.ValueChanged    += Recalculate_Total;
      ShippingRate.ValueChanged     += Recalculate_Shipping;
      TaxRate.ValueChanged          += Recalculate_Tax;
   }

   private void Items_CollectionChanged(object sender,
      CollectionChangedEventArgs e)
   {
      InvoiceLineItem lineItem =   e.Item as InvoiceLineItem;
      switch (e.ChangeType)
      {
         case CollectionChangedEventArgs.CollectionChangeTypes.Added:
            ItemsTotalPrice.Value  += lineItem.ExtendedPrice.Value;
            ItemsTotalWeight.Value += lineItem.Weight.Value;
            break;
         case CollectionChangedEventArgs.CollectionChangeTypes.Removed:
            ItemsTotalPrice.Value  -= lineItem.ExtendedPrice.Value;
            ItemsTotalWeight.Value -= lineItem.Weight.Value;
            break;
         case CollectionChangedEventArgs.CollectionChangeTypes.Cleared:
            ItemsTotalPrice.Value  = 0.0M;
            ItemsTotalWeight.Value = 0.0M;
            break;
      }
   }

   private void Recalculate_Tax(object sender,
      ValueChangedEventArgs<decimal> e)
   {
      ItemsTotalTax.Value = ItemsTotalPrice.Value * TaxRate.Value;
   }

   private void Recalculate_Shipping(object sender,
      ValueChangedEventArgs<decimal> e)
   {
      Shipping.Value =   ItemsTotalWeight.Value * ShippingRate.Value;
   }

   private void Recalculate_Total(object sender,
      ValueChangedEventArgs<decimal> e)
   {
      InvoiceTotal.Value = ItemsTotalPrice.Value
                         + ItemsTotalTax.Value
                         + Shipping.Value;
   }

   public void AddItem(InvoiceLineItem lineItem)
   {
      lineItem.LineNumber.Value = Items.Count + 1;
      Items.Add(lineItem.LineNumber.Value, lineItem);
      lineItem.ExtendedPrice.ValueChanged +=
         ExtendedPrice_ValueChanged;
   }

   private void ExtendedPrice_ValueChanged(object sender,
      ValueChangedEventArgs<decimal> e)
   {
      ItemsTotalPrice.Value += (e.NewValue - e.OldValue);
   }
}

Developing a Smart Architecture, Part 2

And there you have it. A very simple implementation that embodies all of the logical operations related to calculating the appropriate iformation related to a simple invoice. This code is very maintainable and easily testable.

To illustrate its usage, consider the following sample program:

private static void Main()
{
   Invoice invoice = new Invoice();
   invoice.InvoiceNumber.Value = "1000";
   invoice.TaxRate.Value = 0.07M;
   invoice.ShippingRate.Value = 1.23M;

   InvoiceLineItem invoiceLineItem = new InvoiceLineItem();
   invoiceLineItem.Quantity.Value = 10;
   invoiceLineItem.UnitPrice.Value = 19.95M;
   invoiceLineItem.Weight.Value = 0.75M;

   invoice.AddItem(invoiceLineItem);

   Console.WriteLine("Invoice Items Total: {0}",
                     invoice.ItemsTotalPrice.Value);
   Console.WriteLine("Invoice TAX: {0}",
                     invoice.ItemsTotalTax.Value);
   Console.WriteLine("Invoice Weight: {0}",
                     invoice.ItemsTotalWeight.Value);
   Console.WriteLine("Invoice Shipping: {0}",
                     invoice.Shipping.Value);
   Console.WriteLine("Invoice GRAND TOTAL: {0}",
                     invoice.InvoiceTotal.Value);
}

Advanced Topics

Although too advanced for details at this point in your design, it is important to realize that there are many alternatives to explicitly coding the fields and explicitly registering them. Full functionallity implementations may use reflection to automatically register applicable fields, and may use Attributes for providing the requirement information. Once you have completed the simple architecture, I will begin to discuss these alternatives.

Conclusion

Even at this early stage, you are able to create "applications" that involve a minimum amount of coding. Even better, the enhancements in the next few installments will not require any (significant) changes to the Invoice sample itself. This is the real "power" of this style of architecture and implementation; you can add power to your library classes (Field, Entity, EntityCollection) as needed, and that capability will become available immediately in both future and past applications.

More Information

The information in these articles is based on the "Smart Architecture" 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.

Downloads

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

  • Live Event Date: November 20, 2014 @ 2:00 p.m. ET / 11:00 a.m. PT Are you wanting to target two or more platforms such as iOS, Android, and/or Windows? You are not alone. 90% of enterprises today are targeting two or more platforms. Attend this eSeminar to discover how mobile app developers can rely on one IDE to create applications across platforms and approaches (web, native, and/or hybrid), saving time, money, and effort and introducing apps to market faster. You'll learn the trade-offs for gaining long …

  • Live Event Date: October 29, 2014 @ 11:00 a.m. ET / 8:00 a.m. PT Are you interested in building a cognitive application using the power of IBM Watson? Need a platform that provides speed and ease for rapidly deploying this application? Join Chris Madison, Watson Solution Architect, as he walks through the process of building a Watson powered application on IBM Bluemix. Chris will talk about the new Watson Services just released on IBM bluemix, but more importantly he will do a step by step cognitive …

Most Popular Programming Stories

More for Developers

Latest Developer Headlines

RSS Feeds