Goals and Objectives :

Objectives Service Oriented Architecture(SOA),.NET, J2EE,TDD,XP,RUP,WATERFALL Methodology,Arduino,3D Printer,Cloud Computing,AJAX

Friday, July 21, 2006

SOA Friendly Architecture version of Jeffrey Palermo's Architecture -- Architecture

Please Note:
I am retiring this implmentation with a new implmentation using WCF, though the
Architecture remains same.
http://vikasnetdev.blogspot.com/2006/12/my-default-soa-architecture-using-wcf.html


This post is inspired by Jeffrey Palermo's post on Application's Architecture


Though it is quite superior as compared to .Net Petshop Architecture, I really doubt that this architecture will scale for distributed computed scenario like petshop architecture does not. Jeffrey may have answer for this. So I came up this SOA friendly architecture which is being used at one of my client's place. I am also going include some feature from Jeffrey'a Architecture like Presentation layer (MVP vs MVC) in my architecture

Note: The author of this blog does not believe that you should use WebSevice 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();
ContactWS.ContactService ws = new ContactWS.ContactService();
ContactWS.Contact[] proxyContactList = ws.GetContacts();
contactListCache = (List)Mapper.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())
{
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;
}



}
}




WebService
namespace BusinessObjectWebservice
{
///
/// Summary description for Service1
///

[WebService(Namespace = "http://tempuri.org/")]
[WebServiceBinding(ConformsTo = WsiProfiles.BasicProfile1_1)]
[ToolboxItem(false)]
public class Service1 : System.Web.Services.WebService
{

[WebMethod]
public List GetContacts()
{
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
{
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;
}


public String UserName
{
get
{
return _userName;
}
set
{
_userName = value;
}
}

public String Name
{
get
{
return _name;
}
set
{
_name = value;
}
}

public String PhoneNumber
{
get
{
return _phoneNumber;
}
set
{
_phoneNumber = value;
}
}


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();
}
}
}

6 comments:

survic said...

I have some comment here:

http://survic.blogspot.com/2006/07/business-object-domain-object-and.html

I know I am picky on this one; but I got bitten by this many times, in both java world and .net world. Let's use MS's terminology.

Vikas said...

Thanks for great input.

Here are answers
SOA Friendly Architecture version of Jeffrey Palermo's Architecture -- Part 2 -- Architecture

Anonymous said...

Beauty in simplicity
Thanks for a clear and simple articulation of a troubling paradigm.
However, don't like the 'tight' linkage to the data layer in your sample business object manager - I feel that the middle tier should be completely agnostic of the data tier, and hence dislike your references to DataRow and DataTable in that tier... (Maybe that's exacerbated by my possible mis-interpretation of your naming convention...)
Perhaps you should abstract the data tier out from the business entity tier a little further?

A great start, and thanks for your publication - keep up the Good Work!

Vikas said...

Hi anonymous,
Thanks for the feedback. Business Manager should not be tightly coupled to data layer, was a dominant concern couple of years back. It was a recommended practice by Microsoft as Microsoft kept changing Date Access Technology starting from DAO,RDO,ADO and finally to ADO.Net. Recently, I have not seen much architecture really concerned about changes in Data Access APIs and then trying isolating all data access objects in one layer or adding a abstraction over it. At the same I will not bet that Microsoft will not change the Data Access Technologies in future. If one is really concerned about it (with good reasons), it is a very good idea to abstract the data access objects. Anyway, Excellent suggestion.

Another point that I would like to emphasize is that the caching mechanism demonstrated in this example may not be suitable for very large objects.

Anonymous said...

Hi Vikas

I am working on ASP.NET 2.0.
While designing Business Classes/Entities I got confused about the architecture.
In My design of class a typical employee class will look like below
Actually this class is nothing but an Entity but in this class itself I thought of adding methods
which are related to this entity but they will not use any member variable
so I can keep them as static methods as shown below e.g.

getAllEmployees()
But my confusion is about memory.
If I keep these functions as non-static function then will each object have memory allocated to the function
of only one will be there and logically connected.?
If I keep these functions as static function then when the memory is released for these functions?
Or is there any better way to do this.
This design I got it from the timetracker project which is available as a guideline.
It would be nice if you help me
Thank you
Bharat
Sample class

Class Employee:
{
private int _EmployeeId;
private string _Name;
public virtual int EmployeeId
{
getp {
p return this._EmployeeId;
}
set
{
this._EmployeeId = value;
}
}
public virtual string Name
{
get
{
return this._Name;
}
set
{
this._Name = value;
}
}

public bool Save(){//Code goes here}
public static List GetAllEmployees()
{ //Code goes here } }

Vikas said...

Survic wrote

Seriously:

1. Will you use “dataportal”?

2. After generics, how many collections classes left?


Vikas Wrote


Will you use dataportal

Do you mean Object Manager?

Are you talking about Architecture/Design Pattern or Rocky's implementation?

For Enterprise Ready Application (Data Layer could be deployed on Database Server), I will definitely use Object Manager/Entity Aggregator/Fascade/Command Pattern (in combination).For a project less than 3 months, it may not be required. But if I have framework ready for Enterprise Ready Application, then why not use some part of it for short term projects also. That is my thought process.


After generics, how many collections classes left?

Finished proof of concept and feasibility study which involved the following
1. Fixing the framework to handle generic collection (converting webservice DTO to real generic collections.
2. Moved the Business logic code from selected complex collection to business object. I have to convince my colleagues that in "modern" OO model, there is no top level collections, only classes, and 1-m, m-1, m-m class relationships. Don’t remember where I read this? :)
3, I also discovered the ugly side of Generics. I also learnt practically that it is not polymorphic. I had some situations where, our framework was calling some methods on collection through reflection. This was a critical point. If I had not solved this problem, we would have left with 20% of Strong Typed Collections that we have. We came with a fix.

4. We ran couple of scenarios. I knocked out couple of strong typed collections to test the framework.
Now after all this, we have to knock out all strong typed collection from our application. My estimate is that we would be left with 0 strong typed collections after that.



Survic wrote

---Object Manager?
---Yes, I checked your diagram again. CSLA's "Data portal" corresponds to the whole objectManger-WebService-GateWay mechanism.

I wonder why you do not simply adopt CSLA’s datdaportal? I am really curious.

Is it the “4-CRUD or 1-Execute” limitation (and all those "criteria, exists" workarounds)? To me, the “4-CRUD or 1-Execute” limitation is very serious, because it tends to twist the domain model.

I like your design: you use code generation to remove the problem of the “4-CRUD or 1-Execute” limitation. “Emit” is also good; just timing, maintenance, flexibility tradeoffs -- also, we need to do the code generation anyway (I am too addict to aop; code generation is more flexible and down-to-earch).

The simplest way is to remove "dataportal" and ignore the limitations/workarounds; because most of time, we use either web or two-tier winform, no "app servers"/web services. We can always add that mechanism when we really need it. However, as you said, if you have it, why not use it, to make all things consistent.


---We would be left with 0 strong typed collections after that.
---wow!!!


Vikas wrote

--No “4-CRUD or 1-Execute” limitation, no strong typed collections – congratulations, your design is one of the very few best!!!

Well.It is a decent architecture from 18000 feet height.:)
I have to get off high horse and admit It is a business intelligence tool. Not much CRUD right now. It is in development. Once it goes in Maintainenace mode, we have to design and create CRUD.

Sure we can discuss Dataportal. Give me some time on this.