XCRM: Marketing Campaigns and Customer support

XAF Team Blog
07 October 2008

This post may be outdated. For the latest Domain Components concepts and examples refer to the current online documentation.

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.

Marketing Campaigns

A Marketing Campaign has no relationships. However, a Lead and Opportunity may reference a Campaign:

[Test]
public void CampaignRelationships() {
    RegisterDC<ILead>();
    RegisterDC<ICampaign>();
    RegisterDC<IContact>();
    RegisterDC<IAccount>();
    RegisterDC<IOpportunity>();
    Generate();
    ICampaign campaign = ObjectSpace.CreateObject<ICampaign>();
    campaign.Name = "Playboy back cover";
    ILead lead = ObjectSpace.CreateObject<ILead>();
    IOpportunity opportunity = ObjectSpace.CreateObject<IOpportunity>();
    lead.Campaign = campaign;
    opportunity.Campaign = campaign;
}
[DomainComponent]
public interface ICampaign {
    string Name { get; set; }
}
[DomainComponent]
public interface ILead {
    
    ICampaign Campaign { get; set; }
}
[DomainComponent]
public interface IOpportunity {
    
    ICampaign Campaign { get; set; }
}

Instead of adding the Campaign reference property to both the ILead and IOpportunity components, I could add the ICampaignResult interface and inherit the ILead and IOpportunity from it. But I think, this is overkill. However, if one more class references the Campaign, and business logic is supplied for this reference, I'll choose this approach.

Cases (customer support incidents)

A Case is a record about a user's inquiry or problem. It is linked to an Account and/or a Contact. If the Contact is set, the Account is initialized from the Contact’s Account, if any:

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

    ICase case1 = ObjectSpace.CreateObject<ICase>();
    case1.Subject = "1";
    case1.Contact = roman;
    Assert.IsNull(case1.Account);

    ICase case2 = ObjectSpace.CreateObject<ICase>();
    case2.Subject = "2";
    case2.Account = dx;
    Assert.IsNull(case2.Contact);

    roman.Account = dx;
    ICase case3 = ObjectSpace.CreateObject<ICase>();
    case3.Subject = "3";
    case3.Contact = roman;
    Assert.AreEqual(dx, case3.Account);
}

[DomainComponent]
public interface ICase {
    string Subject { get; set; }
    IAccount Account { get; set; }
    IContact Contact { get; set; }
}

To make this test pass we need an extra code, in addition to the interface. We need domain logic for the ICase to initialize the ICase.Account property by the ICase.Contact's Account:

[DomainLogic(typeof(ICase))]
public class CaseLogic {
    public static void AfterChange_Contact(ICase self) {
        if (self.Contact != null && self.Account == null) {
            self.Account = self.Contact.Account;
        }
    }
}

Here is how we are planning to add domain logic to the classes that are generated by the XpoBuilder. We are going to use naming conventions. If the XpoBuilder finds a method named “AfterChange_XXX”, it tries to call this method in the generated setter of a XXX property. We'll probably add the attribute-based binding in the future.

A Case should have a human-readable ID that can be used in communications to quickly reference the Case. This ID should be generated only once, when a new object is beeing created. In addtion, the ID property should be read-only, but persistent.

Here is one more DC design challenge: how domain logic can specify the initial value of a read-only property? Should I add an AfterConstruction method to the business logic to set a value? But in this instance, I'll have to provide some system interfaces to call the ID’s setter. So, I think it is better to implement the Init_XXX domain logic methods that will allow you to set a property's initial value for a newly created object.

Since the ID property doesn't have a setter, it won't be persisted by default. To make this property persistent, I've decided to decorate it with the PersistentDc attribute. It could make sence to call this attribute Persistent, but in XAF this name conflicts with XPO’s Persistent attribute. So, it will be PersistentDc for now, and we will see what to do with it later.

[DomainComponent]
public interface ICase {
    [PersistentDc]
    string ID { get; }
    
}
[DomainLogic(typeof(ICase))]
public class CaseLogic {
    public static string Init_ID(ICase self) {
        //ToDo: Change to "select count(*)+1 from case" analog
        return Guid.NewGuid().ToString();
    }
    
}

This is a very simplistic implementation. I will return with a more realistic code, as soon we have a proper infrastructure.

Tags
3 comment(s)
Anonymous
Martin Hart Turner

Roman:

When writing these articles, could you please tell us what the acronyms are (RWA, XCRM etc.) - I'm seeing a lot of these recently, and for non-English speaking residents they can be confusing.

8 October, 2008
Sigurd Decroos
Sigurd Decroos

1 remark: Guid.NewGuid.ToString gives an ID out of order. If you will have many records in your datatable, you will start to have paging problems. It is better to use a sequential GUID based on the time (e.g. www.codeplex.com/SeqGuidGenerator). That way, the paging tables will always be extended at the back and you will get better performance.

8 October, 2008
Roman Eremin (DevExpress)
Roman Eremin (DevExpress)

Sigurd:

Please note that this ID is not a database key - this is just an autogenerated readonly unique string that will let human reference the issue in email or other type of communication.

So its content won't affect any aspect of database server work.

8 October, 2008

Please login or register to post comments.