Developing on Microsoft CRM

time to read 10 min | 1944 words

I am currently in the process of leading a team in a project that is built around Microsoft CRM. A while ago I posted what I consider essential requirements for working effectively with business platforms. Since then, I had had a lot of time to play with MS CRM and see what the development story is.

Please remember, this is an evaluation of Microsoft CRM from a developer perspective. I don't touch on any of the other aspects that it may have.

I have already started to dislike it, and I have a feeling that it would only grow more acute in the future. The executive summary of this post is here:

Developing on MS CRM is building on a CRUD platform where all your business logic reside inside triggers.

Let me go over the points that I have outlined in my earlier post, again, those points are brought up without any particular order.

Source Control

MS CRM supports the creation of new entities and customizing existing ones. Those customizations are kept in a dark cave, to be let out at irregular interval for a slow walk around the block. The source control story on MS CRM is similar to the source control story of the average DB, except that you need to imagine that any DDL operation takes between one to thirty minutes to complete. And there is not textual representation that you can keep on the side.

Further extension points are available as plugins (callouts), which are triggers for CRUD operations on the CRM, and JavaScript customizations which you are supposed to write in a small text area inside the browser and allows you to respond to such events as changed / load. The SCM story for the plugins is a standard plugin issue, but for the JS customization there is literally no option except full import / export of the changes made from the CRM baseline image. See the previous paragraph about the performance implications of this, unacceptable.

Let me make it simple, if you want me to develop on your platform, source control is not a negotiable attribute. It should be as natural as breathing. If I have to do extra work to get it working, this is a critical issue.

Ease of deployment

You deploy a CRM installation by export your changes from the dev machine and importing to the staging/production installation. This is a lengthy process, and it doesn't capture all the changes that were made, you need to make several others (moving callouts, configuration, etc) from one environment to another.

Oh, and get ready to be intimately familiar with IISReset, and apparently resetting three other services is mandatory as well. And you need to do those on a regular basis just to get it deployed.

Debuggable

Well, you can attach to the CRM process, if you really feel like it, but that is frustrating, because you get to control only a small part of what is going on. It also looks like the CRM is throwing a lot of exceptions in normal operation, since I am unable to keep debugging in a "stop on all thrown exception", because it keeps throwing a lot of those.

Testable

There isn't any real way to do testing on the CRM without resorting to UI level testing, which is painful, to say the least. A lot of the methods in the extension API available expose such things as "string preEntityXml", which make writing the code hard, and testing harder.

Automation of deployment

As far as I can see, that is not doable easily, and it is a painful process (see deployment above) that really should be done with a click of a button.

Separation of Concerns & Don't Repeat Yourself

One of the issues that I have run into so far is that the entire development model for the CRM is reactive, and that doesn't really lend itself to keeping concerns separated and duplication minimized. The main problem is that the exposed API is primitive in the extreme and doesn't really lend itself to good practices (see extension options below).

In particular, the decision to base the extension options on non-portable Web Services and XML is a big mistake in this regard.

Doesn't shoot me in the foot

No comment on that so far, I haven't dug into it enough to know whatever this is an issue yet.

Make sense

This refers to what goes under the covers, and how I need to interact with it. So far, I had been told countless times "That is the way the CRM does it" in response to "WTF is this doing?!"

Possible to extend

Hacks are not something that I enjoy doing, and the more I dig into MS CRM, the more I feel like I need to resort to black magic hacks just to get things working. Let us go over some of the issues that I have run into so far.

Programming Model

If I were to suggest building a platform where users will have direct access to the DB tables' and all the logic for the system would be implemented using triggers, I would be called a madman. Nevertheless, this is the programming model that you have when you work in the CRM. Responding to actions in the CRM using plugins (called callouts).

Those callouts have signatures such as this one:

public override PreCalloutReturnValue PreCreate(
	CalloutUserContext userContext, 
	CalloutEntityContext entityContext, 
	ref string entityXml, 
	ref string errorMessage)

Then, you need to configure them in a config file, which lets you limit what this function will get. In that function, you can use the provided information to do something, but there is a lot of boiler plate code that you need to handle just to get out of the ugliness of this.

Configuration

Let us assume that you had the unmitigated goal to actually do try to develop a non trivial plugin to the CRM. You now need to supply it with some configuration, but the standard app.config / web.config approach will not work for you. The plugins are loaded into IIS's process, and I am not about to change w3wp.exe.config, that is taking it too far, and it violate the configuration / code should be a single unit issue.

Public Interface & API

The decision to base the entire extensibility model on web services and XML was a big mistake. It is mistake because the API that you get into the CRM is about the worst you can think of. Procedural, generated code, with very little flexibility or any way to supply layers on top of it that would allow you to present a nicer interface.

The generated code from the web service reference suffers from the usual awkwardness of WS code everywhere. Let us take this example, I want to change the customer preferred restaurant type and whatever he has a discount in it. Un order to do that, I need to write something like:

//preparing to call to the CRM
CrmService svc = new CrmService();
svc.Credentials = CredentialsHelper.GetCredentials();
svc.CallerIdValue = new CallerId();
svc.CallerIdValue.CallerGuid  = currentUserId;
ColumnSet columnsNeeded = new ColumnSet();
columnsNeeded.Attributes = new string[] {"name","new_customerpreferredresturanttype"};

//calling to the CRM
customer customer = (customer)svc.Retrieve("customer", customerGuid, columnsNeeded);

//updating customer value
customer.new_customerpreferredrestauranttype = new Picklist();
customer.new_customerpreferredrestauranttype.Value = 13;// Japanese 
customer.new_customerhasrestaurantdiscount = new CrmBoolean();
customer.new_customerhasrestaurantdiscount.Value = true;

svc.Update(customer);

Let us count the number of issues in this example:

  • It take 15 lines to do something as simple of that, and only two of those lines are actually doing anything useful, the rest are just there to make the API happy.
  • customer as a type name. .Net follows the PascalCase convention, and it is annoying in the extreme to see this in the middle of my code.
  • Magic strings all over the place. You don't have any way to avoid that.
  • Awkward API - CrmBoolean, anyone? Yes, they needed that to support nullable value types in 1.1, but it goes back to the problems with relying on the Web Service generated API to handle this scenario.
  • Magic values. Do you see the restaurant type constant there, it is a magic number that was defined in the CRM and it has no textual representation. You have the ID and the display value, and nothing else.

I couldn't get Microsoft's flagship web services' product to talk to the CRM successfully, so I am going to assume that the CRM is only accessible to ASMX platform (feel free to tell me if you can get Axis to successfully interact with MS CRM). That being the case, and since .Net is the obviously technology to work with the CRM, it would have been much better if MS would have simply supplied us with the ability to generate a DLL from the CRM with all the entities definitions and useful approach for interfacing with it. CodeDOM make it about as easy as providing a web service, so I have no idea why they choose not to do it.

Being able to ship an actual development platform, rather than anemic set of Web Service would have significantly improve the possibilities of improvements.

This shows itself in many other places, where the API hands me a string of XML and expect me to do something with it. Well, what am I supposed to do with it? Why not give me an object instance that I can actually work with?

The problem is that it can't do that, because there is no way for me to get the type that it is using for the entity. The best I can do is use the generated class from the generated proxy, and obviously the CRM has no way to give me that type, since it exists in my plugin assembly alone.

Subverting Intent

You may have noticed that the field names are stuff like "new_customerpreferredrestauranttype", that is because the CRM, in its wisdom, has decided that you don't really need to have readable (PascalCase) names, even if you took the care to put them into the CRM in this manner, it will lower case them and make your life harder.

Team Development Story

This is something that is at least as big as the source control issues, and for the same reason. The CRM doesn't support any sort of team development on a project. The main issue is that you don't have a way to work locally on a CRM instance, and then commit those changes to the trunk. The entire is forced to work on the same instance, with all the associated problems that this entails.

This has a lot of scary implications, some of which are:

  • I have no control over what goes into the system, no way to review changes, no way to revert or selectively modify what goes in.
  • I can't just pull out a debugger and try to find an issue in an errant plugin, because doing so will send the entire team into a screeching halt.

 

Performance

So far I had issue with it from a developer perspective, it moves from a simple request causing the DB to take 100% for minutes to stuttering under the load of a single user. I understand that there are several fixes for those issues, but that is on a clean machine, just trying to get to grips with the way that the CRM works.

Conclusion

As you can imagine, that is not a situation that sits very well with me, and I am actively working to overcome as much of those limitations as I can. I am making good inroads, but I can't believe how bloody hard the platform make it to use it.