Code in forms

Almost every Dynamics AX developer knows them rule that business logic shouldn’t go to forms, but only few take it at least a bit seriously. It’s surprising that although developers complain about slow and hard to maintain forms, it doesn’t induce then to do something differently.

I believe it has several reasons:

  1. Programmers often don’t understand exactly what problems application logic in forms brings. So they know the rule, but they don’t understand it.
  2. Programmers don’t know how else to structure code. They know the rule, but they can’t apply it.
  3. Or they actually don’t care about quality.

Problems with code in forms

Form is copied to layer as a whole

In AX2009 and older version the form is copied to the active layer as soon as you change even a single property or method. That causes enormous troubles for upgrades and code merging. For example: if you change one method in CUS layer and another method changes later in VAR layer, the change doesn’t appear in CUS layer until you manually upgrade it. If the same happened in a class, no upgrade would be needed, because individual methods (not the whole object) are copied to higher layers.

Fortunately AX2012 has significantly reduced this problem – forms are copied in smaller parts, not as complete forms, but data sources still suffer from the same problem.

Form is not a type

Form in Dynamics AX is not type (as classes and tables, among others), it’s in essence just somehow assembled instance of FormRun class. Therefore you can’t reference it as type, which would be often useful.

For example, you can’t use forms with is and as operators. Or if you want to call a form method from another object, compiler can’t check whether the method exists, which easily leads to errors. By the way, such calls are also not taken into account in cross-references and form methods can’t be referenced by functions such as methodStr() either (e.g. in setTimeOut() calls), therefore compiler again can’t verify their existence.

See more about method calls in my article Dynamic method despatch in X++.

Reusability of code

As already mentioned, code written in a form is difficult to call safely from other objects, therefore every form should contain only the code that’s related directly to it and doesn’t represent any more general concept. All code that could be useful even somewhere else should be moved to classed, or alternatively to tables, table maps or so.

Inheritance

Because form is not individual class, you can’t create its child and inherit behavior. Nevertheless that would be often very useful. For example:

  • A single form offering different behavior for different data context. That’s implemented by a class with specialized children. See PurchTable form and its controller classes PurchTableForm_Project and PurchTableForm_Journal, for instance.
  • In many cases you can minimize changes in code of an existing class if you create its child and implement changes of behavior there. You’ll then change a single method in the parent class – you’ll set construct() to return instance of your class. Because no other methods were changed, they can’t cause layer conflicts during upgrade.
  • A child of the class may be used in unit testing to override some methods (e.g. to simulate a data source) or to provide access to instance variables and protected methods.

If you have code in a class, creating a child is trivial. In case of a form, you either have to do without the advantages described above, or you have to first refactor the code to a class.

Interfaces

I often see forms that do something like this:

Object caller = element.args().caller();
if (caller.name() = formStr(Form1)
    || caller.name() = formStr(Form2)
    || caller.name() = formStr(Form3))
{
    caller.doSomething();
}

This code basically says that the enumerated forms support some behavior represented by doSomething() method. If we want add, remove or rename a supported form, it’s necessary to change this code, with all consequences such as copying the form to the active layer etc.

Normal object-oriented code would use an interface and it wouldn’t need the enumeration of supported objects at all. Unfortunately forms are not classes and can’t implement interfaces. But you can pass an instance of the form controller class in Args.caller() or Args.parmEnum() and work with it instead of with the form.

DoSomethingProvider provider = element.args().caller() as DoSomethingProvider;
if (provider)
{
    provider.doSomething();
}

Testing

For any automated tests, it’s much easier and faster to work with classes and methods than working with the whole form, searching for controls in the form, reading their properties etc. For example, instead of finding Post button in a form and call its click() method, it’s easier to simply call post() method in the controller class.

Even for UI tests it’s sometimes useful to replace the class containing the real application logic by some other implementation – it may read test data from another data source, log information about test runs and so on.

Code on client

All form methods run on client. If you, say, send a request from client to database, it’s sent to AOS, AOS sends it to database, the database sends response to AOS and AOS finally passes it to client. Note that every connection requires some overhead, depending especially on network latency and amount of data to transfer.

Ignoring this fact is the most common reason for performance problems with forms, because if you’re not careful, it’s easy to generate a lot of requests and transfer significant amount of data.

Detailed discussion would be beyond the scope of this post, but basic rules can be summarized like this:

  1. Minimize number of calls between individual tiers (client/AOS/database). If you need several requests to database or server objects, don’t send each individual call from client. Use a server-bound method to call everything needed from AOS and returns only the result, so there will be only one call to and from server.
  2. Don’t try to move everything to AOS – if your code doesn’t need anything from server, leave it running on client.
  3. Use cache. It’s especially important for display and edit methods.

Solution

Controller class

Writing code for forms in classes is not much harder than doing it directly in forms. You just have to create a new class (by convention, the class is named as the form + Form suffix, e.g. SalesTableForm), create its instance in the form and pass into it references of all objects that you’ll need. Then everything is almost as usual, you just create methods in the new class instead of in the form.

If you work in lower versions than 2012 and your solution may be extended in higher layers (e.g. by end-user in USR layer), I recommend to create such a class as soon as you need to add any code to the form. In other cases, you may begin with code in form (unless you need to call it from other objects or so), but you must be ready to refactor it to class as soon as it gets a bit more complicated.

The controller class is tightly bound to the form, therefore it must run on client. Code that should run on server needs to be implemented in other classes or alternatively in server methods.

Surprisingly often developers ignore even already existing classes and write code directly to forms. Here the solution must be quality control and training of developers.

Refactoring

Refactoring of forms with lots of code is often quite laborious, but it’s usually good investment (in comparison with long-term problems with maintainability, performance etc.) The main problem is that it’s usually done only after the complexity of the form logic exceeded what the development team is able to maintain.

In the first phase, create a controller class and move code into it without much effort to change its structure. This is the most difficult phase, because you have to deal with all reference to automatically declared variables (form controls, data sources), you have no idea which methods are called by other objects, you don’t have sufficient support in cross-references etc.

As soon as you have code in the class, set method access modifiers (private and so on) as restrictively as possible – it will show you what you can safely refactor and where you have to take other objects into account. The second phase is about dividing code to multiple classes according to their responsibilities, requirements (e.g. server-bound), usage of inheritance and so on.

3 Comments

Leave a Reply

Your email address will not be published.