I've been working exclusively alone on a project for a while and have as much liberty as any developer could hope for. I'm a blessed man. Nonetheless, I have tried to kind of "pretend" like I am working in a team environment that has regimented deadlines. This is mostly due to integrity and partly due to prevention for laziness...how easy is it to constantly be researching solutions without implementing them?
There was a necessary period of growth in skill sets that had to occur and while I certainly haven't 'arrived' at some point the rubber has to hit the road and instead of striking out to google for an answer first, there must be some confidence on the toolkit that has been developed. A car has to at least be moving before turning the wheels even in the right direction will make any difference.
So design decisions whether regarding overall architectural structure or about simple object creation aren't strictly design decisions, but are really business decisions. Maintainability is almost at the top of my list for development ; partly because it's just fun to meet the challenge, but also because I work alone and I could be buried with feature requests if I am not careful to write robust, scalable code. Still, this goal has a way of stalling design decisions if you're not careful.
Realigning my thinking into Test Driven Development has largely stumped this phenomenon. Most importantly because TDD isn't about the tests, but meeting requirements in a iterative, thoughtful way.
An example of this came up for deciding how I am going to query my DB using NHibernate as my OR/M. Now, I am using an IRepository for controlling access to the NHibernate implementation of data access and this brings along a whole sweet suite (excuse me) of tools that are a snap to use. I need to create Query Objects to get the data I need in my domain, but do I really want to wrap so much that NHibernate already does for you? For example, do I need to wrap every ICriteria implementation with my own custom implementation to avoid the dependency on NHIbernate's API?How far does one take isolating a domain from specific implementation of these tools?
There seem to be at least three options:
- Have more specific IRepository<T> implementations that expose methods like "GetCustomerByNameAndGender(string name, Gender gender)", perhaps in CustomerRepository:IRepository<Customer>
- Create an Object Query language as I go that accepts calls maybe with fluid interface calls like .Where(PropertyName name) and so on
- Respect the Single Reponsibility Principle and use NHibernate's API within the Domain, but only within query objects.
The first option really just moves the complexity into different area. Maintaining a million little methods really misses out on the joys of Object Oriented thinking where you can treat tasks as things and have adaptable code. The second option is tempting at first, but this is where the business man in me has to speak up and question the wisdom in this route. Do I really want to write a parallel Object Query language just for the sake of independence from an external tool's API? I think this is called "not created here" (or something like that) syndrome, right?
So, after weighing my options, I very simply refactored my implementations of IQuery<T> to use the rich NHIbernate API and my Domain Entities use these objects that fill a collection of ICriteria objects, IProjection types and so on. MY IRepository<T> accepts any object of type IQuery<T> and returns the results I need. If I ever need to switch to a different OR/M provider I'll have to write implementations of IQuery<T> that speak that ORM's language. Really, if I am making that kind of change to an application, this level of adaption seems acceptable.
One tool that can keep these changes from affecting my service layer is some kind of Dependency Injection container that gets the appropriate instance for me at runtime. So instead of instantiating IQuery<T> implementations directly from, say, a higher service layer, I can invoke Castle or Structure Map or whatever to get the instance that I configured. These kinds of tools are perfect for these situations.
OK ...back to coding.
I am sure everyone else has already figured this out but thought I'd jot it down here.
I wanted to isolate all messages being sent from NHibernate to its own logging file. NHibernate has lots to say during its operations and often the messages it delivers are the only way of tracking down some obsure error since sometimes the exception messages in NHibernate are...well, wanting. After poking around the forums and trying out a few methods, here is how I finally split my NHibernate logs from the rest of my application logs.
The Configuration
For simplicity, say I want just two log text files produced, system.log and nhibernatelog.log. Look over the log4net configuration and Ignore the Console and AspNetTraceAppenders below:
<log4net>
<appender name="Console" type="log4net.Appender.ConsoleAppender">
<layout type="log4net.Layout.PatternLayout">
<!-- Pattern to output the caller's file name and line number -->
<conversionPattern value="%5level [%thread] (%file:%line) - %message%newline"/>
</layout>
</appender>
<appender name="AspNetTraceAppender" type="log4net.Appender.AspNetTraceAppender">
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date [%thread] %-5level %logger [%property{NDC}] - %message%newline"/>
</layout>
</appender>
<appender name="NHibernateAppender" type="log4net.Appender.RollingFileAppender">
<file value="../../../log/nhibernatelog.log"/>
<appendToFile value="true"/>
<maximumFileSize value="1000KB"/>
<maxSizeRollBackups value="2"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %level %thread %logger - %message%newline"/>
</layout>
</appender>
<appender name="SystemAppender" type="log4net.Appender.RollingFileAppender">
<file value="../../../log/system.log"/>
<appendToFile value="true"/>
<maximumFileSize value="1000KB"/>
<maxSizeRollBackups value="2"/>
<layout type="log4net.Layout.PatternLayout">
<conversionPattern value="%date %level %thread %logger - %message%newline"/>
</layout>
</appender>
<root>
<level value="ALL"/>
<appender-ref ref="AspNetTraceAppender"/>
<appender-ref ref="Console"/>
</root>
<logger name="NHibernate">
<level value="ERROR" />
<appender-ref ref="NHibernateAppender"/>
</logger>
<logger name="NHibernate.Loader.Loader">
<level value="INFO" />
<appender-ref ref="NHibernateAppender"/>
</logger>
<logger name="System">
<level value="ALL" />
<appender-ref ref="SystemAppender"/>
</logger>
</log4net>
The primary points of interest are:
- I do not have my SystemAppender or my NHibernateAppender inside the <root /> element. Doing so resulted in duplicate entries
- The 'name' attribute on the <logger/> element is important. Take note.
Start 'er up
To get things wired up when the application starts, add this to your global.asax in the Application_Start section:
log4net.Config.XmlConfigurator.Configure();
Logging to 'system.log'
To get a reference to my 'system' logger, i just have to use the following:
static ILog logger = LogManager.GetLogger("System");
Notice that I am referencing the logger by its name ("System") in the <logger/> section above. Then to write out to the file, just this:
logger.Debug("Writing Debug Message");
Logging to 'nhibernate.log'
The important thing to understand about controlling logging for NHibernate is that it writes using the following call:
LogManager.GetLogger( Type or string )
Check out this forum post for an explanation of the implications. I snagged the 'logger' setup from this post.
What this means is that the <logger/> element in the configuration will need to have its 'name' set to 'NHibernate' since that is how it is called...any other name NHibernate just won't recognize and logging won't happen.
Note, too , that the configuration above is only logging the SQL (NHibernate.Loader.Loader) sent to the DB and error messages. This helps sift out the rif-raf that I am not real interested in usually.
Once these have been all set up I was successfully logging to the right file. Here are the posts that helped me:
Implementing the Model-View-Presenter has been fun. I appreciate it's tendency to enforce discipline in keeping any business decisions out of the View layer. I was always bewildered when I would read that
the business logic should be separated from the 'code-behind' source and then see millions of DataSet examples where that was tons of business logic embedded in Web Pages.
However, trying to separate the presentation logic away and keep the client as dumb as possible presents (forgive the pun) some new challenges. Keeping the presenter interacting with only
the interfaces of both the IView and IFacade (or IService or whatever...IModel) is straightforward so long as basic property setters and getters on the view are the requirements. But when you get
into nested collections within collections AND want to keep the client dumb, then it is time to reach into the patterns hat for a solution.
Specifically, I wasn't sure how to have the Presenter authoritative for logic decisions while still keeping the DataBind() calls in place on my DataSource controls (like a Repeater). My first reaction is
to create a kind of 'placeholder' property for the DataItem and have that iteratively set during binding for processing in the Presenter layer. For example, perhaps a different list in a dropdown
list needs to appear BASED ON WHAT THE DATA ITEM IS. I want my presenter to inject the correct list, but how to do that during binding?
A refactoring has emerged that I'll call Replace Placeholder Property With Visitor. The Visitor pattern is well-suited
to exactly this kind of databinding dilemma. No doubt, some will think it's heavyhanded, but I have found it elegant and keeps responsibilities between the view and presenter where they belong.
To start, say we have Person we want to edit that has a collection of Addresses. We want to bind a Repeater control on a collection of Labels so that for each label, we have a blank Address
form. Within each address form, we have a drop down list of StreetTypes (Rd, St, Ln, etc). So it looks like this.
To use the Visitor pattern to inject the list of StreetTypes into the DropDown list, we'll abstract away the contracts and have the actors only deal with those. First, the ISaveAddressViewItem interface
This will live in the Presentation project so the Presenter will interact with any implementation of it. Before creating that let's have it inherit from a more generic interface (explain later):
public interface IViewRepeaterItem
{
}
Ok, now let's create the contract that has the property names we expect.
public interface ISaveAddressViewItem:IViewRepeaterItem
{
string Label { get;}
string SuiteApartment { get;set;}
string PostalCode { get;set;}
string StreetField1Value { get;set;}
string StreetField1StreetType { get;set;}
string StreetField2Value { get;set;}
string StreetField3Value { get;set;}
IList<StreetTypes> StreetTypes { set;}
}
Next, let's create a custom RepeaterItem that will implement this interface. We'll put this in its own project where we create our other custom controls (NOT App_Code!):
public class SaveAddressViewRepeaterItem : RepeaterItem, ISaveAddressViewItem,IViewRepeaterItem
{
public SaveAddressViewRepeaterItem(int itemIndex, ListItemType itemType) : base(itemIndex, itemType)
{
}
public string Label
{
get { return ControlUtil.FindFirstControlById<System.Web.UI.WebControls.Label>(this, "Label").Text; }
}
public string SuiteApartment
{
get { return ControlUtil.FindFirstControlById<TextBox>(this, "SuiteApartment").Text; }
set { ControlUtil.FindFirstControlById<TextBox>(this, "SuiteApartment").Text = value; }
}
public string PostalCode
{
get { return ControlUtil.FindFirstControlById<TextBox>(this, "PostalCode").Text; }
set { ControlUtil.FindFirstControlById<TextBox>(this, "PostalCode").Text = value; }
}
public string StreetField1Value
{
get {return ControlUtil.FindFirstControlById<TextBox>(this,"StreetField1Value").Text; }
set {ControlUtil.FindFirstControlById<TextBox>(this,"StreetField1Value").Text = value; }
}
public string StreetField1StreetType
{
get { return ControlUtil.FindFirstControlById<DropDownList>(this, "StreetField1StreetTypes").SelectedValue; }
set {
ControlUtil.FindFirstControlById<DropDownList>(this, "StreetField1StreetTypes").SelectedValue = value;}
}
public string StreetField2Value
{
get { return ControlUtil.FindFirstControlById<TextBox>(this, "StreetField2Value").Text; }
set { ControlUtil.FindFirstControlById<TextBox>(this, "StreetField2Value").Text = value; }
}
public string StreetField3Value
{
get { return ControlUtil.FindFirstControlById<TextBox>(this, "StreetField3Value").Text; }
set { ControlUtil.FindFirstControlById<TextBox>(this, "StreetField3Value").Text = value; }
}
public IList<StreetTypes> StreetTypes
{
set
{
DropDownList list = ControlUtil.FindFirstControlById<DropDownList>(this, "StreetField1StreetTypes");
if (list != null)
list.DataSource = value;
}
}
}
Before we look at the Repeater that will consume this, let's look at the contract for the Presenter that the ISaveAddressViewItem's parent will interact with during databinding
public interface IPresenterVisitor
{
void PostVisit(IViewRepeaterItem item);
void SetItemVisit(IViewRepeaterItem item);
void LoadItemVisit(IViewRepeaterItem item);
}
Now let's look at the Repeater control (also in the Class project, not in the Web folder) we create to bring the IViewRepeaterItem and IPresenterVisitor together. We need to tell this somewhat generic repeater control what kind of custom RepeaterItem
to instantiate in order to get our implementation, so quick and dirty I add a ItemType property that I can declare in the aspx form. We also need to tell the Repeater control who the
Presenter is that implements IPresenterVisitor. Finally, we need to override certain events to wireup our
conversation between the View and Presenter during binding. This is where the visitor pattern shines.
public class ViewRepeater : Repeater
{
public ViewRepeater()
{
ItemDataBound += new RepeaterItemEventHandler(SetItem);
}
private Type _itemType;
[Bindable(true)]
public Type ItemType
{
get { return (Type)ViewState["ItemType"]; }
set
{
if(value.GetInterface(typeof(IViewRepeaterItem).ToString())==null)
{
throw new InvalidCastException("The RepeaterItem must implement the IViewRepeaterItem interface");
}
ViewState["ItemType"] = value;
}
}
private IPresenterVisitor _visitor;
public IPresenterVisitor Visitor
{
get { return _visitor; }
set { _visitor = value; }
}
protected override RepeaterItem CreateItem(int itemIndex, ListItemType itemType)
{
IViewRepeaterItem item = Activator.CreateInstance(ItemType, itemIndex, itemType) as IViewRepeaterItem;
((RepeaterItem)item).DataBinding += new System.EventHandler(LoadItem);
return (RepeaterItem)item;
}
/// <summary>
/// Used to assign datasource to controls before binding.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void LoadItem(object sender, System.EventArgs e)
{
Visitor.LoadItemVisit((IViewRepeaterItem)sender);
}
/// <summary>
/// Our controls have been bound, but now the presenter can set hte values to the model object's value.
/// </summary>
/// <param name="sender"></param>
/// <param name="e"></param>
void SetItem(object sender, RepeaterItemEventArgs e)
{
if (e.Item.ItemType.Equals(ListItemType.Item) ||
e.Item.ItemType.Equals(ListItemType.AlternatingItem) ||
e.Item.ItemType.Equals(ListItemType.EditItem))
{
Visitor.SetItemVisit((IViewRepeaterItem)e.Item);
}
}
/// <summary>
/// This is used to iterate the items and allow the presenter to perform actions on each item such as during a Save operation.
/// </summary>
public void Post()
{
foreach(RepeaterItem item in Items)
{
if (item is IViewRepeaterItem)
{
Visitor.PostVisit((IViewRepeaterItem)item);
}
}
}
}
The Presenter can now interact with the View during databinding by implementing the three methods in IPresenterVisitor
public void PostVisit(IViewRepeaterItem item)
{
ISaveAddressViewItem viewItem = (ISaveAddressViewItem) item;
if (new NonEmptyStringSpecification().IsSatisfiedBy(viewItem.PostalCode) ||
new NonEmptyStringSpecification().IsSatisfiedBy(viewItem.StreetField1Value))
{
ContactAddressDTO dto = GetAddressByLabel(viewItem.Label);
bool isNull = !new NonNullSpecification<ContactAddressDTO>().IsSatisfiedBy(dto);
if ((!isNull && dto.StateMarker.Equals(DTOStateMarker.DELETED) ||
isNull))
{
PostalCodeDTO postalCode = new PostalCodeDTO(0, viewItem.PostalCode);
StreetAddressDTO streetAddress = new StreetAddressDTO(0, postalCode, viewItem.SuiteApartment, null, null);
StreetTypes type = new ValueObjectFieldBrowser<StreetTypes>().GetBy("Abbreviation", viewItem.StreetField1StreetType);
streetAddress.StreetFields.Add(new StreetFieldDTO(0, 0, viewItem.StreetField1Value, type));
if (new NonEmptyStringSpecification().IsSatisfiedBy(viewItem.StreetField2Value))
{
streetAddress.StreetFields.Add(new StreetFieldDTO(0, 1, viewItem.StreetField2Value, StreetTypes._NONE));
}
if (new NonEmptyStringSpecification().IsSatisfiedBy(viewItem.StreetField3Value))
{
streetAddress.StreetFields.Add(new StreetFieldDTO(0, 2, viewItem.StreetField3Value, StreetTypes._NONE));
}
ContactAddressDTO newAddress = new ContactAddressDTO(0, viewItem.Label, streetAddress);
Contact.Addresses.Add(newAddress);
}
}
}
public void LoadItemVisit(IViewRepeaterItem item)
{
((ISaveAddressViewItem) item).StreetTypes = StreetTypes.LIST_ALL;
}
public void SetItemVisit(IViewRepeaterItem item)
{
ISaveAddressViewItem viewItem = (ISaveAddressViewItem)item;
/*//Check if Contact has addresses,
if so then search for an address matching item.Label then populate the fields as necessary
if not, then do nothin*/
if (new NonNullSpecification<ContactDTO>().IsSatisfiedBy(Contact))
{
if (Contact.Addresses.Count > 0)
{
ContactAddressDTO address = GetAddressByLabel(viewItem.Label);
if (address != null)
{
bool isDeleted = address.StateMarker.Equals(DTOS.DTOStateMarker.DELETED);
viewItem.StreetField1Value = isDeleted?"":address.StreetAddress.StreetFields[0].Value;
viewItem.StreetField1StreetType = isDeleted ? "" : address.StreetAddress.StreetFields[0].StreetType.Abbreviation;
if (address.StreetAddress.StreetFields.Count > 1)
{
viewItem.StreetField2Value = isDeleted ? "" : address.StreetAddress.StreetFields[1].Value;
}
if (address.StreetAddress.StreetFields.Count > 2)
{
viewItem.StreetField3Value = isDeleted ? "" : address.StreetAddress.StreetFields[2].Value;
}
}
}
}
}
The details of the Presenter in relating to the underlying model and how the View is injected are left out...there's plenty on the web about how to do this. What is intersting here is the
LoadItemVisit method...if we had complex logic such as presenting a different StreetTypes list based on some property in the ViewItem (I know, that's stupid), we could do it right here in our presenter...
just where it should be. The View remains dumb to logic and is relying on the Presenter to inject collections and values into its controls.
Once you get used to the "dual-dispatch" of the Visitor pattern, it becomes a very useful way of getting rid of some properties that are temporal to hold values while processing collections.
Resources