I’ve reviewed my architecture diagram, and the revised one is as follows:
Although it may seem like a straight-forward diagram, I had to create a prototype, and the diagram just doesn’t do justice to the implementation. There are things that are hard to communicate using just these ovals and boxes. Make sure to download and try out my sample solution. It has a WinForms app ready to run.
I’ve made a VS solution as a sample implementation of this diagram. You can download it here. In it, the UI uses the M-V-P pattern, I use the Strategy pattern for my Data Provider, and my Repository objects are Factories. I can’t include all the source in this blog post, so I’ll just include the key pieces:
The following is a snippet from my WinForms class. Notice how I delegate key events to my Presenter class. This class implements the IContactView interface(included in my source code), so the presenter can control the view.
62 //
63 // Required for Windows Form Designer support
64 //
65 _presenter = new Presenter.ContactPresenter(this);
66 _currentContact = new Model.Contact();
67 InitializeComponent();
68
69 //
70 // TODO: Add any constructor code after InitializeComponent call
71 //
72 }
73
74 private Presenter.ContactPresenter _presenter;
75
76 private void button2_Click(object sender, System.EventArgs e) {
77 _presenter.LookUpContactByUserName(ddlUserName.SelectedItem.ToString());
78 }
79
80 private void button1_Click(object sender, System.EventArgs e) {
81 this.CurrentContact.UserName = txtUserName.Text;
82 this.CurrentContact.Name = txtName.Text;
83 this.CurrentContact.PhoneNumber = txtPhoneNumber.Text;
84 this.CurrentContact.Age = int.Parse(txtAge.Text);
85 _presenter.SaveContact();
86 }
87
88 private Model.Contact _currentContact;
89
90 public Model.Contact CurrentContact {
91 get {
92 return _currentContact;
93 }
94 set {
95 _currentContact = value;
96 }
97 }
98
99 public void Update() {
100 if(CurrentContact != null) {
101 txtUserName.Text = CurrentContact.UserName;
102 txtName.Text = CurrentContact.Name;
103 txtPhoneNumber.Text = CurrentContact.PhoneNumber;
104 txtAge.Text = CurrentContact.Age.ToString();
105 }
106 }
In my Presenter class that follows, I handle View behaviors and contact the Repository objects in my business layer to get and save my business object.
7 {
8 public ContactPresenter(View.IContactView view) {
9 _view = view;
10 }
11
12 private View.IContactView _view;
13 private Model.Repository.ContactRepository _repository = new Model.Repository.ContactRepository();
14
15 public Model.Repository.ContactRepository Repository {
16 get { return _repository; }
17 }
18
19 public void SaveContact() {
20 Repository.SaveContact(_view.CurrentContact);
21 _view.Update();
22 }
23
24 public void LookUpContactByUserName(string userName) {
25 _view.CurrentContact = _repository[userName];
26 _view.Update();
27 }
28 }
My business object is very simple:
6 {
7 // public fields used for simplicity (Don’t do this for real).
8 public string UserName;
9 public string Name;
10 public string PhoneNumber;
11 public int Age;
12 }
And my repository object (inside my business assembly): Note how I’m using a ConfigurationService singleton. This configuration service is responsible for reading configuration required by my business layer. This method does couple an appSettings key with my business layer, but if this is a problem for a particular application, the Presenter layer, on application startup, could get appropriate configuration and set the instance of ConfigurationService in the Repository layer.
The ConfigurationService will handle (based on the config key) creating an instance of the DataProvider. The DataProvider will implement IDataAccessLayer (included in source code). This interface defines methods for getting data objects. The data object responsible for Contact data implements IContactData, an interface defined in the Repository layer. To switch persistence mediums, I merely change the config item and provide a library that implements the appropriate interfaces.
6
7 private Data.IContactData ContactData {
8 get { return ConfigurationService.GetInstance().DataProvider.GetContactData();}
9 }
10
11 public Contact this[string userName] {
12 get { return this.ContactData.GetContact(userName); }
13 }
14
15 public void SaveContact(Contact contact) {
16 ContactData.SaveContact(contact);
17 }
18 }
My data provider object follows. Note that only one method is defined now, but the class would grow with the number of domain objects needing corresponding data objects.
8
9 public IContactData GetContactData() {
10 return new ContactData();
11 }
12 }
And finally, I have stubbed out a data object which handles getting and saving my Contact objects. I just whipped up a quick implementation for demonstration purposes, and it works because I implement the IContactData interface. An instance of this type is created and returned by my DataAccessLayer class to the Repository, which can use it.
8
9 private static Contact jpalermo, sbellware;
10
11 static ContactData() {
12 jpalermo = new Contact();
13 jpalermo.UserName = “jpalermo”;
14 jpalermo.Name = “Jeffrey Palermo”;
15 jpalermo.PhoneNumber = “555-5555”;
16 jpalermo.Age = 26;
17
18 sbellware = new Contact();
19 sbellware.UserName = “sbellware”;
20 sbellware.Name = “Scott Bellware”;
21 sbellware.PhoneNumber = “555-5555”;
22 sbellware.Age = int.MaxValue;
23 }
24
25 public void SaveContact(Contact contact) {
26 // Bogus implementation. In a real scenario, I’d go to my database or other
27 // persistence medium to save the contact.
28 Contact o = null;
29 switch(contact.UserName) {
30 case “jpalermo”:
31 o = jpalermo;
32 break;
33 case “sbellware”:
34 o = sbellware;
35 break;
36 }
37 o.UserName = contact.UserName;
38 o.Name = contact.Name;
39 o.PhoneNumber = contact.PhoneNumber;
40 o.Age = contact.Age;
41 }
42
43 public Contact GetContact(string userName) {
44 // Bogus implementation. In a real scenario, I’d go to my database or other
45 // persistence medium to retrieve the contact.
46 Contact o = null;
47 switch(userName) {
48 case “jpalermo”:
49 o = jpalermo;
50 break;
51 case “sbellware”:
52 o = sbellware;
53 break;
54 }
55 return o;
56 }
57 }
I wanted to create this prototype as a proof of concept of this architecture. It may seem complex with all the interfaces, but I believe that for an enterprise-class application, it provides the flexibility required for unit-testing and bolting on components as required. The biggest issue, I believe, it testability. Each class is highly cohesive in that it serves a clear purpose, but the components are loosely-coupled because each can be used and tested in isolation.
One important thing to note is that the business assembly doesn’t reference any other assemblies. All of the assemblies reference it. It is able to communicate and even call to the other assemblys only because classes in other assemblies implement interfaces defined in the business assembly.
Note: The downloadable sample has been revised for testability, and unit tests have been included.