Saturday, April 18, 2009

Separate Domain from Presentation – Part I

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.

Figure 1

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 applications 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 “ifs” 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 one DataSet, 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 a DataSet called CoursesDS for the Courses Data Source. As you can see, the Irefactor.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 there CoursesDS 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 the IRefactor.CoursesView project. Add Settings.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 created IRefactor.Common.CoursesDS DataSet.
    Open the FrmMain’s designer. Remove the coursesDS (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 the IRefactor.Common.CoursesDS DataSet.
    Drag this CoursesDS to the FrmMain Form.
Figure 5
  • Dragging the IRefactor.Common.CoursesDS DataSet onto the FrmMain surface generates an instance of the class. Rename it to coursesDS (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 its DataSource to coursesDS instance (coursesDS now comes from IRefactor.Common project). Also change its DataMember to point the Courses table.

Figure 6


  • In FrmMain code behind file add reference to the IRefactor.Common namespace and change the CoursesDS 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 object IRefactor.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.


No comments:

Post a Comment