Separate Domain from Presentation – Part I
How to separate domain from presentation
Cross post from IRefactor
Visual Studio can be a swiss knife in the hands of a Software Developer.
You want a fully functional application within several hours; here you go sir:
- Create Windows Forms Application.
- Add relevant Data Sources (in Figure 1 below:
Courses
table). - Drag generated entities from the Data Sources onto relevant Forms.
- Add minimum code to show the Forms in the required order.
- Compile & execute.
Walla, I present you an “enterprise application”!
Though it looks amateur, it allows fully functional CRUD operations.
But, the power can corrupt…
Lately, I came across a few projects implemented using the above approach. Needless to say, the projects were “spaghetti code” - all the UI elements, visual states, business logic & data access, were centralized within a few Forms.
(Don't think it's a rare situation! There are enough companies that try to "save" time using such a “quick & dirty” approach).
I am a pragmatic Software Engineer and I do believe that the “quick & dirty” approach is vital in some cases.
If you present a demo application that demonstrates your capabilities; go ahead, use the necessary tools to implement it as fast as possible.
However, if you develop a real life application using the above approach, it’s a sure road to hell. Slowly but surely, the “fast development” takes more and more time:
- The customer’s button event click is very similar to the employee’s event click, but correlate and update different UI elements » you duplicate the logic in both places.
- New requirement add new conditions to some wizard flow » you add an “
if
” statement to address the conditions. Sometimes, you add a “switch
” statement due to the fact that many “if
s” already exist. - New requirements impose saving more data » you add more parameters to your methods. You also go further and add those parameters to each and every place (form/method) where the change is required.
- The classes and methods become huge, they are overcrowded with UI elements, visual states transformations and business logic. Next time you try to read the code, you will need at least a few hours to concentrate and walk through the mess.
The Project Manager responsible for the aforementioned projects, said to me:
“Any change request I want to apply, come back with unrealistic time estimations. When I ask the developer why it takes so long, he answers: My Gosh – do you know what it takes to change that code? Do you understand how long I need to retest the application, just to verify I didn’t break anything?”
It seems obvious that if in the first place, the project had been written in clear separation of UI, Business Logic and Data Access Layers, it would have been easier to maintain the product lifetime. However, clearly it is not the case here, so what can we do?
One of the tools we can apply here is: Refactoring.
Refactoring is a process of improving software’s internal structure without changing its external behavior. Refactoring ensures completeness of the external behavior, by executing automatic unit tests. We improve the code in incremental small steps (refactoring steps) and each change is followed by a compilation and unit tests execution.
In the following posts, I will demonstrate refactoring called “Separate Domain from Presentation”, which obviously breaks the tight coupling between UI and BL. After finishing the demonstrations of the refactoring process itself (it will take me several posts to do it), I will also explain how to create the suite of unit tests, which allow validation of external behavior completeness.
Remarks
- I will refactor the application from figure 1 (above).
The application contains one View (
FrmMain
) and one domain object (CoursesDS
). - Though the sample application contains one
Form
and oneDataSet
, the same technique can be applied in more complicated situations. Just identify the groups of objects that fit together and apply for each one of them the following steps of separation. - It’s advisable to download the sample IRefactor.CoursesView project and go through the refactoring steps while reviewing the code.
- In the next posts, I will introduce how to enhance the separation using refactoring towards MVP (or MVC) patterns.
- It’s harder to write about the refactoring process, than actually implement it. Don’t be intimidated, it takes longer to explain each and every small step (and provide screenshots) than to do it in practice. To separate Domain from Presentation (up to MVP) takes me about 10 min of work.
Refactoring Steps
- When the
Irefactor.CoursesView
project was defined initially, Visual Studio generated aDataSet
calledCoursesDS
for the Courses Data Source. As you can see, theIrefactor.CoursesView
project mixes UI (FrmMain
) elements and Domain (CoursesDS
) elements.Figure 2 - Let’s start by removing the
CoursesDS
domain object from the UI (View
).Create a project
IRefactor.Common
and copy thereCoursesDS DataSet
.(Drag the
CoursesDS
from project to project, Visual Studio will do the rest).Due to the fact that
CoursesDS
is a VS auto generated class, one should also copy the connection string definition which is stored in the Settings of theIRefactor.CoursesView
project. AddSettings.settings
to IRefactor.Common Properties folder and copy the connection string.Figure 3 - Compile the Solution and execute the Unit Tests.
- Change the
FrmMain
(View
) to use the newly createdIRefactor.Common.CoursesDS DataSet
.Open the
FrmMain
’s designer. Remove thecoursesDS
(the instance of the domain object:IRefactor.CoursesView.CoursesDS DataSet
– see red arrow 1).Figure 4 - On the Toolbox window, under the recently added
IRefactor.Common
Components, you will see theIRefactor.Common.CoursesDS DataSet
.Drag this
CoursesDS
to theFrmMain Form
.Figure 5 - Dragging the
IRefactor.Common.CoursesDS DataSet
onto theFrmMain
surface generates an instance of the class. Rename it tocoursesDS
(to match the previous name). - Repeat the three last steps also for
IRefactor.CoursesView.CoursesTableAdapter
(see Figure 4, red arrow 2) - Select the
BindingSource
(see Figure 4,bsCourses
) and change itsDataSource
tocoursesDS
instance (coursesDS
now comes fromIRefactor.Common
project). Also change itsDataMember
to point theCourses
table.Figure 6 - In
FrmMain
code behind file, add reference to theIRefactor.Common namespace
and change theCoursesDS
to a fully qualified name.// add reference using IRefactor.Common; // ... private void coursesBindingNavigatorSaveItem_Click { (object sender, EventArgs e) // ... // change the coursesDS to IRefactor.Common.CoursesDS IRefactor.Common.CoursesDS.CoursesDataTable changes = this.coursesDS.Courses.GetChanges() as IRefactor.Common.CoursesDS.CoursesDataTable; // ... }
- Compile the Solution and execute the Unit Tests.
- Go over
IRefactor.CoursesView
and remove all instances of the previous domain objectIRefactor.CoursesView.CoursesDS DataSet
. Also, delete the CoursesDS.xsd file from the project. - Compile the Solution and execute the Unit Tests.
We have successfully completed the first steps. We have separated the UI project called IRefactor.CoursesView
from the Domain Project called IRefactor.Common
. Now it’s time to become serious and continue the refactoring towards the MVP pattern.