Monday, December 25, 2006

My Default SOA Architecture using WCF

Continued from http://vikasnetdev.blogspot.com/2006/07/soa-friendly-architecture-version-of.htmlWith the advent of Net3.0, I am upgrading the implementation of my default SOA friendly architecture to use WCF as a medium to connect my business layer with database layer


Note: The author of this blog does not believe that you should use WebSevice/WCF in standalone application when none required. But we live in a world where we don't make rules. If you database is protected by another firewall, you may find this architecture handy. It is more important to have logically separation in architecture that will scale well if application is deployed in a distributed manner.


Here is SOA friendly Architecture


Here is deployment view


Major Components

1. UI Components/Views:
Provide User Interface

Controllers
Get the Business Objects from Gateway and provides to UI

Gateway/Factory
It is factory layer that provides the business object. It knows how to create or get the object across the network boundaries. First looks in repository if none, gets the object across the wire.Our example is going to exploit the capabilities of Web Service. It will convert the proxy object into real domain object using Mapper utility.

References:
http://www.martinfowler.com/eaaCatalog/gateway.html
http://www.martinfowler.com/eaaCatalog/repository.html
http://www.martinfowler.com/eaaCatalog/dataTransferObject.html


Business Object Manager
It populates the business object using data object. This is where one can aggregate the business objects before transferred across the network.


Business Object/Entity Object
Contains the Attributes and operations related to domain entity

Process Object/Workflow Object
Business objects collaborate together to perform some useful operations to users.


Data Object
Data Objects retrieve the data from Database. Will contain all the CRUD operations.

Here is Code.

Gateway


namespace Gateway
{
public class Contact
{
static Contact()
{
//
}
private static List contactListCache = null;
private static Object syncRoot = new Object();
public static void ClearCache()
{
lock (syncRoot)
{
contactListCache = null;
}
}
public List GetContact()
{
return ContactList;
}

public List ContactList
{
get
{
if (contactListCache == null)
{
lock (syncRoot)
{
contactListCache = new List();
ContractServiceWS.ContractServiceClient contractService = new ContractServiceWS.ContractServiceClient("WSHttpBinding_IContractService");
ContractServiceWS.Contact[] proxyContactList = contractService.GetContract();
for (int i = 0; i <= proxyContactList.Length - 1; i++) { BusinessObject.Contact contact = new BusinessObject.Contact(proxyContactList[i].UserName, proxyContactList[i].Name, proxyContactList[i].PhoneNumber, proxyContactList[i].Age); contactListCache.Add(contact); } contactListCache = (List)Converter.MapProperties_Fields((System.Collections.IList)proxyContactList, typeof(List), typeof(BusinessObject.Contact));
}


}
return (contactListCache);
}
}
}
}


namespace Gateway
{
public abstract class Mapper
{

public static object MapProperties_Fields(object SourceObj, Type DestinationType)
{
if (SourceObj == null)
{
return null;
}
Type sourceType = SourceObj.GetType();
object destinationObj = Activator.CreateInstance(DestinationType);

foreach (PropertyInfo sourceProperty in sourceType.GetProperties())
{


if (sourceProperty.Name.Equals("ExtensionData") == false)
{
MemberInfo destinationMember = DestinationType.GetMember(sourceProperty.Name)[0];

if (destinationMember.MemberType == MemberTypes.Property)
{
PropertyInfo destinationProperty = ((PropertyInfo)(destinationMember));
if ((destinationProperty.CanWrite == true))
{
if (destinationProperty.PropertyType.Equals(sourceProperty.PropertyType))
{
destinationProperty.SetValue(destinationObj, sourceProperty.GetValue(SourceObj, null), null);
}
}
}
}
}
return destinationObj;
}



public static IList MapProperties_Fields(IList SourceList, Type DestinationListType, Type DestinationElementType)
{
if (SourceList == null)
{
return null;
}
IList destinationList = ((IList)(Activator.CreateInstance(DestinationListType)));
foreach (object sourceObj in SourceList)
{
destinationList.Add(MapProperties_Fields(sourceObj, DestinationElementType));
}
return destinationList;
}
}
}





WCF Service

[ServiceContract()]
public interface IContractService
{
[OperationContract]
IList GetContract();
}

public class MyService : IContractService
{
public IList GetContract()
{
BusinessObjectManager.Contact contactManager = new BusinessObjectManager.Contact();
return contactManager.GetContacts();
}
}



Business Object Manager

namespace BusinessObjectManager
{
public class Contact
{
public List GetContacts()
{
DataTable dataTable;
List list = new List();
dataTable = DataObject.Contact.GetContact();
try
{
foreach (DataRow dataRow in dataTable.Rows)
{
BusinessObject.Contact contact = new BusinessObject.Contact(dataRow[0].ToString(), dataRow[1].ToString(), dataRow[2].ToString(), Int32.Parse(dataRow[3].ToString()));
list.Add(contact);
}
}
catch (Exception ex)
{
throw ex;
}
finally
{
dataTable = null;
}
return(list);
}
}
}



Business Object


namespace BusinessObject
{
[DataContract]
public class Contact
{
private String _userName;
private String _name;
private String _phoneNumber;
private int _age;
public Contact()
{
}
public Contact(string userName, string name,String phoneNumber, int age)
{
_userName = userName;
_name = name;
_phoneNumber = phoneNumber;
_age = age;
}
[DataMember]
public String UserName
{
get
{
return _userName;
}
set
{
_userName = value;
}
}
[DataMember]
public String Name
{
get
{
return _name;
}
set
{
_name = value;
}
}
[DataMember]
public String PhoneNumber
{
get
{
return _phoneNumber;
}
set
{
_phoneNumber = value;
}
}
[DataMember]
public int Age
{
get
{
return _age;
}
set
{
_age = value;
}
}
}
}



Data Object
namespace DataObject
{
public class Contact
{
private const String ContactTableName = "Contact";

private class Fields
{
public const string UserName = "ContactUser_Name";
public const string Name = "Contact_Name";
public const string PhoneNumber = "Contact_Phone_Number";
public const string Age = "Contact_Age";
}


public static DataTable GetContact()
{
System.Text.StringBuilder sql = new System.Text.StringBuilder();
System.Data.DataTable dataTable;

//Build the SQL

AppendBaseSelectColumns(sql);

sql.Append(" FROM");
sql.Append( " " + ContactTableName);

try
{
dataTable = GetDataTable(sql.ToString());
}
catch (Exception ex)
{
//Log the error
throw ex;
}


return(dataTable);


}

private static String AppendBaseSelectColumns(System.Text.StringBuilder sql)
{

sql.Append("SELECT");
sql.Append(" " + Fields.UserName + ",");
sql.Append(" " + Fields.Name + ",");
sql.Append(" " + Fields.PhoneNumber + ",");
sql.Append(" " + Fields.Age );
return sql.ToString();

}

private static DataTable GetDataTable(string sql)
{
//Real life one will connect to Database and return the
DataTable dataTable = new DataTable();
dataTable.Columns.Add(new DataColumn("Contact_User_Name", typeof(String)));
dataTable.Columns.Add(new DataColumn("Contact_Name", typeof(String)));
dataTable.Columns.Add(new DataColumn("Contact_Phone_Number", typeof(String)));
dataTable.Columns.Add(new DataColumn("Contact_Age", typeof(System.Int32)));
dataTable.Rows.Add(new Object[]{ "VK", "Vikas","222-222-2222",int.MaxValue});
dataTable.Rows.Add(new Object[] { "SV", "Survic", "222-222-9999", int.MaxValue });
return dataTable;

}

}
}


Presenter/Controller
namespace Presenter
{
public class ContactPresenter
{
public ContactPresenter(View.IContactView view)
{
_view = view;
_view.LookUp += new EventHandler(_view_LookUp);
_view.Persist += new EventHandler(_view_Persist);
}

private View.IContactView _view;
private BusinessObject.Contact _currentContact;


public View.IContactView View
{
get { return _view; }
}
public BusinessObject.Contact CurrentContact
{
get { return _currentContact; }
}


public void SaveContact()
{
if (_currentContact != null)
{
_currentContact.Name = _view.ContactName;
_currentContact.PhoneNumber = _view.ContactPhoneNumber;
_currentContact.Age = _view.ContactAge;
}
}

public void LookUpContactByUserName(string userName)
{
Gateway.Contact contactGateway = new Gateway.Contact();
List contactList = contactGateway.GetContact();

foreach (BusinessObject.Contact contact in contactList)
{
if (contact.UserName.ToUpper().Trim().Equals(userName.ToUpper().Trim()))
{
_currentContact = contact;
break;
}
}

_view.ContactUserName = _currentContact.UserName;
_view.ContactName = _currentContact.Name;
_view.ContactPhoneNumber = _currentContact.PhoneNumber;
_view.ContactAge = _currentContact.Age;
}

private void _view_LookUp(object sender, EventArgs e)
{
this.LookUpContactByUserName(_view.UserNameToLookUp);
}

private void _view_Persist(object sender, EventArgs e)
{
this.SaveContact();
}
}
}


Saturday, December 23, 2006

Pitfalls of Development Driven Testing -- TDD

There comes a time during battles when elite commandos have to throw away their best gadgets which may include sophisticated gears, body armors, tools and weapons and resort to hand-to-hand combat. During one of those moments, I wrote the following blog post
Confessions of a failed extreme programmer

After the fog of war was over, I was told by my colleagues about the bugs in my components in certain scenarios. These bugs would have been never there, if only I had written my unit tests. So I started writing my tests using the framework which I prepared.
To me surprise, my test was succeeding while in real condition, it should have failed. After serious debugging, I found a serious bug in my framework and as a result, 40% of my automated tests were flawed.


Well, if you survived your hand-to-hand combat, you have to oil your gear.

Moral of the story: It does not hurt to use a knife during hand-to-hand combat. Not using it may prove to be fatal. Automated Unit tests are knives to Developers locked in a mortal combat with very aggressive deadlines.