XCRM: Writing real-world CRM application

XAF Team Blog
01 October 2008

EDIT:
Domain Components (DC) is in maintenance mode and we do not recommend its use in new software projects.
 

This post is one in a series about our work on Domain Components (DC) framework and related component libraries. I’m just describing what we are working on, what problems we are facing and what results we've got so far.

I'm starting to write a generic CRM application, to try the new domain component (DC) technology and decide what classes should be included in our Domain Component technology. This application should be suitable for small business and extendable to support specific markets. While writing, I'm sure to find bottlenecks in XAF that we will have to fix.

In general, a CRM application should be able to manage the following things:

  • Contacts
  • Accounts
  • Leads
  • Opportunities
  • Products
  • Appointments/Calendar/Tasks
  • Service Requests
  • Marketing Campaigns

In addition, even a basic CRM system should have a security system that is aware of organization structure.

To emphasize the most interesting design parts, I won't detail every domain class. I will create most classes in brief form, and then extend them as required. In the first iteration, I will focus on class relationships.

Contact. Can belong to an Account. So, it has the Account property.

Account. Can belong to another Account. So, it has the ParentAccount and Subaccounts properties. In addition, it should have a Contacts list, and a PrimaryContact, which may not belong to the Contacts list.

I’m using TDD, so I will capture this knowledge in unit tests.

Unit tests for Domain Components

To write tests for domain components, I think I will need a DC infrastructure to be able to generate components from interfaces and business logic. I won't use the entire XAF – I will need only small parts of its services. Here is an example of a test:

[DomainComponent]
public interface IBasicTestThing {
    string Name { get; set; }
}
[TestFixture]
public class BasicTests : BaseTest {
    [Test]
    public void TestBasicThing() {
        RegisterDC<IBasicTestThing>();
        Generate();
        IBasicTestThing thing1 = ObjectSpace.CreateObject<IBasicTestThing>();
        thing1.Name = "abc";
        ObjectSpace.CommitChanges();
        IBasicTestThing thing2 = ObjectSpace.FindObject<IBasicTestThing>(null);
        Assert.AreEqual("abc", thing2.Name);
    }
}

In all my tests, I'll register and generate domain components, and use an ObjectSpace object. So, all my tests will be inherited from the BaseTest class that will provide the required services. Look how it's implemented now:

public class BaseTest {
    private IObjectSpace objectSpace;
    private IObjectSpaceProvider osProvider;

    [SetUp]
    public virtual void SetUp() {
        XafTypesInfo.Reset();
        osProvider = new ObjectSpaceProvider(new MemoryDataStoreProvider());
        objectSpace = osProvider.CreateObjectSpace();
    }
    public void RegisterDC<T>() {
        if (typeof(T).IsInterface) {
            XafTypesInfo.Instance.AddEntityToGenerate(typeof(T).Name, typeof(T));
        } else {
            XafTypesInfo.Instance.RegisterEntity(typeof(T));
        }
    }
    public IObjectSpace ObjectSpace {
        get { return objectSpace; }
    }
    public void Generate() {
        XafTypesInfo.Instance.GenerateEntities();
    }
}

The SetUp method cleans up the current type information, and creates an Object Space using a newly created Object Space Provider (using in-memory data store). The RegisterDC and Generate methods work with the XafTypesInfo system, like it's performed in XAF.

Capturing domain knowledge in unit tests

I’m ready to write a test for the Contact - Account relationship:

[Test]
public void ContactAccountRelationships() {
    RegisterDC<IContact>();
    RegisterDC<IAccount>();
    Generate();
    IContact roman = ObjectSpace.CreateObject<IContact>();
    roman.FirstName = "Roman";
    roman.LastName = "Eremin";
    IAccount dx = ObjectSpace.CreateObject<IAccount>();
    dx.Name = "DevExpress";

    dx.PrimaryContact = roman;
    Assert.IsNull(roman.Account);

    dx.Contacts.Add(roman);
    Assert.AreEqual(dx, roman.Account);

    IAccount xafTeam = ObjectSpace.CreateObject<IAccount>();
    xafTeam.ParentAccount = dx;
    Assert.IsTrue(Enumerator.Exists<IAccount>(dx.Subaccounts, xafTeam));
}

Enumerator is a helper class from the DevExpress.ExpressApp.Utils.dll assembly. It provides utility methods for the IEnumerable interface (similar extension methods already exist in .net 3.0).

Here is the code that makes this test pass (remember – I’m focused on relationships, so don’t tell me that the IContact should contain the IPerson interface) :


[DomainComponent]
public interface IAccount {
    string Name { get; set; }

    IAccount ParentAccount { get; set; }
    IList<IAccount> Subaccounts { get; }

    IContact PrimaryContact { get; set; }
    IList<IContact> Contacts { get; }
}
[DomainComponent]
public interface IContact {
    IAccount Account { get; set; }
}

No logic is required, because the XpoBuilder automatically generates the correct associations and XPO manages them. So, when I add roman to the dx.Contacts list, the roman.Account property is initialized automatically.

Free DevExpress Products - Get Your Copy Today

The following free DevExpress product offers remain available. Should you have any questions about the free offers below, please submit a ticket via the DevExpress Support Center at your convenience. We'll be happy to follow-up.
Tags
No Comments

Please login or register to post comments.